メールヘッダ・インジェクションの挙動を実際に手を動かして確認した

みなさんは、メールヘッダ・インジェクション見たことありますか？
最近は大体ライブラリやフレームワークによって対策されてるので見なくなってるらしいです。
しかし、まだスクラッチで書いていたら起きる可能性があるようだったので、どうやっておこるのか、どうやって対策するのかを理解するために調べて見ました。

また、簡単に確認できるように脆弱なサンプルアプリを作って MailCatcher という gem を利用してメールの受信を行います。

# そもそもメールヘッダ・インジェクションとは

メールヘッダーインジェクションとは、メールヘッダーが改行文字によって構成されることを利用した改行インジェクション攻撃の一種です。(HTTPのヘッダも同じ仕様で同じ形式なのでHTTPヘッダ・インジェクションってやつと似てます)

メールのソースは下記のようになっていてヘッダ部分に変数などで入力がある場合に改行が混在することで起こります。

![1.png](https://qiita-image-store.s3.amazonaws.com/0/82745/126b0dc7-c98a-661e-0b0f-6efe8ea33234.png)

# 確認するためにサンプルを作る

## 簡単に実装するためにPHPを利用する

http://php.net/manual/ja/function.mail.php

> additional_headers は、メールヘッダインジェクション対策を行っていません。 したがって、指定したヘッダが安全なものであり、ヘッダ以外のものを含まないようにするのはユーザー側の役目となります。 複数の改行文字を置くことでメール本文を始めたりしてはいけません。

php の mail 関数はデフォルトで対策してないって明記されているので、対策せずに利用して行きます。

## 必要なツール

- Docker
- Docker Compose

macの人は下記でインストールできます。

```sh
$ brew cask install docker
```

## サンプルアプリを作る

やったこと

- from, to, subject, 本文を送信する必要があるのでそのためのフォームを書きます。
- postで来た場合はメールを投げてリダイレクトするようにします。

```php:html/index.php
<?php
// ホントはバリデーションしたりエラー処理したりするべきだけどサンプルなので
if($_SERVER["REQUEST_METHOD"] === "POST"){
  $from    = empty($_POST['from']) ? 'example@example.com' : $_POST['from'];
  $to      = empty($_POST['to']) ? 'example@example.com' : $_POST['to'];
  $subject = empty($_POST['subject']) ? 'タイトル' : $_POST['subject'];
  $message = empty($_POST['message']) ? 'テスト本文' : $_POST['message'];
  $headers = 'From: ' . $from . "\r\n";
  mb_send_mail($to, $subject, $message, $headers);

  $url = "http://${_SERVER['HTTP_HOST']}";
  header('Location: ' . $url);
  exit;
}
<!DOCTYPE html>
<html lang = "ja">
<head>
  <meta charset = "UFT-8">
  <title>メールヘッダーインジェクションのサンプル</title>
</head>
<body>
  <h1>メールヘッダーインジェクションのサンプル</h1>
  <form method="post">
    <label for="from">From:</label>
    <input type = "text" id="from" name="from" value="example-from@example.com"><br/>
    <label for="to">To:</label>
    <input type = "text" id="to" name="to" value="example-to@example.com"><br/>
    <label for="subject">タイトル:</label>
    <input type = "text" id="subject" name="subject" value="タイトル"><br/>
    <label for="message">本文:</label>
    <input type = "text" id="message" name="message" value="テスト本文"><br/>
    <input type = "submit" value ="送信">
  </form>
</body>
</html>
```

## 簡単に動かすために docker 化する

やったこと

- mailcatcher で使うための sqlite3 と ruby を入れる
- mailcatcher を入れる
- php.ini をコピーする

```:Dockerfile
FROM php:7.2.9-apache

RUN apt-get update && apt-get install -y --no-install-recommends \
libsqlite3-dev \
ruby-dev

RUN gem install mailcatcher

COPY php.ini /usr/local/etc/php/
```

やったこと

- タイムゾーンの設定
- mailcatcher にメールを送るための設定
  - Rails, PHP, Django の設定が公式サイトに記載があるので参考にした

```php:php.ini
[Date]
date.timezone = "Asia/Tokyo"

[mail function]
sendmail_path = /usr/bin/env catchmail --smtp-ip smtp --smtp-port 1025
```

## 簡単に動かすために docker-compose の設定する

やったこと

- 先程作ったアプリの設定
- mailcatcher の設定
  - `sj26/mailcatcher` は作者が Docker hub にアップしてるのでそのまま利用
  - `restart: always` PCシャットダウンしたあとでも自動的に起動するので便利なので入れてる
  - `volumes:` の設定は 作ったアプリを書き換えながら試すときに便利なので入れている

```yaml:docker-compose.yml
version: '2'
services:
  smtp:
    image: sj26/mailcatcher
    ports:
      - "1080:1080"
    restart: always
  php:
    build: .
    ports:
      - '80:80'
    restart: always
    volumes:
      - ./html:/var/www/html
```

## 成果物

https://github.com/OshiroSeiya/mailheader-injection

```bash:起動コマンド
$ docker-compose up -d
```

下記にアクセスして起動してるか確認します。

お問い合わせフォーム
http://localhost

メールの受信確認
http://localhost:1080


# 実際にお問い合わせフォームに攻撃する

from パラメータに `%0d%0a` をいれて改行されるように書き換える。

操作前: example-from%40example.com
操作後: example-from%40example.com%0d%0aCc:%20example-cc%40example.com

ブラウザからリクエストを送るとURLエンコードをブラウザが自動でやってくれるので改行文字がエスケープされてしまいます。
まずはそちらを確認して、次に curl でリクエストを送って確認します。

## ブラウザから送信ボタンをおしてメール送信した場合

ブラウザから送信します。

```:リクエスト
POST / HTTP/1.1
Host: localhost
Content-Length: 206
Cache-Control: max-age=0
Origin: http://localhost
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://localhost/
Accept-Encoding: gzip, deflate
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Connection: close

from=example-from%2540example.com%250d%250aCc%3A%2520example-cc%2540example.com&to=example-to%40example.com&subject=%E3%82%BF%E3%82%A4%E3%83%88%E3%83%AB&message=%E3%83%86%E3%82%B9%E3%83%88%E6%9C%AC%E6%96%87
```


```:レスポンス
HTTP/1.1 302 Found
Date: Tue, 11 Sep 2018 12:41:14 GMT
Server: Apache/2.4.25 (Debian)
X-Powered-By: PHP/7.2.9
Location: http://localhost
Content-Length: 0
Connection: close
Content-Type: text/html; charset=UTF-8
```

```:メールソース
Date: Tue, 11 Sep 2018 12:41:15 +0000
From: example-from%40example.com%0d%0aCc:%20example-cc%40example.com
To: example-to@example.com
Message-ID: <5b97b7eb2ae66_352aeb772650d89959f@7daf46499537.mail>
Subject: =?UTF-8?Q?=E3=82=BF=E3=82=A4=E3=83=88=E3=83=AB?=
Mime-Version: 1.0
Content-Type: text/plain;
 charset=UTF-8
Content-Transfer-Encoding: base64

44OG44K544OI5pys5paH
```

<img width="573" alt="2.png" src="https://qiita-image-store.s3.amazonaws.com/0/82745/c4cea8de-3d74-65b9-35f6-bc82ab2448a5.png">


リクエストを見ると `%250d%250a` となって URL エンコードされていることがわかります。
そしてメールソースも分割されていないことがわかります。

## curlリクエストしてメール送信した場合

下記のコマンドでポストのリクエストを curl で送ります。

```bash
$ curl -i -s -k  -X $'POST' --data-binary $'from=example-from%40example.com%0d%0aCc:%20example-cc%40example.com&to=example-to%40example.com&subject=%E3%82%BF%E3%82%A4%E3%83%88%E3%83%AB&message=%E3%83%86%E3%82%B9%E3%83%88%E6%9C%AC%E6%96%87' $'http://localhost/'
```

```:リクエスト
POST / HTTP/1.1
Host: localhost
User-Agent: curl/7.54.0
Accept: */*
Content-Length: 194
Content-Type: application/x-www-form-urlencoded
Connection: close

from=example-from%40example.com%0d%0aCc:%20example-cc%40example.com&to=example-to%40example.com&subject=%E3%82%BF%E3%82%A4%E3%83%88%E3%83%AB&message=%E3%83%86%E3%82%B9%E3%83%88%E6%9C%AC%E6%96%87
```

```:レスポンス
HTTP/1.1 302 Found
Date: Tue, 11 Sep 2018 12:44:48 GMT
Server: Apache/2.4.25 (Debian)
X-Powered-By: PHP/7.2.9
Location: http://localhost
Content-Length: 0
Connection: close
Content-Type: text/html; charset=UTF-8
```

```:メールソース
Date: Tue, 11 Sep 2018 12:44:48 +0000
From: example-from@example.com
To: example-to@example.com
Cc: example-cc@example.com
Message-ID: <5b97b8c0d0c6a_3d2ac2b2d0f0d0433a9@7daf46499537.mail>
Subject: =?UTF-8?Q?=E3=82=BF=E3=82=A4=E3=83=88=E3=83=AB?=
Mime-Version: 1.0
Content-Type: text/plain;
 charset=UTF-8
Content-Transfer-Encoding: base64

44OG44K544OI5pys5paH
```

<img width="492" alt="3.png" src="https://qiita-image-store.s3.amazonaws.com/0/82745/3e839ca7-ec5f-387e-3930-822ab86b9e7a.png">

リクエストで URL エンコードされずに送れてることが確認できました。
そしてメールソースを見ると `Cc: example-cc@example.com` といった形でヘッダーが改行され `example-cc@example.com` にメールが送信されてしまったことがわかります。

# 対策するには

よくある対策は2つです。

- ヘッダに入力値を入れない
- 入力値の改行を削除する

今回はヘッダに値を入れて動作確認してる都合上入力値の改行を削除する方向で対象するのが良いでしょう。

```diff:index.php
 <?php
 if($_SERVER["REQUEST_METHOD"] === "POST"){
-  $from    = empty($_POST['from']) ? 'example@example.com' : $_POST['from'];
+  $from    = empty($_POST['from']) ? 'example@example.com' : str_replace(array("\r\n","\r","\n"), '', $_POST['from']);
   $to      = empty($_POST['to']) ? 'example@example.com' : $_POST['to'];
```

```:リクエスト
POST / HTTP/1.1
Host: localhost
User-Agent: curl/7.54.0
Accept: */*
Content-Length: 200
Content-Type: application/x-www-form-urlencoded
Connection: close

from=example-from%40example.com%0d%0a%0d%0aCc:%20example-cc%40example.com&to=example-to%40example.com&subject=%E3%82%BF%E3%82%A4%E3%83%88%E3%83%AB&message=%E3%83%86%E3%82%B9%E3%83%88%E6%9C%AC%E6%96%87
```

```:レスポンス
HTTP/1.1 302 Found
Date: Tue, 11 Sep 2018 13:07:36 GMT
Server: Apache/2.4.25 (Debian)
X-Powered-By: PHP/7.2.9
Location: http://localhost
Content-Length: 0
Connection: close
Content-Type: text/html; charset=UTF-8
```

```:メールソース
Date: Tue, 11 Sep 2018 13:07:36 +0000
From: example-from@example.comCc: example-cc@example.com
To: example-to@example.com
Message-ID: <5b97be18ae551_452afd992c30dc946e1@7daf46499537.mail>
Subject: =?UTF-8?Q?=E3=82=BF=E3=82=A4=E3=83=88=E3=83=AB?=
Mime-Version: 1.0
Content-Type: text/plain;
 charset=UTF-8
Content-Transfer-Encoding: base64

44OG44K544OI5pys5paH
```

<img width="503" alt="4.png" src="https://qiita-image-store.s3.amazonaws.com/0/82745/37547789-8f8b-d90e-9f90-861d3dac6099.png">

`From: example-from@example.comCc: example-cc@example.com` となりヘッダに改行を追加することができてないのがわかります。

# まとめ

メールヘッダ・インジェクションがどういうものか、実際に実装し確認したことでより理解が深まった気がします。
あと、PHP だと 7.2.9 (現時点の最新版)でもスクラッチで書いていたら起きる可能性があることがわかりました。

# おまけ

対策前に欲張って改行をもう一つつけてメールの body までいじれないか試したところ

```
Warning: mb_send_mail(): Multiple or malformed newlines found in additional_header
```

だめです！

# 参考リンク

https://mailcatcher.me/
https://www.docker.com/
http://php.net/manual/ja/function.mail.php