なぜ今さら「メールヘッダーインジェクション」を語るのか?
「お問い合わせフォーム? ああ、PHPのmail()関数でサクッと書いたよ」。もし君がそう答えるなら、残念ながら君のシステムは既に攻撃者の踏み台リストに載っているかもしれない。
メールヘッダーインジェクションは、Web黎明期からの古典的な脆弱性だ。しかし、現代の複雑なクラウドアーキテクチャにおいても、この「一見単純な穴」が原因で、企業のメールサーバーがブラックリスト入りし、最悪の場合はランサムウェアの標的となるケースを私は現場で何度も見てきた。
今日は、なぜこの脆弱性が未だに消えないのか、そしてどうすれば「絶対に抜かれない」実装ができるのか。教科書には載っていない「現場の泥臭い実戦論」を叩き込む。
—
攻撃者が狙う「改行」という名のスキマ
メールヘッダーインジェクションの本質は、「SMTPプロトコルの仕様」と「アプリケーションの入力処理」のミスマッチにある。
メールのヘッダー(To:, Subject:, From:など)は、CRLF(\r\n)という特殊文字で区切られている。攻撃者は、ユーザー入力欄にこの\r\nを注入することで、ヘッダーを強制的に終了させ、その後に自分の好きなヘッダー(Bcc:, Cc:)や本文を追記する。
攻撃のPoC(概念実証)
例えば、お問い合わせフォームの「メールアドレス入力欄」に、悪意あるユーザーが以下のように入力したとする。
victim@example.com\r\nBcc: spam-target1@example.com, spam-target2@example.com
バックエンドで以下のような素朴な実装をしているとどうなるか。
// 危険なコード例:絶対に真似してはいけない
$to = “admin@company.com”;
$subject = “お問い合わせ”;
$headers = “From: ” . $_POST[‘email’]; // ここに改行コードが注入される!
mail($to, $subject, $message, $headers);
結果として、サーバーは「admin@company.com」だけでなく、攻撃者が指定したスパム先へもメールを送信してしまう。これが「メールサーバーの踏み台化」だ。これを利用して数百万通のスパムを撒かれたら、君の会社のドメインは一生メールが届かない「呪い」をかけられることになる。
—
防御の鉄則:ブラックリストではなく「ホワイトリスト」
現場で最もやってはいけないのが、「\rや\nを削除する」といったブラックリスト方式のフィルタリングだ。攻撃者はエンコード手法を次々と変えてくる。
正解は、外部からの入力をヘッダーに直接入れないこと。 どうしても動的に生成する必要があるなら、以下の鉄則を厳守してくれ。
実装サンプル:PHP(モダンなアプローチ)
PHPMailerやSymfony Mailerを使うのが今の業界標準だ。これらは内部でヘッダー注入を防止するエスケープ処理が組み込まれている。
use PHPMailer\PHPMailer\PHPMailer;
// 1. ライブラリを活用する(独自実装はバグの温床)
$mail = new PHPMailer(true);
try {
// 2. 入力値は信頼しない
$userEmail = filter_var($_POST[‘email’], FILTER_SANITIZE_EMAIL);
// 3. 検証:許可された形式かチェック
if (!filter_var($userEmail, FILTER_VALIDATE_EMAIL)) {
throw new Exception(“無効なメールアドレスです”);
}
$mail->setFrom($userEmail, ‘お問い合わせユーザー’);
$mail->addAddress(‘admin@company.com’);
$mail->Subject = ‘お問い合わせ’;
$mail->Body = $_POST[‘message’];
$mail->send();
} catch (Exception $e) {
// エラーはログに隠蔽し、ユーザーには簡潔なメッセージを
error_log($e->getMessage());
}
実装サンプル:Python (Django/FastAPI)
Pythonでメールを送る場合も、標準ライブラリのemail.headerモジュールを使ってヘッダーを適切にエンコードするのがセオリーだ。
from email.message import EmailMessage
def send_secure_email(user_input_email, message_body):
msg = EmailMessage()
msg[‘Subject’] = ‘お問い合わせ’
msg[‘From’] = ‘noreply@company.com’ # ユーザーの入力をFromに直接入れない
msg[‘To’] = ‘admin@company.com’
# ユーザーのメールはReply-Toヘッダーに留めるのが安全な設計
msg.add_header(‘Reply-To’, user_input_email)
msg.set_content(message_body)
# これで標準ライブラリがヘッダーの構造を壊さないように制御してくれる
—
現場のシニアが教える「運用のTips」
最後に、コード以外の防御レイヤーについての助言だ。
1. メールを自前で送信しない:
可能なら SendGrid や Amazon SES などのメール配信APIを使ってくれ。これらはAPI経由で送信するため、ヘッダーインジェクションの余地が物理的に存在しない。
2. MTA(Postfix等)の制限:
Postfixの設定ファイル(main.cf)で、smtpd_recipient_restrictionsを適切に設定し、外部からの不正な中継(リレー)を遮断せよ。
3. 監視の徹底:
Webサーバーのアクセスログだけでなく、メールサーバーのログ(送信キューの急増)を監視するアラートを立てろ。異常な送信数が発生した瞬間に管理者に通知が飛ぶ仕組みこそが、インシデントハンドリングの生命線だ。
最後に
セキュリティは「ツール」ではなく「規律」だ。
「動けばいい」という考えは、攻撃者にとっては「穴があってラッキー」という招待状に過ぎない。今回紹介したライブラリの使用と、ユーザー入力を直接ヘッダーに触れさせない設計を、今日のプルリクエストから徹底してほしい。
君の書くコードが、会社の、そしてユーザーの守り神になることを期待している。
コメント