【PHP】大手サービスのログイン処理に（悪い意味で）驚いた件

1. VALUE-DOMAIN では、API が提供されていない
2. 仕方ないから力技で、管理画面へログインしてあれこれするプログラムを作ろう
3. とりあえずログイン処理を書こう
4. あれ？ ログイン画面が CSRF 対策されてない
5. あれ？ GET メソッドも受け付けるっぽい（今ココ）

※なお、この記事の趣旨は、実在するサービスを例に、悪い部分はマネしないよう、反面教師としようというものです。ディスる意図はありません。

--------------------------------------------------------------------------------

## VALUE-DOMAIN へログインする処理

PHP だとこんなイメージです。

```php
//-- 後から分かりましたがGETメソッドでも良いみたいです
$http->post('username', '*****')
     ->post('password', '*****')
     ->post('action', 'login2')
     ->fetch('https://www.value-domain.com/login.php');
```

要するに、login.php へ、GET か POST メソッドで、`username` と `password` を投げるだけでログインできます。

CSRF 対策用トークンを発行 ⇒ 照合する処理などは、行われていないようです。

--------------------------------------------------------------------------------

## メリット？

ログイン処理が、とても簡単に書けました(汗)

プログラムを書けない人でも…

```html
<form action="https://www.value-domain.com/login.php" method="post">
<input type="hidden" name="username" value="*****">
<input type="hidden" name="password" value="*****">
<input type="hidden" name="action" value="login2">
<input type="submit" value="ログイン！">
</form>
```

こんな HTML を用意すれば、ワンクリックでログインできますね。[^1]

[^1]: 古き良き時代？は、こんな感じでログインできるのが、わりと普通ではありました。

後から分かったのですが、GET メソッドでも良いみたいなので、`https://www.value-domain.com/login.php?username=*****&password=*****&action=login2` という URL でログインできてしまいます。

~~令和の時代に、これはなかなか斬新です。~~

--------------------------------------------------------------------------------

### ログイン処理で GET メソッド

**POST メソッドにすれば安心という話では決してない**のですが、ID やパスワードを受け取るログイン処理では、GET メソッドは使えないようにするのが普通です。

