开发者生态
morning
Dead.letter (CVE-2026-45185) 人类与 LLM 在 Exim 上进行未经身份验证的 RCE 竞赛
2026-05-12
1 阅读
fedek_
2026 年 5 月 12 日安全研究 Federico Kirschbaum Andres Luksenberg 返回博客 Dead.Letter (CVE-2026-45185) XBOW 如何在 Exim 上发现未经身份验证的 RCE XBOW 发现了 CVE-2026-45185,这是 Exim 中一个关键的未经身份验证的 RCE,并使用披露窗口来测试人类和自主漏洞利用开发可以走多远。 CVE-2026-45185 .简介 亲爱的读者, 首先是一个故事。那些旧的、破旧的之一。这是一个关于邂逅和误会、破碎的心和无声的背叛、曾经被认为永远的爱情却完全不同的故事。这一次,是在通常不会讲述这种形式的故事的环境中讲述的。这些页面是我们正在构建的产品早期测试的副产品。专注于查找和检测本机代码中的漏洞的产品。所以你将要同时读到两件事。这是我们发现并报告的对全球范围内的漏洞的技术描述,更安静的是,它也是我如何试图与我们现在生活的世界的新形态和平相处的描述。我花了近十年的时间专业地编写漏洞利用程序,总共花了二十年的时间在安全领域。近年来,大型语言模型已经改变了范式,直到现在,每当涉及使用它们来编写漏洞时,我都保持观望态度。这是我第一次放下手表,让其中一个模型进入迄今为止只有我自己的双手去过的地方。此外,在我的整个职业生涯中,我从未读过 Exim 的一行文字。我依稀记得几年前的一篇 Qualys 文章 (https://www.qualys.com/2021/05/04/21nails/21nails.txt) 当时让我大吃一惊,但我从未坐下来研究代码本身。我希望接下来的内容能找到两类读者:一类是为了技术深度而来,另一类是为了故事而来。如果它同时找到两者,我会很高兴。该漏洞是 GnuTLS(许多基于 Debian 的发行版上的默认 TLS 库,包括 Ubuntu)处理 TLS 连接时触发的释放后使用漏洞。在 TLS 关闭期间,Exim 释放其 TLS 传输缓冲区 - 但嵌套的 BDAT 接收包装器仍然可以处理传入字节并最终调用 ungetc(),它将单个字符 (\n) 写入释放的区域。该一字节写入落在 Exim 的分配器元数据上,破坏了分配器的内部形状;然后,该漏洞利用该损坏来获取更多原语。这里重要的是触发这个错误几乎不需要在服务器上进行特殊配置。这不仅仅是腐败本身的技术形式,也使它成为 Exim 迄今为止发现的最高级别的错误之一。乍一看,写入原语可能看起来很弱:它将单个换行符放入已释放的内存区域中。但正如本文的其余部分将表明的那样,一个字节足以一路升级到远程代码执行。接下来将详细介绍 Exim 的工作原理以及该漏洞在机器内部的位置。如果您只是为了故事而来,请随意跳至[设置挑战]。注意:本文中的所有代码均来自 Exim 4.97,因为它是基于 Debian(包括 Ubuntu 24.04 LTS)的默认安装。 Exim 基础知识 当客户端通过纯文本 SMTP 会话发出 STARTTLS 时,Exim 的命令调度程序将运行以下处理程序: #File: src/smtp_in.c int smtp_setup_msg ( void ) { // ... switch ( smtp_read_command (...)) { // ... case STARTTLS_CMD: HAD (SCH_STARTTLS); if (!fl.tls_advertished) { did = synprot_error (L_smtp_protocol_error, 503 , NULL , US "未通告时使用的 STARTTLS 命令" );休息 ; } /* 如果定义了 ACL 检查,则应用 ACL 检查 */ if ( acl_smtp_starttls && (rc = acl_check (ACL_WHERE_STARTTLS, NULL , acl_smtp_starttls, &user_msg, &log_msg)) != OK) { done = smtp_handle_acl_fail (ACL_WHERE_STARTTLS, rc, user_msg,日志消息);休息 ; } // ... s = NULL ; if ((rc = tls_server_start (&s)) == OK) //[1] { // ... DEBUG (D_tls) debug_printf ( "TLS active\n" );休息 ; /* 成功 STARTTLS */ } // ... } // ... } [1] 调用 tls_server_start(),最终调用 tls_init(),然后分配一个新的 GnuTLS 服务器会话 #File: src/tls-gnu.c static int tls_init ( const host_item *host, smtp_transport_options_block * ob, const uschar * require_ciphers, exim_gnutls_state_st **caller_state, tls_support * tlsp, uschar ** errstr) { //... state = &state_server;状态->tlsp = tlsp; DEBUG (D_tls) debug_printf ( "初始化 GnuTLS 服务器会话\n" ); rc = gnutls_init(&state->session, GNUTLS_SERVER); //... } state->session 是一个 gnutls_session_t ,它是 GnuTLS 句柄,拥有此 TLS 连接的每一个加密状态:协商的密码套件、记录层密钥、读写序列号、ALPN 选择等,但重要的是它还处理传输调用