PHPへのメールヘッダーインジェクション

(Last Updated On: 2018年8月13日)

メールヘッダーインジェクションによる攻撃は一昔前に流行った攻撃です。最近ではあまり聞きませんが、PHPのmail/mb_send_mail関数はメールヘッダーインジェクションに対して十分に安全でしょうか?

実は十分に安全と言える対策は最近になって追加しました。1つは随分前に、もう1つは最近修正しています。それらの修正を紹介します。

メールヘッダーインジェクションのおさらい

メールヘッダーインジェクションとはメールヘッダーが改行文字(\r\n)によって構成されることを利用した改行インジェクション攻撃の一種です。例えば、メールヘッダーインジェクション対策がないメール送信APIの宛先(To)ヘッダーに

yohgaki@ohgaki.net\r\nBcc: user@example.com

という文字列を設定するとメールヘッダーが

To: yohgaki@ohgaki.net
Bcc: user@example.com

となりuser@example.comにもメールが送信されてしまいます。

これがメールヘッダーインジェクションです。改行文字に意味があるプロトコルではインジェクションが可能です。他の改行インジェクションが可能なプロトコルには

  • HTTPプロトコル (HTTPヘッダーの仕様はメールヘッダーの仕様と同じ形式)
  • Memcachedのテキストプロトコル(テキストプロトコルの場合、改行でコマンドを分割)

などがあります。

mail/mb_send_mail関数の仕様

mailとmb_send_mailは同じシグニチャを持っています。

bool mail ( string $to , string $subject , string $message [, string $additional_headers [,string $additional_parameters ]] )

$toと$subjectには古くからメールヘッダーインジェクション対策が行われていました。$toと$subjectによるメールヘッダーインジェクションは防止されていましたが、文字列型の$additional_headersにはメールヘッダーインジェクション対策がありませんでした。

文字列型の$additional_headerに対するインジェクション対策

$additional_headersにはNULL文字インジェクション対策は行われていましたが、改行インジェクション(メールヘッダーインジェクション対策)がありませんでした。

2015年の6月の定期リリースで修正されました。(私が直しました)

  • Fixed bug #68776 (mail() does not have mail header injection prevention for additional headers).

http://php.net/ChangeLog-5.php#5.6.10

この修正は$additional_headerに不正な改行文字列が含まれていないか、チェックし不正な場合は送信しないようにします。

具体的にはメールヘッダーのボディ部分に許可される”\r\n”、”\r\n “または”\r\n\t”以外の改行文字列があった場合にエラーとなるようになりました。例えば、RFC違反の”\r”のみ”\n”のみ、”\n\r”など、MTAによっては改行として処理してしまう文字列がエラーになります。

修正前のmail関数は”\r\n\r\n”というメールヘッダーとメールボディを分割する文字列までも許可していました。これにより$additional_headersに外部入力が含まれる場合、MIMEメールを送ってメール本文を不正に書き換えたメールを送る、という攻撃も可能でした。メール送信ライブラリの中にはこのバグを利用してMIMEメールを送っている物まであり「MIMEメールが送れなくなる」というバグレポートまで受け取りました。

配列型の$additional_headerインジェクション対策

文字列型の$additional_headerを使っている限り「メールヘッダーインジェクションのおさらい」で紹介した形式のインジェクションが可能です。対策済みのmail関数でもBccヘッダーを設定して不正な送信先にメールを送信するのは容易です。

「文字列型の$additional_headerに対するインジェクション対策」だけでは不十分であることは明らかなので、$additional_headerを配列型のデータで設定する仕様をPHP 7.2用に追加しました。

. Implemented bug #69791 (Disallow mail header injections by extra headers)
(Yasuo)

この対策ではメールヘッダーのヘッダー名を配列のキー、ヘッダーボディを配列の値として保存した配列型の$additional_headerを利用します。

ヘッダー名はRFCで許可された英数数字と記号のみ、ヘッダーボディはRFCで許可されている”\r\n “または”\r\n\t”のみ許可します。配列型の$additional_headerも最終的には文字列に変換されますが、複数行メールヘッダーは許可された形式のみになります。

不正な”\r”のみ”\n”のみなどのおかしな改行文字は「文字列型の$additional_headerに対するインジェクション対策」のコードで検出されます。

まとめ

mail関数の$additional_headerパラメーターを利用すればメールヘッダーインジェクション攻撃が可能であることはPHP開発者の間ではよく知られた”仕様”でした。PHP 7.2からは配列型の$additional_headerを利用することによりほぼ完全に意図しないメールヘッダーインジェクションを防止できるようになります。1

メールヘッダーインジェクションは過去のもの、と思っていた方も居るかも知れません。しかし、攻撃に対する対策が実装されても完全に対策されてるとは限りません。こういう細かい仕様を知らずに、思わぬ脆弱性を作らないようにするには「データを検証」することが有効です。そもそもメールヘッダーに使用する文字列には制御文字などは含まれていてはなりません。

PHP 7.2でも文字列型の$additional_headerは利用できます。PHPのmail/mb_send_mail関数でメールヘッダーインジェクションが完全に過去のものになるのは、少なくとも、近い将来はありません。

2行にまとめると

  • そもそも外部データはバリデーションしておくべき
  • PHP 7.2からは$additional_headerは配列で指定する

  1. $additional_header配列を生成するコードによっては意図しない要素が追加され、ヘダーインジェクションされる、というリスクは残ります。 

投稿者: yohgaki