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.
| |

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.

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:
| |

Vì 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ề:
| |
Sau đó, tôi kiểm tra một điều kiện sai:
| |

Vì 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ư dual và TO_CHAR(1/0). Tôi cũng kiểm tra một payload Oracle UNION SELECT đơn giản:
| |

Ứ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:
| |

Ứ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:
| |

Ứ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ư USERNAME và PASSWORD, 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.


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:
| |



Ứ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:
| |

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.

Kết quả từ Turbo Intruder cho thấy các ký tự được trích xuất như sau:
| |
Do đó, mật khẩu administrator khôi phục được là:
| |
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:
| |

Ứ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:
| |
Sau khi đăng nhập thành công, bài lab hiển thị trạng thái “Solved”.

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() và 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.