邮件头注入是攻击者通过在用户输入中插入\r\n伪造邮件头的漏洞;ThinkPHP的think\facade\Mail因默认使用不安全的mail()函数且未自动过滤自定义头中的换行符而受影响。

邮件头注入是什么,为什么ThinkPHP的think\facade\Mail会受影响
邮件头注入不是ThinkPHP特有的漏洞,而是所有基于PHP mail() 函数或封装其逻辑的邮件类共有的风险:当用户可控的输入(比如收件人邮箱、主题、发件人昵称)未经校验直接拼入邮件头,攻击者可在其中插入换行符(\r\n),从而添加伪造的 CC、BCC、Subject 甚至执行任意命令(取决于MTA配置)。ThinkPHP 6.x 的 think\facade\Mail 默认使用 mail() 驱动,若你手动构造 To 或 Subject 字段并拼接用户输入,就可能触发该问题。
收件人参数必须走to()方法,禁止字符串拼接
常见错误是把用户提交的邮箱直接拼进 mail() 第二个参数,例如:mail("admin@example.com", $subject, $body, "To: $user_email\r\n...")。ThinkPHP 正确做法是交由框架处理收件人字段:
-
to()方法内部会对邮箱格式做基础校验(是否含@、是否含控制字符),并自动转义换行符 - 传入多个邮箱时,用数组而非逗号拼接字符串:
Mail::to(['a@x.com', 'b@y.com']),避免解析歧义 - 绝对不要把用户输入的邮箱放进
subject()或from()的第二个参数(昵称位),那里不校验换行符
自定义邮件头必须过滤\r和\n
如果你用 Mail::withHeaders() 或底层 Swift_Mailer 手动加头(比如设置 X-User-ID),而值来自用户输入,就必须主动清理:
- 用
str_replace(["\r", "\n"], '', $input)剥离所有回车换行 - 不要只用
trim()—— 它只去首尾空白,中间的\r\n仍可被解析为新头 - ThinkPHP 自身不自动过滤自定义头内容,这是开发者责任
推荐改用SMTP驱动并关闭mail()函数
根本规避方式是绕过 mail() 这个易出错的底层接口:
立即学习“PHP免费学习笔记(深入)”;
- 在
config/mail.php中将default改为'smtp',并配置host、username等 - 在
php.ini中设disable_functions = mail(需服务器权限),从根源禁用不安全调用 - SMTP 驱动(如 SwiftMailer)本身对头字段有更严格的编码和分隔逻辑,比裸调
mail()抗注入能力强得多
最危险的环节永远是“以为框架替你挡了,结果只是没报错”——收件人邮箱看似正常,但混入 %0d%0a URL 编码后照样能绕过前端校验,最终在服务端解码触发注入。务必在服务端入口就做白名单式清洗,而不是依赖框架某一层的“默认处理”。



















