news 2026/9/10 21:18:50

Envoy ext_authz 过滤器的 UAF 修复详解:HTTP 鉴权拒绝请求路径上的内存安全修复

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Envoy ext_authz 过滤器的 UAF 修复详解:HTTP 鉴权拒绝请求路径上的内存安全修复

Envoy ext_authz 过滤器的 UAF 修复详解:HTTP 鉴权拒绝请求路径上的内存安全修复

【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy

导读

本文基于 Envoy 仓库中 changelogs/current/bug_fixes/ext_authz__fix_uaf_when_rejecting_request.rst 记录的漏洞修复(CVE-2026-50572)展开,深入剖析 ext_authz(External Authorization,外部鉴权)过滤器在通过 HTTP 协议调用外部鉴权服务并拒绝请求时存在的 use-after-free(UAF,释放后使用)内存安全问题及其修复。读完本文,你将掌握 ext_authz 过滤器的完整请求处理链路、UAF 漏洞的成因与触发场景,以及 Envoy 如何通过生命周期管理和引用计数机制从根本上消除此类风险。

一、安全公告内容与影响面

根据 changelogs/current/bug_fixes/ext_authz__fix_uaf_when_rejecting_request.rst 的官方记录,本次修复针对的是安全公告CVE-2026-50572(对应 GitHub Advisory GHSA-q8wp-gf7q-m8cv),问题定性为:

Fixed UAF when ext_authz over HTTP causes request to be rejected. (修复了 ext_authz 通过 HTTP 调用鉴权服务并拒绝请求时产生的释放后使用问题。)

这条变更记录言简意赅,却指向一个安全敏感的领域:HTTP 传输模式下的 ext_authz 过滤器。该过滤器本身承担着网关的访问控制职责,一旦在"拒绝(deny)"路径上出现 UAF,攻击者可能借机触发崩溃(拒绝服务)甚至进一步的内存破坏。

同目录下还有一条姊妹修复 changelogs/current/bug_fixes/ext_authz__fix-handling-of-pathless-requests.rst(CVE-2026-73547),修复的是无 URI 路径请求(如 CONNECT)导致进程异常终止的问题,两者共同构成近期 ext_authz 过滤器在 HTTP 模式下的安全性加固。

二、背景:ext_authz 过滤器在 HTTP 模式下的工作流

2.1 过滤器在 Envoy 中的定位

ext_authz(External Authorization)是 Envoy 内置的核心 HTTP 过滤器,允许 Envoy 在将请求转发到上游之前,先调用一个外部鉴权服务进行校验。它支持两种传输协议:

  • gRPC 模式:使用envoy.service.auth.v3.AuthorizationgRPC 服务,通过CheckRPC 完成鉴权;
  • HTTP 模式(本文主角):通过普通的 HTTPPOST请求将原始请求属性(方法、路径、头部等)发送给鉴权服务,依据 HTTP 响应状态码判断允许(200)或拒绝(其余状态码)。

过滤器的核心实现位于 source/extensions/filters/http/ext_authz/ext_authz.cc,对应的类声明在 source/extensions/filters/http/ext_authz/ext_authz.h。

2.2 请求处理主链路:decode → check → onComplete

