II. Phân tích và khai thác các lỗ hổng SQL Injection (tiếp)
5. Khai thác lỗ hổng SQL injection - Truy xuất dữ liệu trong các bảng (database tables) (tiếp)
5.3. Khai thác dữ liệu từ các bảng với cột hiển thị hạn chế
Từ các ví dụ trên chúng ta thấy lượng thông tin chúng ta cần khai thác thường gồm nhiều trường, và trên thực tế số lượng đó sẽ còn lớn hơn. Có thể một trường hợp xấu xảy ra, là số cột dữ liệu tương thích với kiểu string hạn chế, hay nói cách khác là chúng ta chỉ có sử dụng lượng cột hiển thị dữ liệu nhỏ hơn số trường dữ liệu cần khai thác. Một cách tự nhiên, chúng ta có thể nghĩ tới cách khiến các dữ liệu cần khai thác hiển thị đồng thời trong cùng một cột - bằng cách nối chuỗi (ghép chuỗi).
Với mỗi hệ cơ sở dữ liệu khác nhau có các cú pháp nối chuỗi khác nhau, một số ví dụ:
database management system | String concatenation syntax |
---|---|
Oracle | 'Vi'\|\|'blo' |
Microsoft | 'Vi'+'blo' |
PostgreSQL | 'Vi'\|\|'blo' |
MySQL | 'Vi' 'blo' hoặc CONCAT('Vi','blo') |
Kết quả từ các cú pháp nối chuỗi trên đều cho ra từ Viblo. Ngoài ra, để dữ liệu hiển thị được dễ dàng tìm kiếm, chúng ta có thể sử dụng các cú pháp nối chuỗi tương ứng nhằm phân tách các trường dữ liệu bằng các ký hiệu đặc biệt. Xét ví dụ payload khai thác trường username và password trong bảng users, hệ cơ sở dữ liệu PostgreSQL như sau:
' UNION SELECT NULL, username || '~' || password FROM users--
Khi đó kết quả hiển thị theo dạng username~password
như sau:
administrator~1qazSDmy6g32as
usertest~123456asd
guest~guest
...
Phân tích lab SQL injection UNION attack, retrieving multiple values in a single column
Miêu tả: Trang web chứa lỗ hổng SQL injection trong chức năng bộ lọc hiển thị sản phẩm. Kết quả được hiển thị trong giao diện trả về. Để giải quyết bài lab, chúng ta cần khai thác lỗ hổng nhằm truy xuất thông tin người dùng administrator trong bảng users, biết rằng bảng này chứa hai cột username và password.
Kiểm tra lỗ hổng SQL injection tại tham số category
, payload /filter?category=' OR 1=1--
Kiểm tra số cột dữ liệu trả về của câu lệnh truy vấn. Payload: /filter?category=Pets' UNION SELECT NULL, NULL--
Như vậy câu lệnh truy vấn trả về cột dữ liệu. Tiếp theo tìm kiếm cột dữ liệu tương thích với kiểu string. Payload /filter?category=' UNION SELECT NULL, 'column2'--
và /filter?category=' UNION SELECT 'column1', NULL--
. Ở đây chỉ có cột có thể lợi dụng để hiển thị thông tin khai thác:
Do chúng ta đã biết tên bảng cần khai thác là users cũng với hai cột có tên là username và password, nên ta có kết hợp nối chuỗi để hiển thị đồng thời hai cột dữ liệu username và password. Payload: /filter?category=' UNION SELECT NULL, username || ':' || password FROM users--
Câu truy vấn khi đó sẽ truy xuất thông tin từ hai cột username, password trong bảng users và hiển thị dữ liệu theo định dạng username:password
, kết quả:
Đăng nhập với tài khoản administrator:kxdurl7kgn5rblp42i59
, bài lab hoàn thành:
6. Blind SQL injection
6.1. Sử dụng điều kiện logic khai thác lỗ hổng Blind SQL injection
Trong các trường hợp trên thì các dữ liệu truy xuất đều được hiển thị ra giao diện khiến kẻ tấn công có thể tận dụng lỗ hổng SQL injection nhằm truy xuất và đọc dữ liệu nhạy cảm. Vậy thì, khi câu truy vấn không còn hiển thị dữ liệu tới người dùng, lỗ hổng SQL injection có còn xảy ra không? Thật bất ngờ khi câu trả lời vẫn là có! Các lỗ hổng dạng này được gọi là Blind SQL injection. Có thể nhiều bạn có một thắc mắc: Kẻ tấn công không thể đọc được dữ liệu, thì kể cả payload tấn công có thực thi, dữ liệu được truy xuất, thì có tác dụng gì nhỉ? Thật thú vị khi trong trường hợp này, chúng ta vẫn có thể "đọc" được các thông tin nhạy cảm, tuy nhiên phương thức tấn công sẽ đặc biệt hơn!
Xem xét đoạn code php sau:
<?php if( isset( $_GET[ 'Submit' ] ) ) { // Get input $id = $_GET[ 'id' ]; // Check database $getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id';"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $getid ); // Removed 'or die' to suppress mysql errors // Get results $num = @mysqli_num_rows( $result ); // The '@' character suppresses errors if( $num > 0 ) { // Feedback for end user echo '<pre>User ID exists in the database.</pre>'; } else { // User wasn't found, so the page wasn't! header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' ); // Feedback for end user echo '<pre>User ID is MISSING from the database.</pre>'; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
} ?>
Tham số id
có thể thay đổi tùy ý. Các trường dữ liệu được truy vấn first_name, last_name không được hiển thị ra giao diện, mà chỉ trả về các thông báo User ID exists in the database khi id người dùng tồn tại, User ID is MISSING from the database khi id người dùng không tồn tại.
Chính các dòng thông báo khác nhau này có thể giúp chúng ta "đọc" các dữ liệu nhạy cảm. Trước hết, xác định ký tự comment:
Dấu comment hợp lệ là ký tự #
. Phương pháp tấn công đặc biệt này dựa vào cơ sở các biểu thức logic:
Dựa vào thông báo trả về chúng ta có thể xác định biểu thức sau and là đúng hoặc sai. Vẫn hơi khó hình dung phải nhỉ, cùng xem input sau đây nhé:
Biểu thức logic cũng có thể áp dụng đối với các ký tự, như vậy có thể sử dụng các cú pháp lệnh SQL trong một vế, kết hợp các ký tự cụ thể ở vế còn lại nhằm xác định kết quả truy vấn là đúng hay sai. Cùng xem xét một số câu lệnh thường sử dụng trong tấn công Blind SQL injection nhé:
- Length: độ dài
database management system | Length syntax |
---|---|
Oracle | LENGTH(string_expression) |
Microsoft | LEN(string_expression) |
PostgreSQL | LENGTH(string_expression) |
MySQL | LENGTH(string_expression) |
Các câu lệnh trên trả về độ dài string_expression, có thể sử dụng lệnh truy vấn trong các hàm LENGTH. Ví dụ trong khai thác Blind SQL injection:
Như vậy chúng ta xác định được độ dài tên cơ sở dữ liệu gồm ký tự.
- Substring: lấy chuỗi con
database management system | Substring syntax |
---|---|
Oracle | SUBSTR('Viblo', 4, 2) |
Microsoft | SUBSTRING('Viblo', 4, 2) |
PostgreSQL | SUBSTRING('Viblo', 4, 2) |
MySQL | SUBSTRING('Viblo', 4, 2) |
Kết quả các câu lệnh trên đều trả về chuỗi lo nghĩa là lấy hai ký tự bắt đầu từ vị trí số (tính từ ). Có thể sử dụng lệnh truy vấn trong các hàm SUBSTRING. Ví dụ trong khai thác Blind SQL injection:
Từ đây ta có thể xác định ký tự đầu tiên của tên cơ sở dữ liệu là d
, tương tự như vậy chúng ta có thể xác định toàn bộ tên cơ sở dữ liệu, tên bảng, tên cột, các ký tự của dữ liệu.
Để hiểu rõ về phương pháp khai thác sử dụng các hàm LENGTH() và SUBSTRING(), chúng ta cùng phân tích kỹ hơn trong lab sau:
Phân tích lab Blind SQL injection with conditional responses
Miêu tả: Trang web chứa lỗ hổng SQL injection dạng Blind khi phân tích và thực hiện truy vấn SQL bằng cookie theo dõi (tracking cookie), trong câu truy vấn có chứa giá trị của cookie đã gửi. Kết quả của lệnh truy vấn SQL không được hiển thị, tuy nhiên ứng dụng sẽ hiển thị thông báo Welcome back! trong giao diện nếu truy vấn trả về dữ liệu hợp lệ. Chúng ta cần khai thắc lỗ hổng nhằm tìm kiếm mật khẩu tài khoản administrator, biết rằng trong cơ sở dữ liệu chứa bảng users, gồm cột username và password.
Chúng ta đã biết trang web chứa lỗ hổng Blind SQL injection tại cookie TrackingId. Giả sử câu truy vấn trang web sử dụng có dạng như sau:
SELECT TrackingId FROM TrackedUsers WHERE TrackingId = '3VdsB26Bc9ZSWws8'
Chúng ta có thể xác nhận lỗ hổng tồn tại bằng cách thêm biểu thức logic vào giá trị cookie TrackingId, payload:
Khi câu truy vấn có logic đúng thì thông báo Welcome back! được hiển thị, như vậy chúng ta có thể chắc chặn tại cookie này chứa lỗ hổng Blind SQL injection.
Đề bài đã cho biết trang web chứa bảng users, kiểm tra sự tồn tại của bảng users như sau:
Với các thông tin được cho, chúng ta có câu lệnh truy vấn mật khẩu user administrator như sau:
SELECT password FROM users WHERE username='administrator'
Kết hợp câu lệnh trên, chúng ta có thể tạo payload kiểm tra độ dài mật khẩu này như sau:
' AND LENGTH(CAST((SELECT password FROM users WHERE username='administrator') AS varchar))=1--
Hàm CAST() có tác dụng chuyển kết quả lệnh truy vấn sang dạng varchar. Từ đây chúng ta xác định được độ dài mật khẩu này bằng , sử dụng chức năng Intruder trong Burp Suite:
Cuối cùng, sử dụng hàm SUBSTRING() xây dựng payload xác định từng ký tự của mật khẩu như sau:
' AND SUBSTRING((SELECT password FROM users WHERE username='administrator'),1,1)='a
Lần lượt xác định từng ký tự, cuối cùng chúng ta thu được password của administrator với ký tự và đăng nhập để hoàn thành bài lab: