delphij's Chaos

选择chaos这个词是因为~~实在很难找到一个更合适的词来形容这儿了……

02 Jan 2024

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>\r0x0d) 表示将打字头挪到一行开始,而「换行」(<LF>\n0x0a) 则表示将打印纸向上推一行。对电传打字机来说,由于一行的长度不固定,「回车」操作所需的时间也不固定, 而「换行」操作则是电机推一个固定的长度,两部分的驱动设备是独立的,并且前一操作通常需要消耗更多时间。 从设计角度,先开始「回车」再开始「换行」意味着两个动作可以同时进行并提高速度。

在上世纪六十年代,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 扩展) 来规避问题。