从 ext_authz.cc 可以看到,过滤器在decodeHeaders()中发起对外部鉴权服务的调用:

Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) { ... // Initiate a call to the authorization server since we are not disabled. initiateCall(headers); return filter_return_ == FilterReturn::StopDecoding ? Http::FilterHeadersStatus::StopAllIterationAndWatermark : Http::FilterHeadersStatus::Continue; }

initiateCall()(ext_authz.cc#L321-L442)会完成 CheckRequest 的组装(头部、查询参数、元数据等),然后调用客户端的check()方法,并把过滤器自身作为RequestCallbacks传入:

state_ = State::Calling; filter_return_ = FilterReturn::StopDecoding; // 暂停过滤器链继续迭代 active_client_ = client_to_use; initiating_call_ = true; client_to_use->check(*this, check_request_, decoder_callbacks_->activeSpan(), decoder_callbacks_->streamInfo()); initiating_call_ = false;

这里的关键是active_client_成员(ext_authz.h#L566 处声明为Filters::Common::ExtAuthz::Client* active_client_{nullptr}),它持有的是当前正在服务这一次 in-flight 鉴权请求的客户端裸指针(raw pointer)。这个裸指针正是本次 UAF 风险的核心关注点。

当鉴权服务返回后,HTTP 客户端实现会回调过滤器的onComplete()(ext_authz.cc#L700-L1160)。以"拒绝"分支为例(ext_authz.cc#L1012-L1094):

case CheckStatus::Denied: { ... // setResponseFlag must be called before sendLocalReply decoder_callbacks_->streamInfo().setResponseFlag( StreamInfo::CoreResponseFlag::UnauthorizedExternalService); decoder_callbacks_->sendLocalReply( response->status_code, response->body, &headers = response->headers_to_set, &callbacks = *decoder_callbacks_, this -> void { ... }, std::nullopt, Filters::Common::ExtAuthz::ResponseCodeDetails::get().AuthzDenied); break; }

sendLocalReply()会直接向下游返回鉴权拒绝的响应(例如 403 Forbidden),随后过滤器链被终止、流被销毁。

三、UAF 漏洞成因剖析:HTTP 客户端对象生命周期与裸指针

3.1 引用:active_client_裸指针的保存与清空

initiateCall()中,active_client_ = client_to_use被赋值为当前调用使用的客户端对象指针;随后在onComplete()的开头(ext_authz.cc#L705-L706):

updateLoggingInfo(response->grpc_status); active_client_ = nullptr;

以及在onDestroy()(ext_authz.cc#L679-L687)中:

void Filter::onDestroy() { if (state_ == State::Calling) { state_ = State::Complete; if (active_client_ != nullptr) { active_client_->cancel(); active_client_ = nullptr; } } }

3.2 UAF 的触发场景(修复前)

从代码结构上可以推断,修复前的问题出在客户端对象(RawHttpClientImpl)的销毁时机上:

  1. HTTP 模式下的客户端是每流创建的。与 gRPC 模式下复用全局异步客户端不同,HTTP 模式的RawHttpClientImpl是伴随当前流创建、由流自身持有生命周期的;
  2. 当鉴权服务返回"拒绝",过滤器通过sendLocalReply()立即终止了流。此时流的销毁顺序可能导致:HTTP 客户端对象先于过滤器回调完成对响应数据的解析而被释放
  3. 一旦客户端对象被释放,而onComplete()回调路径中仍存在对该客户端对象(例如其内部的 stream info、上游字节计量器bytes_meter等)的访问,就会形成典型的UAF:悬空指针被解引用

特别地,updateLoggingInfo()(ext_authz.cc#L637-L673)中存在对该指针所指向对象的深入访问:

// Use the client that actually served this check request for stream info. auto const* stream_info = active_client_ ? active_client_->streamInfo() : client_->streamInfo(); if (stream_info == nullptr) { return; } const auto& bytes_meter = stream_info->getUpstreamBytesMeter(); ...

如果active_client_指向的对象已被释放,active_client_->streamInfo()就是对悬空指针的访问,属于典型的释放后使用。同理,在拒绝路径上,response->headers_to_set等响应对象如果与客户端内部缓冲的生命周期纠缠在一起,也可能在流销毁后仍被回调访问。

3.3 为什么 HTTP 模式特别容易触发

对比两条传输通道的实现:

  • gRPC 客户端由grpcAsyncClientManager统一管理,以 shared_ptr 形式引用,生命周期与集群强绑定,流销毁不会导致客户端消失;
  • HTTP 客户端(RawHttpClientImpl,见 source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc)是按流创建、随流销毁的临时对象。从 ext_authz.cc#L298-L319 的createPerRouteHttpClient()可以看到,每个流都会std::make_unique<RawHttpClientImpl>(...)

因此,HTTP 模式下的客户端生命周期与流的销毁时序耦合极深,一旦"拒绝 + 立即终止流"的路径执行顺序有偏差,客户端被提前释放的风险远高于 gRPC 模式。这也解释了为何该 CVE 明确限定于 "ext_authz over HTTP"。

四、修复方案的工程解读

从当前仓库代码可以观察到,围绕该 UAF 已经形成了一套完整的生命周期防护体系:

  1. 显式状态机管理:过滤器通过State { NotStarted, Calling, Complete }枚举(ext_authz.h#L553)跟踪与鉴权服务的通信阶段。onDestroy()中仅在State::Calling时执行取消逻辑,避免在回调已经完成(Complete)后再次触碰客户端;
  2. 回调入口处立即解绑onComplete()一进入就将active_client_置空,确保后续任何对客户端对象的访问都发生在解绑之前,杜绝"回调执行一半、客户端已释放"的窗口;
  3. 流销毁时的取消协议onDestroy()中调用active_client_->cancel()并置空指针,保证在流被终止(包括被拒绝后终止)时,挂起的异步回调不会再去触碰已销毁的客户端;
  4. 空指针防御updateLoggingInfo()中对active_client_stream_info都做了判空,即使客户端不可用,也不会因悬空指针访问而崩溃。

测试方面,test/extensions/filters/http/ext_authz/ext_authz_filter_test.cc 中大量用例覆盖了拒绝路径的sendLocalReply期望(例如 1754、1793、1831、3206、3252 等行的EXPECT_CALL(decoder_filter_callbacks_, sendLocalReply(...))),以及 3927-3929 行展示的"先 cancel 再 onDestroy"的销毁协议测试,从单元测试层面锁定了客户端生命周期的正确管理。这也印证了 test/extensions/filters/http/ext_authz/ext_authz_filter_test.cc#L3927-L3929 中取消与销毁的顺序要求。

五、拒绝路径的完整生命周期时序

结合源码,修复后"HTTP 鉴权拒绝请求"的完整时序可归纳为:

  1. decodeHeaders()收到客户端请求,过滤器组装CheckRequest并发起 HTTP 调用(initiateCall()),记录active_client_,将filter_return_置为StopDecoding
  2. HTTP 鉴权服务返回非 200 状态码,RawHttpClientImpl解析响应并回调onComplete()
  3. onComplete()立即将active_client_置空并更新日志信息(此刻客户端仍存活);
  4. 进入CheckStatus::Denied分支:校验响应头、按max_denied_response_body_bytes截断响应体、设置UnauthorizedExternalService响应标志位,最后通过sendLocalReply()向下游返回拒绝响应(AuthzDenied响应码详情);
  5. 流进入终止流程,过滤器链调用onDestroy():此时state_已是Complete,不再执行取消操作,避免二次触碰已回收的客户端对象。

上述第 3 步与第 5 步的配合,正是本次修复消除 UAF 的关键:回调完成即解绑,流销毁不再干预已完成的状态

六、运维与升级建议

  • 升级路径:修复包含在 CVE-2026-50572 对应的安全版本中,所有在生产环境使用HTTP 模式 ext_authz的部署都应在回归测试后尽快升级;
  • 风险评估:本次漏洞仅影响ext_authz配置为http_service的场景;gRPC 模式客户端由共享引用管理,不受此 CVE 影响;
  • 验证手段:可通过单元测试 test/extensions/filters/http/ext_authz/ext_authz_filter_test.cc 与集成测试 test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc 验证拒绝路径与流销毁的稳定性;
  • 配置参考:HTTP 模式的配置示例可参考 test/extensions/filters/http/ext_authz/ext_authz.yaml,过滤器完整配置项定义位于 api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto。

结语

CVE-2026-50572 的修复看似只是一行 changelog,背后却是 Envoy 对"HTTP 模式下按流创建客户端 + 拒绝路径立即终止流"这一组合场景中对象生命周期严谨性的重新审视。从active_client_的解绑时机、状态机管理到流销毁取消协议,source/extensions/filters/http/ext_authz/ext_authz.cc 中的每一处细节都体现了 C++ 网络代理在异步生命周期管理上的典型工程实践。对于运行 HTTP 模式 ext_authz 的服务网格与 API 网关,理解这一修复不仅是安全合规的要求,更能帮助你在遇到类似"流终止与异步回调竞态"问题时快速定位根因。

【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 21:16:27

三星联手Mistral AI,本地大模型进入芯片制造

近日&#xff0c;三星电子与法国AI新贵Mistral AI达成重要合作&#xff1a;后者将向三星提供可本地化部署的AI大模型套件&#xff0c;用于半导体制造与工程业务&#xff0c;帮助三星构建定制化AI能力。协议在韩国与法国于巴黎举行的双边峰会期间宣布&#xff0c;被视为两国在高…

作者头像 李华
网站建设 2026/9/10 21:15:03

Keploy 几分钟装好:面向新手的完整安装指南

Keploy 几分钟装好&#xff1a;面向新手的完整安装指南 【免费下载链接】keploy Open-source platform for creating safe, isolated production sandboxes for API, integration, and E2E testing. 项目地址: https://gitcode.com/GitHub_Trending/ke/keploy 测试全靠手…

作者头像 李华