Thursday, December 31, 2009

PHP Underground Security - SQL Injection

SQL Injection không còn xa lạ với chúng ta trong những năm trở lại đây, thậm chí nó đã trở nên phổ biến và được rất nhiều người lợi dụng. Thế nhưng SQL Injection là gì ? Tìm lỗi, lợi dụng, vá lỗi như thế nào thì lại là một vấn đề khác, vấn đề mà tôi sẽ đề cập trong chủ đề này.

I. SQL Injection là gì ?
Injection nghĩa là tiêm, nhiễm và với SQL Injection ta có thể chèn (tiêm) thêm truy vấn SQL vào câu truy vấn dựa vào lỗi của ứng dụng web.

II. Các ứng dụng của SQL Injection:
Trước khi đi tìm hiểu sâu vào việc lợi dụng (exploit) thì các bạn cần nhớ vài kí tự quan trọng mà ta sẽ phải dùng khi thực hành.
- dấu nháy đơn ('): dấu này trong ngôn ngữ SQL dùng để "gói" chuỗi. Ta thường thêm nó vào sau tham số kiểu số trên chuỗi truy vấn để kiểm tra có lỗi hay không. Nguyên nhân là do không kiểm tra kiểu dữ liệu.
- dấu thăng (#) và dấu (--): các dấu này để đánh dấu chú thích, nghĩa là những kí tự đứng sau một trong hai dấu này trên cùng một dòng sẽ được xem là chú thích được bỏ qua khi thực hiện truy vấn.
- dấu (;): dùng để kết thúc một truy vấn và tất nhiên sau nó là bắt đầu một truy vấn khác. Đôi khi ta dùng union để nối hai câu truy vấn.

Và sau đây là các ứng dụng lợi dụng lỗi SQL Injection:

1/ Login Bypassing – đoạt quyền Admin
Xem code PHP của file login.php
$logged = 0;
 
$nick = $_POST['nick'];
$pass = $_POST['pass'];
 
$db = mysql_connect('localhost','user','pass') or die('Error: '. mysql_error());
mysql_select_db("dbname", $link);
 
 
$query = "SELECT * FROM dbname WHERE nick ='".$nick."' AND pass ='" .$pass. "'";

$result = mysql_query($query, $db);
 
if (mysql_num_rows($result) == 0) {
echo "Denied! Your Login is Incorrect";
exit;
}
 
$logged = 1;
 
[...]
 
//EoF
Ta thấy rằng nếu truy vấn không trả về bảng ghi nào tồn tại trong CSDL thì lập tức người dùng sẽ được đưa trở về trang index.html, ngược lại sẽ đăng nhập thành công.

Lưu ý: Ở đây tôi dùng "bản ghi" (record) thay cho "kết quả" cho đúng với bản chất.

Code trên tuy đơn giản nhưng ta thấy rằng chỉ cần làm cho truy vấn có bản ghi trả về thì ta sẽ đăng nhập thành công mà không cần biết Nick hay Pass.

Phần này đòi hỏi người đọc phải biết một tí về câu lệnh Select và điều kiện của mệnh đề Where, kết quả của phép toán OR.

Nếu ta đăng nhập với nick và pass không khớp với một bản ghi trong CSDL thì tất nhiên không có bản ghi nào thoả mãn để được trả về.

Nhưng giả sử ta login với thông tin sau:
$nick = xyz' OR '1' = '1 
$pass = xyz' OR '1' = '1
thì câu truy vấn sẽ trở thành
"SELECT * FROM sql_inj WHERE nick ='xyz' OR '1' = '1' AND pass ='xyz' OR '1' = '1'" 
xyz là một chuỗi gì đó bất kì, quan trọng là phần OR 1 = 1
Câu truy vấn kia sẽ: lựa tất cả các bản ghi nào thoả điều kiện (WHERE) nick = xyz hoặc 1=1 và pass=xyz hoặc 1=1
Mà 1 = 1 là hằng đúng nên dù (nick = xyz) là FALSE thì kết của của (nick = xyz OR 1=1) sẽ là TRUE, tương tự với (pass=xyz hoặc 1=1) cũng sẽ là TRUE, mà (true AND true) = true. (ta có thể xem 0 là FALSE và 1 là TRUE).

Vậy với điều kiện là một hằng đúng thì mọi bản ghi đều thoả rồi, vậy nên trong CSDL có bao nhiêu bản ghi (tương ứng chừng ấy User) sẽ được trả về, và khi đó ...

Ngoài ra ta còn có thể làm ngắn gọn hơn vì chỉ cần nhập
$nick = xyz' OR '1' = '1 #
hoặc
$nick = xyz' OR '1' = '1 --
là được.


2/ Modify database - Thay đổi dữ liệu
Cũng từ VD trên, ta có thể thay đổi password, email của admin bằng cách:
$nick = xyz'; UPDATE users SET email = 'email_của_mình' WHERE nick='admin';#
Sau đó ta chỉ việc forgot pass admin và vào check mail mình là xong. Tương tự cho cách đổi pass admin:
$nick = xyz' UNION UPDATE users SET password = 'pass mới' WHERE nick='admin';#
Ngoài ra còn có rất nhiều cách để lợi dụng như nâng quyền của mình lên Admin, tìm tên CSDL và thậm chí DROP Database.

Đối với phương thức GET thì ta cũng sử dụng tương tự bằng cách đưa tham số lên URL.


III. Cách tìm lỗi:
Qua VD bạn nhận thấy đặc trưng của SQL Injection là gì ? Đó chính là sử dụng dữ liệu được nhập vào trong câu truy vấn mà không qua kiểm tra.

IV. Cách vá lỗi:
1. Mởi php.ini và set magic_quotes_gcp thành On
Nó sẽ chèn "\" trước (') có trong:
-COOKIE
-POST
-GET

2. Sử dụng hàm addslashes() để "gói" chuỗi bằng dấu "/"

3. Sử dụng hàm htmlspecialchars(), mysql_escape_string() ... để mã hoá kí tự đặc biệt trong câu truy vấn

4. Ép kiểu:
- Ta biết id của một đối tượng (user, category, box, product ...) luôn là kiểu số nguyên, vì vậy ta dùng:
$id = (int)$_POST['id'];
hoặc
$id = (int)$_GET['id']; 

hoàn tất :)

No comments:

Post a Comment