Blind SQL: Bẻ khóa mật khẩu với phản hồi có điều kiện
Ý kiến
0
Chưa có ý kiến nào. Hãy là người đầu tiên chia sẻ!
Chưa có ý kiến nào. Hãy là người đầu tiên chia sẻ!
Blind SQL Injection với phản hồi có điều kiện (Boolean-based) là kỹ thuật tấn công cơ sở dữ liệu xảy ra khi trang web không trực tiếp hiển thị dữ liệu hoặc thông báo lỗi ra màn hình. Để lấy cắp thông tin, kẻ tấn công chèn các mệnh đề logic (Đúng/Sai) vào truy vấn gốc để đặt ra các "câu hỏi" cho hệ thống.
Bằng cách quan sát sự thay đổi trên giao diện web (ví dụ: trang hiển thị bình thường nếu câu hỏi Đúng, hoặc bị thiếu dữ liệu nếu Sai), kẻ tấn công có thể nhận biết được kết quả. Quá trình dò dẫm True/False này được tự động hóa và lặp lại liên tục để trích xuất lén lút từng ký tự của dữ liệu nhạy cảm.
Đề bài:
Phòng thí nghiệm chứa lỗ hổng ở tính năng sử dụng COOKIE theo dõi (tracking cookie) để phân tích và thực hiện truy vấn SQL chứa giá trị của cookie đã gửi.
Kết quả truy vấn sẽ không trả về bất kỳ thông báo lỗi nào, nhưng sẽ hiển thị dòng chữ “Welcome back!” nếu truy vấn là đúng.
Có một bảng dữ liệu là users chứa thông tin username và password của tất cả người dùng. Mục tiêu của bạn là lấy được mật khẩu của tài khoản có tên là administrator.
Gợi ý: Mật khẩu chỉ bao gồm các ký tự chữ và số thường (a-z, 0-9).
Phân Tích: Ta có thể thấy bài lab này không cho chúng ta biết lỗi hệ thống là gì khi gửi request, nhưng lại trả về “Welcome back!” khi request chứa điều kiện đúng. Nói cách khác, đây là một trò chơi "đúng/sai", hệ thống sẽ gật đầu khi bạn hỏi đúng và lắc đầu khi bạn hỏi sai.
Mục tiêu cuối cùng là tìm ra mật khẩu của administrator.
Xác nhận xem lỗ hổng Boolean có thực sự hoạt động không.
Kiểm tra sự tồn tại của bảng và user mục tiêu.
Tìm ra độ dài của mật khẩu.
Dò và xác định từng ký tự của mật khẩu (Sử dụng công cụ Intruder trong Burp Suite).
Mở Burp Suite. Thực hiện truy cập bài lab bằng trình duyệt tích hợp, sau đó vào Target → Site map để khóa mục tiêu vào URL của lab. Gửi request chứa Method=GET về Repeater (Ctrl + R) để giả lập mô phỏng.
Trong Request, tìm phần Header chứa Cookie: TrackingId=.... Tại đây, ta kiểm thử xem web có dính lỗi không bằng cách chèn một đoạn mã mô phỏng điều kiện Đúng/Sai:
' AND 1=1--
Dùng AND để buộc web phải thực hiện thêm một điều kiện. Vì 1=1 là một điều kiện đúng (TRUE), trang web trả về thông báo Welcome back!.
Tiếp tục thử với điều kiện sai:
' AND 1=0--
Vì 1=0 là sai (FALSE), kết quả trả về sẽ bị mất dòng chữ Welcome back!.
-> Kết luận: Trang web có cơ chế phản hồi True-False dựa trên truy vấn do người dùng nhập vào.
Xác nhận sự tồn tại của bảng “users”:
' AND (SELECT 'a' FROM users LIMIT 1) ='a'--
Khi gửi payload này, bạn đang hỏi Database: "Trong bảng users có tồn tại dữ liệu không? Nếu có hãy trả về 'a'. Khi đem so sánh với 'a', nó có bằng nhau không?"
Nếu hiển thị Welcome back!: Database xác nhận có (TRUE).
Nếu không hiển thị: Database xác nhận không (FALSE).
Sau khi đã xác nhận được sự tồn tại của bảng “users” thì tiếp tục mò vào dữ liệu của nó.
' AND (SELECT 'a' FROM users WHERE username='administrator')='a'--
Payload này mang ý nghĩa: "Hãy tìm trong bảng users, có user nào tên là administrator không? Nếu có, trả về kết quả 'a'."
Nếu tìm thấy: Kết quả là 'a'='a' -> Đúng (TRUE).
Nếu không thấy: Kết quả là NULL='a' -> Sai (FALSE).
Như vậy việc xác định sự tồn tại của thư mục users và user administator đã hoàn tất. Tiếp theo đây chính là phần thú vị nhất của bài Lab này chính là quá trình xác định và bẻ khóa mật khẩu.
Bước 1: Xác định độ dài mật khẩu
Muốn dò mật khẩu thì điều đầu tiên phải biết nó dài bao nhiêu. Tiến hành sử dụng Repeater để thử nghiệm:
' AND (SELECT 'a' FORM users WHERE username='administator' AND LENGTH(password) >1 ='a'--
Sau khi gửi payload, nó hỏi database rằng:
“Ê database, trong bảng “users” có thằng nào tên là ‘administator’ mà mật khẩu của họ có độ dài lớn hơn 1 không?”
Trường hợp 1: Nếu có trả về ‘a’ =’a’ hiển thị Welcom back!
Trường hợp 2: Nếu không trả về ‘Null’ =’a’ làm mất thông báo
Hãy thay đổi giá trị từ 1 -> 2 -> 3... cho đến khi màn hình không còn trả về Welcome back! nữa. Con số làm cho điều kiện bị FALSE chính là giới hạn độ dài của mật khẩu.
Để tiết kiệm thời gian, ta đưa request này sang Intruder (Ctrl + I):
Tại vị trí số 1, ta bôi đen và nhấn Add § để biến nó thành biến thay đổi: LENGTH(password)=§1§.
Trong tab Payloads, chọn Payload type: Numbers.
Cấu hình: From: 1 | To: 30 | Step: 1.
Bắt đầu tấn công. Dựa vào cột Length (độ dài phản hồi) của kết quả, bạn sẽ thấy con số chính xác khiến chữ "Welcome back!" xuất hiện.
Đây là phần thú vị và tốn thời gian nhất. Ta sử dụng payload sau:
' AND (SELECT SUBSTR(password,1,1) FROM users WHERE username='administrator')='a'--
#Chú thích:
SUBSTR(string,start-position,length)
String: Chuỗi ký tự,tên cột muốn trích xuất
Start-position: Vị trí bắt đầu (trong SQL vị trị bắt đầu là 1, không phải 0). Nếu dùng số âm, vị trí sẽ đọc ngược
length: số ký tự cần lấy, Nếu bỏ qua, hàm sẽ lấy toàn bộ phần còn lại của chuỗi
Như thế ta tiến hành đưa vào Intruder để chạy, vì chúng ta cần thay đổi không chỉ một mà tới hai tham số chính là vị trí của mật khẩu và kí tự của mật khẩu tại vị trí đó, nên ta chọn “Cluster bomb Attack- thay vì Sniper attack”.
' AND (SELECT SUBSTR(password,§1§,1) FROM users WHERE username='administrator')='§a§'--
Payload thứ nhất (vị trí của mật khẩu- số): ta set-up như phần trên
Payload thứ hai (kí tự của mật khẩu tại vị trí đó): ta chọn Payload type là Brute forcer
Character set: abcdefghijklmnopqrstuvwxyz0123456789 (vì kiểu dữ liệu chỉ có a-z0-9)
Min length: 1
Max length: 1
Mẹo nhỏ (Grep-Match): Qua tab Settings, tìm mục Grep-Match và thêm chuỗi Welcome back. Công cụ sẽ tự động đánh dấu tick vào các request thành công, giúp ta nhặt ra các ký tự đúng một cách cực kỳ nhanh chóng.
Nhấn Start attack. Các payload trả về True sẽ ghép lại thành mật khẩu hoàn chỉnh của administrator. Đăng nhập và bạn đã giải quyết xong bài Lab!
Qua quá trình khai thác trên, chúng ta có thể rút ra những nguyên lý cốt lõi về bảo mật:
Dữ liệu đầu vào (Input) luôn là nguồn nguy hiểm: Lỗ hổng xuất phát từ việc hệ thống đã lấy trực tiếp giá trị TrackingId từ Cookie do người dùng gửi lên và ghép thẳng vào câu lệnh SQL.
Không có thông báo lỗi không có nghĩa là an toàn: Các nhà phát triển thường nghĩ rằng chỉ cần "giấu" thông báo lỗi của Database đi là sẽ chống được SQL Injection. Tuy nhiên, kỹ thuật Boolean-based Blind SQLi chứng minh rằng: Chỉ cần ứng dụng có sự thay đổi hành vi (dù là nhỏ nhất) giữa một truy vấn Đúng và Sai, kẻ tấn công hoàn toàn có thể đặt câu hỏi Yes/No để tái tạo lại toàn bộ dữ liệu.
Mật khẩu lưu dưới dạng Plain-text là một thảm họa: Trong bài lab, mật khẩu được lưu dưới dạng văn bản thô (chỉ có a-z, 0-9) và không được mã hóa hay băm (hash). Điều này giúp quá trình Brute-force diễn ra cực kỳ nhanh gọn.
Để ngăn chặn hoàn toàn lỗ hổng SQL Injection (bao gồm cả các biến thể phức tạp như Blind SQLi), đội ngũ phát triển cần áp dụng các biện pháp bảo mật theo chiều sâu. Dưới đây là các phương pháp tối ưu nhất:
Thay vì cộng chuỗi trực tiếp dữ liệu từ người dùng vào câu lệnh SQL, hãy luôn sử dụng Prepared Statements. Kỹ thuật này buộc cơ sở dữ liệu phải biên dịch cấu trúc câu lệnh SQL trước, sau đó mới đưa dữ liệu của người dùng vào dưới dạng một "tham số" thuần túy.
Nhờ cơ chế này, mọi đoạn mã độc (như ' AND 1=1--) sẽ bị tước bỏ quyền thực thi và chỉ được Database hiểu là một chuỗi văn bản vô hại.
Ví dụ minh họa bằng PHP (sử dụng thư viện PDO):
PHP
<?php
// ❌ CÁCH LÀM SAI (Dễ bị SQL Injection):
// Lấy trực tiếp giá trị từ Cookie và cộng thẳng vào chuỗi truy vấn SQL
$tracking_id = $_COOKIE['TrackingId'] ?? '';
$sql = "SELECT * FROM tracking_logs WHERE id = '" . $tracking_id . "'";
$stmt = $pdo->query($sql); // Cực kỳ nguy hiểm! Kẻ tấn công có thể thao túng $tracking_id
// ✅ CÁCH LÀM ĐÚNG (Sử dụng Prepared Statement):
// Đặt một "placeholder" (ví dụ: :id) thay vì nối chuỗi
$tracking_id = $_COOKIE['TrackingId'] ?? '';
$sql = "SELECT * FROM tracking_logs WHERE id = :id";
// Bước 1: Prepare - Gửi câu lệnh SQL thuần túy lên Database để biên dịch trước
$stmt = $pdo->prepare($sql);
// Bước 2: Execute - Truyền dữ liệu an toàn vào
// Database sẽ chỉ coi giá trị truyền vào là một chuỗi văn bản (String), không phải câu lệnh (Command)
$stmt->execute(['id' => $tracking_id]);
$result = $stmt->fetchAll();
?>
Bài lab trên trở nên dễ dàng một phần vì mật khẩu được lưu ở dạng văn bản thô (plain-text). Trong thực tế, tuyệt đối không được lưu trữ mật khẩu rõ ràng trong Database.
Hãy sử dụng các thuật toán băm (Hash) mạnh mẽ có kèm theo Salt như Bcrypt, Argon2 (được hỗ trợ sẵn trong PHP qua hàm password_hash()). Ngay cả khi kẻ tấn công có trích xuất được cơ sở dữ liệu qua Blind SQLi, chúng cũng chỉ thu được một chuỗi băm vô nghĩa và tốn hàng nghìn năm để bẻ khóa.
Hãy luôn xây dựng tư duy "Không tin tưởng bất kỳ dữ liệu nào từ người dùng". Thiết lập danh sách trắng (White-list) và kiểm tra chặt chẽ định dạng dữ liệu đầu vào:
Nếu TrackingId được quy định là một chuỗi UUID, hãy dùng Regular Expression (RegEx) để kiểm tra đúng chuẩn UUID trước khi đưa vào xử lý.
Nếu dữ liệu là số nguyên (ID, Age), hãy ép kiểu (type casting) về số nguyên (int).
Nếu phát hiện các ký tự lạ, vi phạm định dạng chuẩn (như dấu phẩy ', gạch ngang --), hệ thống cần từ chối request đó ngay lập tức.
Sử dụng các hệ thống tường lửa ứng dụng web (như AWS WAF, Cloudflare WAF, ModSecurity) để tạo ra lớp khiên chắn từ bên ngoài. WAF được tích hợp sẵn các bộ quy tắc (rules) liên tục được cập nhật để tự động phân tích lưu lượng HTTP, phát hiện và chặn đứng các chuỗi request chứa dấu hiệu đặc trưng của SQL Injection trước khi chúng kịp chạm tới máy chủ ứng dụng của bạn.