SMTP Smuggling
最近没怎么关注安全方面的进展,结果错过了去年年底披露的 SMTP Smuggling。 这是 Timo Longin 发现的一个全新的针对 SMTP 协议的攻击手法, 现在的年轻人真是蛮厉害的。
SMTP协议、流水线扩展和信体终结标志
SMTP 协议是 RFC 5321 定义的邮件传输协议,通常采用 TCP
作为传输协议,在同一连接中传输指令和数据。传统上,SMTP 协议采取「一问一答」的形式,但目前正常的 SMTP
服务器和客户端普遍实现了流水线作业(RFC 2920)
来减少客户端与服务器之间的交互次数,从而减少送信延迟。采用流水线作业时,客户端必须继续遵守协议的状态机约束:
举例来说,在发出 EHLO
/ HELO
之后,客户端必须等待服务器的回应,而不能直接开始进入下一状态;
而在发出 DATA
(信体开始)指令之后,在收到服务器的 354 回应之前,也不能开始传送信体。
更进一步,如果服务器没有回应自己支持 PIPELINING
,则客户端必须采取旧式的「一问一答」形式,
而不是新式的流水线作业。
SMTP 协议与 D. J. Bernstein (“djb”) 设计的 QMTP 不同,
在传输信体时不会事先声明信体尺寸。RFC 5321 Section 4.1.1.4 规定,信体以单行的 .
,即 <CRLF>.<CRLF>
作为终结标志。RFC 3030 新增的 BDAT
指令是对此的一项补救措施。
问题
在电传打字机时代,「回车」(<CR>
、\r
或 0x0d
) 表示将打字头挪到一行开始,而「换行」(<LF>
、\n
或 0x0a
)
则表示将打印纸向上推一行。对电传打字机来说,由于一行的长度不固定,「回车」操作所需的时间也不固定,
而「换行」操作则是电机推一个固定的长度,两部分的驱动设备是独立的,并且前一操作通常需要消耗更多时间。
从设计角度,先开始「回车」再开始「换行」意味着两个动作可以同时进行并提高速度。
在上世纪六十年代,ASA (ANSI前身) 和 ISO 分别设计了与 ASCII 类似的编码标准,
在 ASA 草案中,采用「回车+换行」(<CRLF>
) 作为新行的符号,而在 ISO 草案中,则同时接受「回车+换行」(<CRLF>
)
和 「换行」(<LF>
) 作为新行的符号。
这些差异影响了当时的操作系统设计者,他们采纳了不同的设计来表达文本中的新行。
DEC 采纳了「回车+换行」 (<CRLF>
) 来配合电传打字机,这影响了后来的 CP/M 和 MS-DOS 以及 Windows。
而 Multics 则选择了采用 「换行」<LF>
来表示新行,并在设备驱动中将其翻译成回车加换行来支持电传打字机,
这影响了其精神继承者 Unix 和各类类 Unix 系统。
在早期的 Sendmail 版本中,除了标准的 <CRLF>
行末符,
也支持只用 <LF>
作为行末符,尽管 RFC 5321 Section 2.3.8
禁止客户端这样做,但我们都知道「历史无可替代的力量」究竟有多大,
时至今日,仍然有相当多的 MTA 服务器软件选择继续支持它来确保兼容性。
如此一来,对于 <LF>.<LF>
或 <LF>.<CRLF>
而不是 <CRLF>.<CRLF>
便可以有不同的解释。
对于不接受 <LF>
作为行末符的邮件服务器来说,前两种都只是再正常不过的文本,会原样发给下一个邮件服务器;
而对于接受 <LF>
作为行末符的的邮件服务器来说,它会被解读为不同的语义,即信体结束。
而当前一种邮件服务器把邮件发给后一种邮件服务器时,
后者的不同解读可能会导致灾难性的后果:
在 <LF>.<LF>
或 <LF>.<CRLF>
后面可以「夹带」(smuggling)
一组 SMTP 指令,如果这一层邮件服务器是拥有签名权的外发邮件服务器,
并且信任上一层的服务器的话,则可以通过这种方式绕过正常的访问控制来实现不正常的外发操作。
postfix 的解决方法
现时,正常的邮件客户端是不应该发出不带「回车」的裸「换行」的。postfix 的作者 建议 彻底禁止这样做, 这符合 RFC 5321 的规定。 Postfix 3.8.4、 3.7.9、 3.6.13 和 3.5.23 新增的 smtpd_forbid_bare_newline 在遇到裸「换行」时会直接断开连接,从而避免这种攻击。
更早版本的 Postfix 可以通过禁止绕过状态机的流水线操作 (reject_unauth_pipelining
或更早版本的 smtpd_forbid_unauth_pipelining
,并禁止 CHUNKING 扩展) 来规避问题。