📘 第十一章 Web的攻击技术
📄 一句话概括

邮件首部也是靠换行分隔的,所以「收件人」里塞一个换行,就能多出一行 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 秒实验

  1. 把 Bcc: 换成 Cc:,看输出有什么不同(提示:Cc 会显示给收件人)。
  2. 试试在主题里塞一个超长的一行,看会不会被邮件系统截断。
  3. 想一想:为什么发验证码的接口特别怕这个漏洞?

常见误区

  1. 「邮件头里的东西用户看不到,改不了」——表单提交的就是邮件头。
  2. 「删掉换行就安全了」——会把地址拼坏,应当校验并拒绝。
  3. 「这是邮件系统的问题」——是拼邮件头那段代码的问题。
自测三问(点开对答案) 1. 邮件首部注入的成因是什么? —— 用户输入里的换行被拼进邮件头,解析器把它当成新的一行首部。 2. 最典型的后果是什么? —— 多出一行 `Bcc:`,每封信都悄悄抄送给攻击者。 3. 为什么这里不能只删换行? —— 删掉会把两个地址拼成一个畸形地址,语义被静默改变。

注入这一类到这儿差不多了。换一类:让路径往上跑。

相关笔记