Union-based
Approach
Giả sử đường dẫn của một article nào đó là:
Kết quả hiển thị ra của trang web là:
Number of Columns in the Query
Trước tiên, ta cần thử thêm dấu '
phía sau số 1.
Trang web hiển thị lỗi như sau:
Dựa trên lỗi này giúp, ta có thể dễ dàng thực hiện SQL injection hơn.
Tiếp đến ta thử sử dụng UNION
để truy vấn ra một dữ liệu bất kỳ, chẳng hạn số 1:
Lỗi sẽ có dạng:
Ta biết được rằng, số cột của hai bảng là khác nhau. Hay nói cách khác, bảng đang được trang web truy vấn có số cột khác 1.
Ta tiếp tục thử với 1 UNION SELECT 1,2
thì vẫn lỗi. Đến khi 1 UNION SELECT 1,2,3
thì hết lỗi và nhận được article.
Database Information
Từ đây, để có thể lấy ra những dữ liệu mà ta muốn mà không phải là một article, ta cần tạo ra một câu truy vấn không có kết quả:
Lúc này, kết quả của trang web là:
Có thể thấy, cột thứ 1 sẽ là ID của article, cột thứ 2 sẽ là tiêu đề và cột thứ 3 sẽ là nội dung của article. Ta có thể thay giá trị cần truy vấn của cột thứ 3 để thu được những dữ liệu mà ta muốn, chẳng hạn như:
Kết quả là:
Như vậy, ta đã biết được tên của database là sqli_one
.
Câu truy vấn dưới đây cho phép ta lấy ra danh sách các bảng có trong database:
Lý giải một chút:
- Hàm
group_concat
nối các giá trị của một cột lại thành một chuỗi duy nhất, cách nhau bởi dấu phẩy 1. - Database
information_schema
là một metadata database có thể được truy cập bởi tất cả người dùng. Bảngtables
củainformation_schema
chứa danh sách tất cả các bảng. - Trong câu truy vấn trên, ta lấy ra danh sách các bảng có trong database
sqli_one
.
Kết quả thu được là:
Như vậy, database có hai bảng là article
và staff_users
👀.
Desired Information
Tiếp tục dùng database information_schema
để lấy ra các cột có trong bảng staff_users
:
Kết quả:
Cuối cùng, dùng hàm group_concat
để lấy ra username và password:
Có thể thấy, ta dùng dấu :
để phân cách giữa username
và password
. Đồng thời thay đổi ký tự phân cách từ dấu phẩy thành chuỗi <br>
(giúp xuống dòng).
Flag
Success
THM{SQL_INJECTION_3840}
Authentication Bypassing
Approach
Câu truy vấn có thể có dạng như sau:
Với hai trường username
và password
có giá trị được lấy từ form đăng nhập.
Bản chất của việc đăng nhập là server sẽ truy vấn xem có tồn tại một record nào đó trong bảng tài khoản match với username và password mà người dùng nhập vào hay không. Nếu câu truy vấn này trả về kết quả rỗng thì đồng nghĩa với việc đăng nhập không thành công.
Tuy nhiên, ta vẫn có thể khiến câu truy vấn này luôn đúng bằng cách thêm vào chuỗi sau:
Khi đó, câu truy vấn trở thành:
Có thể thấy, câu truy vấn này luôn đúng bởi vì OR 1 = 1
.
Flag
Success
THM{SQL_INJECTION_9581}
Brute Forcing
Approach
Trong trường hợp khác, có thể ta cần phải truy vấn được cả cặp username/password thay vì chỉ bypass authentication.
Đường dẫn của request có dạng như sau:
Response trả về là:
API này giả lập chức năng thông báo cho biết rằng tên tài khoản đã tồn tại hay chưa của các form đăng ký. Cấu trúc câu truy vấn có thể là:
Thử thay admin
thành admin123
thì kết quả là:
Thử thêm vào chuỗi admin123' UNION SELECT 1;--
vào input của username
thì ta kết quả vẫn là false
. Ta dự đoán rằng có thể số cột trong bảng users
khác 1 do đó tiếp tục thử tăng số cột của câu query sau UNION
đến khi kết quả trả về là true
.
Cụ thể, nếu dùng admin123' UNION SELECT 1,2,3;--
thì kết quả trả về sẽ là true
.
Database Name
Bước tiếp theo, ta cần tìm được tên của database, sử dụng câu truy vấn như sau:
Kết quả trả về vẫn là true
bởi vì ký tự %
có nghĩa là match tất cả các tên database.
Thử đổi thành a%
thì kết quả trả về là false
, đồng nghĩa với việc ký tự bắt đầu không phải là a
. Tiếp tục thử đến khi nào kết quả trả về là true
.
Chẳng hạn nếu ta thử s%
thì kết quả trả về là true
. Ta kết luận tên database bắt đầu bằng chữ s
.
Sau khi có được một ký tự thì ta tìm tiếp các ký tự còn lại, ký tự có thể là chữ cái, số hoặc các ký tự đặc biệt. Cuối cùng, ta tìm được tên của database là sqli_three
.
Table
Theo một cách tương tự, ta cũng dò từng ký tự để tìm tên của bảng:
Sau một hồi thử và sai, ta tìm được tên bảng là users
(thật ra có thể dùng luôn chuỗi users
thay vì dò từng ký tự):
Columns
Kế đến ta sẽ dò tên cột, payload như sau:
Mỗi khi ta tìm được một cột thì ta sẽ bỏ qua nó trong câu truy vấn. Chẳng hạn nếu ta tìm được cột có tên là id
thì ta sẽ bỏ qua cột này như sau:
Lặp lại quá trình này ba lần, ta thu được ba cột là id
, username
và password
.
Password
Ta đã biết được rằng có một tài khoản tên là admin
nên ta có thể skip qua bước dò username. Việc cần làm bây giờ chính là dò mật khẩu.
Sau khi dò từng ký tự, mỗi ký tự dò từ 1 - 9
thì ta thu được mật khẩu của admin
là 3845
.
Đăng nhập vào và ta thu được flag.
Flag
Success
THM{SQL_INJECTION_1093}
Time-based
Approach
Request có dạng như sau:
Thử thay đổi giá trị input thành:
Thì ta không nhận được bất cứ thứ gì trả về.
Để nhận biết được kết quả của câu truy vấn, ta có thể dùng hàm sleep
để tạo ra một khoảng delay nhất định.
Hàm sleep
chỉ được thực thi khi câu truy vấn UNION SELECT
được thực hiện thành công.
Khi thực thi câu truy vấn trên, nếu có xảy ra delay thì ta biết được rằng bảng đang được server truy vấn có 2 cột. Ta cũng tiến hành các bước tương tự như trong boolean-based để khai thác.
Thu được mật khẩu của admin
là 4961
.
Flag
Success
THM{SQL_INJECTION_MASTER}
Resources
Footnotes
-
Minh họa cho hàm
group_concat
: ↩