理由は、[IPA のセキュア Web アプリプログラミング【暴露対策】](https://www.ipa.go.jp/security/awareness/vendor/programmingv2/contents/403.html) 辺りを参考にしてみてください。

細かい話を抜きにしても、ウェブアプリに携わる方であれば、URL に ID とパスワードを載せればログインできるというのは、感覚的・直感的に「気持ち悪い」と思えるようになりたいものです。

--------------------------------------------------------------------------------

### ブルートフォースアタック

このような設計は、ブルートフォースアタック（総当たり攻撃）にも弱いです。

`https://www.value-domain.com/login.php?username=□&password=△&action=login2` の、□と△との組み合わせを、辞書を参考にしながら変えていくだけですので、プログラミングを始めて 1 週間ぐらいの人でも試せてしまいます。

**ブルートフォースアタックの対策は、POST or GET の話や、CSRF 対策の話とはまた別に考える必要があります**が、その敷居を多少上げるという意味でも、このような設計は避けたいところです。

なお、ブルートフォースアタック対策の基本的な考え方は、[IPA のセキュア Web アプリプログラミング【アクセス制御/ユーザ認証】](https://www.ipa.go.jp/security/awareness/vendor/programmingv2/contents/101.html) 辺りを参考にしてください。（くどいようですが、他の対策と混同しないようご注意を。）

--------------------------------------------------------------------------------

## ログイン画面の CSRF 対策は必要か

あまり深く考えた事もなかったのですが、Qiita の記事を発見しました。

- [ログイン時のCSRF対策は必要か](https://qiita.com/khsk/items/f654ddddc249f1f7c5e2)

--------------------------------------------------------------------------------

### 個人的な意見

仮に、このログイン画面で CSRF 対策用のトークンを発行するようになっていたとしても…

```html
<form action="" method="post">
<input type="hidden" name="token_example" value="e54e4c85c3aab2dfe3782e6bee5742d39899e227edc1b437d7fd30b1f1f7c3a8">
```

token_example の値を取得して、ログイン処理を作っていたでしょう。[^2]

[^2]: その場合、[1]ログイン画面を取得 ⇒ [2]token_example の値を取得 ⇒ [3]セッションを引き継ぎながら、ログインリクエストを送る…という流れになります。

ただ、XSS や CSRF 対策あたりは、私のような凡人が無い頭を絞って考えなくとも、頭の良い人達が生み出したフレームワークに任せれば良い話だと思っています。[^3][^4]

[^3]: 弁解しておくと、大昔は自分で処理を考えて実装してました。

[^4]: もちろん、フレームワークにも脆弱性が見つかる事はあり、中には [ThinkPHP](https://www.google.co.jp/search?q=ThinkPHP+%E8%84%86%E5%BC%B1%E6%80%A7&ie=utf-8&newwindow=1) のような、とんでもないレベルのものもあります。

令和の時代なら、手間をかけずに簡単に実装できるものですので、別に悩むようなことではなく、ログイン画面にもログアウト画面にも入れれば良いと思います。

--------------------------------------------------------------------------------

## output_add_rewrite_var()

VALUE-DOMAIN の管理画面は、PHP で作られているようなのですが、HTML ソースや HTTP ヘッダを見る感じでは、おそらくかなり昔にスクラッチ開発されたものなのだと思います。[^5][^6]

[^5]: 例えば、セッション処理で、`PHPSESSID=[0-9a-f]{32}` という PHP デフォの設定が使われています。セッション周りを見ると、使っている FW が分かる事も多いです。

[^6]: VALUE-DOMAIN のサービスは、2011 年に、デジロックから現在の GMO 傘下になったようですが、大人の事情で、デジロック時代に作られたシステムが、依然としてベースになっているのではないかと予想しています。

そういった、モダンなフレームワークを使えない（であろう）環境でも、[output_add_rewrite_var()](https://www.php.net/manual/ja/function.output-add-rewrite-var.php) を使うと、とても簡単に CSRF 対策を実装できます。

```php
output_add_rewrite_var('token_example', hash('sha256', session_id()));

//-- もし session_id() だけでは不安なら適当に種を混ぜるとか
output_add_rewrite_var('token_example', hash('sha256', getenv('SERVER_ADDR') . session_id()));
```

たったこれだけで、[`url_rewriter.tags`](https://www.php.net/manual/ja/outcontrol.configuration.php) の設定に従って `token_example` という名前で HTML をリライトしてくれますので、後は受け取ったパラメータをチェックするだけです。

```php
if (filter_input(INPUT_POST, 'token_example') !== hash('sha256', session_id())) {
    // エラー処理
}
```

出力を自動でバッファリング＋リライトしてくれますので、トークンの埋め込み忘れも起こりません。[^7]

[^7]: PHP 7.1 からは、[セッションのリライト設定とは分離された](https://www.php.net/manual/ja/session.configuration.php#ini.session.trans-sid-tags)ので、その辺りの懸念もなくなりました。

実用主義の PHP らしい機能だと思います。

```php
//-- ガラケー時代のDoCoMoのウ○コ仕様にも、たった1行で対応できました
output_add_rewrite_var('guid', 'ON');
```

--------------------------------------------------------------------------------

### 余談

細かい事？を言えば、クッキーにユーザー名が生のまま入ってたりするのも、気になったりします。[^8]

[^8]: さすがに、「クッキーにユーザー名があったら、そのユーザー名でログインしている」なんて判定にはなってないようですが…。

気になって少し調べると、過去には↓

- [VALUE\-DOMAIN に存在していたアカウント乗っ取り可能な CSRF 脆弱性について](http://debiru.hatenablog.com/entry/20160407/value-domain-csrf)

↑のような事もあったみたいです。今回の記事を書くに辺り初めて知ったのですが、あまり知られていないのではないでしょうか。

規模的には大手のサービスだと思いますが、二段階認証にも対応していないようですので、今後大きな事件が起こらない事を、心より願っております。