首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >我拆了 Telerik 这个 8.6 分漏洞,问题出在过滤器没兜住异常

我拆了 Telerik 这个 8.6 分漏洞,问题出在过滤器没兜住异常

原创
作者头像
Lethehong
发布于 2026-11-13 12:59:32
发布于 2026-11-13 12:59:32
60
举报
文章被收录于专栏:网络安全网络安全

一个不需要登录的请求,带上构造过的 filter 参数,就能让 Telerik 的过滤器抛异常。这篇我不复述公告,我把解析器到访问器的调用顺序拆开,再说清今天该不该动手、先动哪一步,以及我自己能验到哪一步、哪一步够不到。

8.6 分不假,但先看清它打的是哪

CVE-2026-106146,CVSS 3.1 给了 8.6,HIGH。我先看的是向量,AV:N、AC:L、PR:N、UI:N,这四个分量连起来的意思是,一个不需要任何身份的人,从网络上发一个可稳定复现的请求就能碰到这个缺陷。

但后面还有三个分量,S:C、C:N、I:N、A:H。机密性和完整性都是 N,只有可用性是 H。说白了,这个漏洞不偷数据、不改数据,它只负责让服务端返回 500。

所以 8.6 分里最扎眼的是 S:C 这一项。CVSS 认为它能突破组件边界,影响宿主系统。但官方描述里写得很克制,原话说的是报告没有证明工作进程被终止,也没有证明无关请求被干扰。这两句话有张力,我后面会单独说。

受影响版本从 2012.2.710 一直到 2026.3.1015,修复版本是 2026.4.1111。这个区间横跨了十几年,说明这个过滤器解析逻辑活了很久,久到没人觉得它需要被重新审一遍。

我先把结论放前面,这个漏洞对大多数项目的真实杀伤力,取决于一个很土的问题,你有没有一个不登录就能打的 Grid 端点。有,今天就动。没有,这周内动。

问题不在解析,在解析之后没人接住

官方描述点了两个函数,FilterNodeVisitor 和 FilterParserException。我按字面拆,这块的调用顺序大概是,请求进来,过滤器参数被解析成表达式树,然后 FilterNodeVisitor 遍历这棵树,逐节点做类型转换和取值。

出问题的地方在遍历阶段。描述里说它没有正确校验 descriptor types、null values 和 stack state。翻译成人话,访问器在往下走的时候,默认了每个节点都有类型、都不为空、栈里始终有东西。

一旦这三个默认假设被打破,它不会返回一个参数不合法的 400,而是直接抛一个未捕获的异常。cast 失败抛 InvalidCastException,取空引用抛 NullReferenceException,栈空了再取就抛 InvalidOperationException。

推演异常触发路径(示意代码,用伪代码还原访问器在三个假设被打破时的分支)

代码语言:txt
复制
// 伪代码,不是可直接编译的利用代码
public object Visit(FilterNode node)
{
    // 假设一,节点一定有类型
    var typed = (TypedNode)node;          // 类型不对就抛 InvalidCastException

    // 假设二,值一定不为空
    var value = typed.Value.ToString();    // 空引用就抛 NullReferenceException

    // 假设三,栈里一定有东西
    var parent = _stack.Peek();            // 栈空就抛 InvalidOperationException

    return Evaluate(parent, value);
}

// 调用方没有 try-catch,FilterParserException 一路冒到框架层
// 最终返回 HTTP 500,而不是 400

而 FilterParserException 这个异常,描述里说在受影响的程序集内没有被捕获。也就是说,解析器自己知道出了错,也抛了专门的异常类型,但调用链上没有人 catch 它。异常一路冒到框架层,最后变成一个 HTTP 500。

图 1 本文示意图:一个未认证请求带着畸形 filter 参数进来,走到访问器遍历时异常没人接,最后变成 500。

我一开始以为这是解析器写得太松,后来看描述,解析器其实做了它该做的事,抛了该抛的异常。真正漏掉的是访问器这一层,它假设上游永远给的是合法树,所以自己没有兜底。

这里有个细节我想单独说。描述里把 descriptor types、null values、stack state 三样并列,说明校验不是漏了一处,是这一层的设计前提就不包含防御。它不是某个分支写错了,是整层没有把输入当敌意看。

图 2 本文示意图:解析层做了该做的事,访问器层假设上游永远合法,异常处理层是空的。

一个 500 能捅多大,取决于谁在接

单次 500 不致命,致命的是这个端点不需要认证。描述里写得很清楚,前提是应用暴露了一个无需认证就能到达、并且接受 filter 参数的端点。

Telerik UI for ASP.NET MVC 的 Grid 组件,默认就带服务端过滤。很多项目直接把 Grid 的 Read 动作挂在匿名可访问的控制器上,因为反正是只读列表。这个习惯,正好把触发条件凑齐了。

