邮件首部也是靠换行分隔的,所以「收件人」里塞一个换行,就能多出一行 Bcc:。
Demo 里本该只发给一个人的信,多出两行首部:一行抄送给了攻击者,一行把优先级提到最高。
上一节是 HTTP 报文,这一节是邮件报文——同一个病,换了个协议。
一、邮件也是「首部 + 空行 + 正文」
一封邮件的样子和 HTTP 报文几乎一样:From:、To:、Subject: 若干行,一个空行,然后是正文。
所以「换行是结构」这条规律在这里同样成立。
表单里的收件人、主题、发件人,只要被直接拼进邮件头,就能注入。
最容易想到的用法是加 Bcc:——每一封本该只发给某个人的信,都悄悄抄送一份给攻击者。
这一招在找回密码、发送验证码的场景里特别危险。
二、实测:7 行变 5 行
Demo 里收件人填 [email protected] 加一个换行加 Bcc: [email protected],
主题里也塞了一行 X-Priority: 1。
不过滤时邮件头是 7 行,多出来的两行分别是 Bcc: 和 X-Priority:,两个都判定为「有」。
删掉换行后回到 5 行,两行都没了。
代价是收件人变成了 [email protected]: [email protected]——一个畸形地址。
这说明「删除」这个修法在邮件这边不够好:它会把地址拼坏,导致邮件发不出去或者发错。
正确做法是校验并拒绝,或者按规范解析地址列表之后再重新拼接。
三、还有哪些坑
To 里的逗号:一封邮件可以发给多个地址,用逗号分隔。
所以不能简单按逗号切就当成「一个地址」——得按 RFC 5322 的地址语法解析。
主题编码:中文主题要按 MIME 编码,编码之后再拼接,如果编码前就注入进去了,
编码反而会把恶意内容「洗白」成合法字符。
正文里的 SMTP 命令:更老的漏洞是在正文里塞 `
.
`,提前结束正文,后面接上 SMTP 命令。 现代库基本都处理了,但自己拼的地方还有。 结论和上一节一样:协议结构字符不能从用户输入直接进来。 这条规律在 HTTP、SMTP、Redis、任何文本协议里都成立。
四、动手 Demo:多出来的 Bcc 和 X-Priority
用一段拼邮件头的代码,把带换行的收件人和主题拼进去,数首部行数; 再把换行删掉重跑一次,对比两次的首部。
TO_EVIL = u'[email protected]\r\nBcc: [email protected]'
SUBJ_EVIL = u'报告\r\nX-Priority: 1 (Highest)'
if clean:
to = re.sub(u'[\r\n]', u'', to)
subject = re.sub(u'[\r\n]', u'', subject)
实跑输出:
--------------------------------------------------------------------
邮件首部注入:多出来的 Bcc
--------------------------------------------------------------------
【不过滤】
From: [email protected]
To: [email protected]
Bcc: [email protected]
Subject: 报告
X-Priority: 1 (Highest)
Content-Type: text/plain; charset=utf-8
Content-Length: 12
首部行数 7 行
有没有 Bcc 有
有没有 X-Priority 有
【过滤换行】
From: [email protected]
To: [email protected]: [email protected]
Subject: 报告X-Priority: 1 (Highest)
Content-Type: text/plain; charset=utf-8
Content-Length: 12
首部行数 5 行
有没有 Bcc 没有
有没有 X-Priority 没有
第二段里,收件人后面多出一行 `Bcc: [email protected]`——
也就是每一封本该只发给波仔的信,都悄悄抄送了一份给攻击者。
这在邮件系统里是个经典问题,因为它和 HTTP 首部注入是同一个病:
**用户输入里的换行被当成了结构**。
修法也一样:拼接进协议结构的东西,必须先把换行去掉或者拒收。邮件这边还要注意 `To` 里本来允许逗号分隔多个地址,所以不能简单按逗号切——得按规范解析地址列表。
不过滤时 7 行:From、To、Bcc、Subject、X-Priority、Content-Type、Content-Length。
多出来的两行都在预期位置上。
过滤后 5 行,Bcc 和 X-Priority 都没了。
但 To 那一行被拼成了 [email protected]: ...,是畸形地址。
所以邮件这边的修法比 HTTP 那边更讲究:不能只删,得校验并拒绝,
因为删除会静默改变语义(把两个地址拼成一个)。
这一节和上一节放一起看最有价值:同一个病,在 HTTP 里删换行就够了,在 SMTP 里删换行会造出新问题。
修法要跟着协议的语义走。
30 秒实验
- 把
Bcc:换成Cc:,看输出有什么不同(提示:Cc 会显示给收件人)。 - 试试在主题里塞一个超长的一行,看会不会被邮件系统截断。
- 想一想:为什么发验证码的接口特别怕这个漏洞?
常见误区
- 「邮件头里的东西用户看不到,改不了」——表单提交的就是邮件头。
- 「删掉换行就安全了」——会把地址拼坏,应当校验并拒绝。
- 「这是邮件系统的问题」——是拼邮件头那段代码的问题。
自测三问(点开对答案)
1. 邮件首部注入的成因是什么? —— 用户输入里的换行被拼进邮件头,解析器把它当成新的一行首部。 2. 最典型的后果是什么? —— 多出一行 `Bcc:`,每封信都悄悄抄送给攻击者。 3. 为什么这里不能只删换行? —— 删掉会把两个地址拼成一个畸形地址,语义被静默改变。注入这一类到这儿差不多了。换一类:让路径往上跑。
- ☐ (本节完,下一节 11.2.6 目录遍历攻击)