所以影响面不取决于漏洞本身,取决于部署姿势。公网可达的 Grid、内网但横向移动能碰到的管理后台、以及任何把过滤器参数透传给 Telerik 的 API,这三种暴露面的处置优先级完全不同。

官方描述特意写了没有证明工作进程被终止。这句话我理解为,单次请求大概率只影响它自己那个请求上下文,不至于把整个 w3wp 打挂。但 8.6 分里的 S:C 又暗示了边界突破的可能,这两者之间的空白,公开信息没有填上。

我的判断是,别把 8.6 当成服务器要挂了,但也别因为只是个 500 就放到下周。未认证加可稳定复现,这两个条件凑在一起,足够让任何一个暴露在公网的 Grid 端点值得今天就看一眼。

图 3 本文示意图:同样是 8.6 分,公网匿名 Grid 和内网鉴权端点,今天要做的事完全不同。

还有一种情况容易被忽略,就是那些挂在客户门户、合作伙伴接口、甚至公开报表页下面的 Grid。它们平时没人管授权,因为业务上就是给外部看的,这类端点最符合描述里的前提。

先查版本,再查谁把这个端点露在外面

动作分两步,第一步是确认资产在不在区间里。Telerik 的版本号在 NuGet 包和 bin 目录的 dll 上都能看到,我习惯直接查项目文件里的引用版本。

查项目里的 Telerik 版本(示意代码,一条命令筛出落在受影响区间的 Telerik 引用)

代码语言:txt
复制
#!/usr/bin/env bash
# 在解决方案根目录执行,扫所有 csproj 和 packages.config
set -euo pipefail

LOW="2012.2.710"
HIGH="2026.3.1015"
FIXED="2026.4.1111"

find . -name '*.csproj' -o -name 'packages.config' | while read -r f; do
  grep -hoE 'Telerik[^"<>]*Version="[0-9.]+"' "$f" 2>/dev/null | \
  sed -E 's/.*Version="([0-9.]+)"/\1/' | while read -r v; do
    if [[ "$v" > "$LOW" || "$v" == "$LOW" ]] && [[ "$v" < "$FIXED" ]]; then
      echo "$f -> $v 落在受影响区间"
    fi
  done
done

第二步比第一步重要,查清楚哪些控制器动作是匿名可访问的,并且接收了 filter 参数。这一步没法靠扫版本号解决,得看路由和授权配置。

如果版本在区间内,但所有 Grid 端点都挂了 Authorize,那这个漏洞对你的实际风险要降一档,因为 PR:N 这个前提不成立了。反过来,版本在区间内、又有一个匿名 Grid,那就别等排期了。

过渡办法有两个。一是在网关层对 filter 参数做长度和字符集限制,把明显畸形的请求挡在外面。二是给匿名端点加一层轻量鉴权,哪怕只是一个静态 token,也能把 PR:N 变成 PR:L。这两个都不是根治,但能把窗口期缩短。

我得说清楚,网关层的字符集限制只能挡掉一部分畸形输入,它挡不住所有能触发异常的组合,因为触发条件里包含类型和栈状态,这两个不是靠正则能判干净的。所以它是拖延,不是修复。

修复版本跳得有点大,升级前先看 breaking change

修复版本是 2026.4.1111,从 2026.3.1015 跳过去,跨了一个 minor。Telerik 的发布节奏里,这种跨度通常带一些 API 调整,尤其是过滤器相关的扩展点。

如果项目里自定义过 FilterNodeVisitor 的子类,或者重写过过滤表达式的绑定逻辑,升级后要重点回归。因为修复大概率就改在这个类的校验分支上,自定义实现可能对不上新的假设。

我没法在这里给出具体的 breaking change 清单,公开信息只给了修复版本号,没有给变更说明。这一块要等 Telerik 的 release note,或者自己拉两个版本的 dll 做 diff。

所以升级顺序我建议是,先在预发环境升一个实例,把带过滤器的 Grid 页面全部点一遍,重点测空值、非法类型、嵌套条件这三种入参。没问题再推生产。

如果预发环境没有匿名 Grid,那就临时开一个测试控制器,挂一个最小 Grid,用匿名身份打一轮,把异常路径先走通。这一步做完,你才知道升级到底修没修掉你那条路径。

我怎么验的,验到哪一步

这一节我把验证过程单独摊开,因为我清楚读者里有人会问,你凭什么这么说。

我做的第一件事是版本比对,拿项目里引用的 Telerik 版本号,逐条对照 2012.2.710 到 2026.3.1015 这个区间,确认哪些资产落在里面。这一步是纯静态的,不碰运行环境。

第二件事是读官方描述里的调用链描述,把 FilterNodeVisitor、FilterParserException、descriptor types、null values、stack state 这几个词抽出来,按 ASP.NET MVC 过滤器的通用结构还原出解析到遍历再到异常冒泡的路径。这一步是读文档加推理,不是实测。

第三件事是把自己项目里所有接收 filter 参数的控制器动作列出来,逐个看授权特性。这一步能给出真实的风险排序,也是我建议所有人先做的一步。

我没做的是,没有搭出一个完整的 ASP.NET MVC 环境去实际发一个畸形 filter 请求,也没有拿到 Telerik 的商业包。所以构造什么样的参数能稳定触发,这个层面我只能按描述推,没有实测输出。

我能确认的是异常路径,cast 失败、空引用、空栈取值,这三种都会走到未捕获的分支。但具体哪一种能被未认证请求稳定命中,取决于应用怎么把 query string 映射到 filter 参数,这一层公开信息没有展开。

这次没查到什么,别把我的推断当结论

公开信息未披露的部分,我列清楚,免得有人拿这篇当定论。

第一,触发所需的参数形态没有披露。官方只说了接受构造过的 filter 参数,没有给样例,也没有说哪一类节点最容易打穿。

第二,S:C 这个分量的依据没有披露。按 CVSS 3.1 的规范,S:C 意味着受影响组件和宿主不在同一个安全域,但描述里又明确说没有证明进程终止。我倾向于认为评分偏保守,但没有证据推翻它,这条我不敢下结论。

第三,worker 进程在持续打的情况下会不会被拖垮,没有数据。单次 500 和持续 500 是两件事,后者会不会引发线程池耗尽、请求队列堆积,公开信息没写,我也不打算凭想象补。

按同类组件的通用规律推断,过滤器解析器往往被当成纯函数来写,假设输入永远来自可信的 UI 层。一旦这个假设被网络请求打破,异常处理就成了最后一道防线,而这道防线经常是空的。这是推断,不是这次公告的结论。

所以这篇里凡是带「大概率」「我倾向于」的句子,你都当成我的判断看,不要当成官方事实转发。

今天该做的,是把匿名过滤器端点找出来

升级是终点,不是起点。起点是搞清楚你有多少个无需认证就能到达、并且接受 filter 参数的端点。这个数字如果大于零,今天就得处理。

处理方式不一定是立刻升级,但至少要把这些端点的暴露面收窄。能放内网的放内网,能加鉴权的加鉴权,两个都做不到的,在网关层加一条针对 filter 参数的规则。

我把动作拆成今天、本周、排期三档,你对号入座。今天要做的,是把公网可达且匿名的 Grid 端点列出来,能关的关,能加鉴权的加鉴权,关不掉也加不了的,先在网关层挡一道。

本周要做的,是在预发环境升到 2026.4.1111,把带过滤器的页面按空值、非法类型、嵌套条件三种入参各测一轮,同时回归自定义过 FilterNodeVisitor 的代码。

排期要做的,是等 Telerik 的 release note 出来,补上 breaking change 清单,再把生产环境分批推上去。这三档之间不要跳步,跳步的代价往往比漏洞本身大。

8.6 分不低,但它的杀伤力被只影响可用性这个事实限制住了。真正让这件事值得写的,不是分数,是那个横跨十四年的版本区间,和它背后那个解析器抛了异常但没人接的结构性疏忽。

这种疏忽不会只出现在一个组件里。你手上的其他过滤器、排序器、查询构建器,大概率也有同样的假设。今天修完 Telerik,顺手把自己项目里的同类代码翻一遍,比等下一个 CVE 更划算。

写在最后

一个横跨十四年没被重审的过滤器,提醒我的不是又出一个洞,而是那些被当成纯函数写的解析逻辑,一旦被网络请求碰到,异常处理就是最后一道防线。这道防线空着,分数再高也只是 500,但空着这件事本身,比 500 更值得翻一遍自己的代码。今天先做一件事,把匿名可达的过滤器端点数出来,这个数字比任何评分都更能告诉你该不该慌。

以上,既然看到这里了,如果觉得不错,随手点个赞、在看、转发三连吧,如果想第一时间收到推送,也可以给我个星标⭐~

谢谢你看我的文章,我们,下次再见。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 8.6 分不假,但先看清它打的是哪
  • 问题不在解析,在解析之后没人接住
  • 一个 500 能捅多大,取决于谁在接
  • 先查版本,再查谁把这个端点露在外面
  • 修复版本跳得有点大,升级前先看 breaking change
  • 我怎么验的,验到哪一步
  • 这次没查到什么,别把我的推断当结论
  • 今天该做的,是把匿名过滤器端点找出来
  • 写在最后
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档