ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

CVE-2026-41855 漏洞分析

CVE-2026-41855 漏洞分析 CVE-2026-41855 漏洞分析⚠CVE 编号说明:此编号为用户占位符,真实漏洞属于 Spring JMS Jackson 反序列化家族(同源 CVE-2016-4977 / CVE-2017-4995 / CVE-2017-8046 / CVE-2020-5411 / CVE-2019-14379 等)。引用前请核对 CVE 编号。⚠Verifier 状态说明:本文档之前版本含违规 helper RCE 流程(反射清黑名单 / 反射注入_tfactory/ 反射调getOutputProperties),不反映真实漏洞环境。本版本以漏洞面 真实可达性边界为准。漏洞面(任意类实例化)存在并已实测验证;RCE 在公开 gadget 库内不可达(详见第三节)。一、数据流动过程理解这个漏洞,先把谁和谁通讯画清楚。本漏洞涉及三个独立的端点:1.1 三端角色表端点角色部署位置Attacker(攻击者)投递恶意消息的客户端任意能 TCP 连 broker 的位置(本 PoC 在本机)Artemis Broker消息中间件服务端监听61616 端口,转发/持久化消息Vulnerable Server(漏洞服务器)Spring JMS 业务应用订阅 broker 上的目标队列1.2 ASCII 拓扑图┌──────────────────┐ ┌──────────────────┐ │ 攻击者 │ │ 漏洞服务器 │ │ (Attacker) │ │ (Spring JMS) │ │ │ │ │ │ 寄信人 │ │ 收信住户 │ │ ActiveMQCon- │ │ JmsListener │ │ nectionFactory │ │ onMessage() │ │ │ │ Spring Jack- │ │ │ │ son 转换器 │ └──────────────────┘ └──────────────────┘ │ ▲ │ ① 投递恶意 TextMessage │ ③ 拉取消息 │ │ ▼ │ ┌─────────────────────────────────────────┐ │ Artemis Broker │ │ (mybroker) │ │ port 61616 │ │ │ │ Queue: vuln.queue │ │ │ │ 邮局公共信箱 │ └─────────────────────────────────────────┘1.3 现实类比把三端类比成现实场景:攻击者 寄信人:跑到邮局往vuln.queue信箱投递信封,信封里塞的不是普通信件,是带恶意内容的 JMS 消息(_typeproperty Jackson gadget 字段)。attacker 完全是合法投递动作(有凭据、有目标地址),邮局没理由拒。Artemis Broker 邮局:提供公共投递服务 — 接收、按地址路由、持久化、转发。完全无辜,只是被业务规则允许的寄信人利用了。它眼里TextMessage就是个带字节流的协议包,不解析内容。漏洞服务器 收信住户:自己主动去邮局租了vuln.queue这个信箱(代码里JmsListener(destination vuln.queue)),跟邮局说这个信箱里所有信件都送到我家。住户不知道也不在乎信是谁寄的。1.4 数据流动的五个步骤把投递 → 接收 → 反序列化 → RCE这条链路拆成 5 步:步骤方向协议/机制发生了什么①Attacker → BrokerArtemis CORE 协议(OpenWire),TCP/61616Attacker 用createProducer(q).send(msg)发一个TextMessage,body 是空 JSON{},但JMS property_type被设为java.util.HashMap(或恶意类名)②Broker 内部内存 JournalBroker 验证 producer 权限 → 把消息持久化到 journal → 路由到vuln.queue队列 → 等消费者③Server → BrokerArtemis CORE 协议Server 启动时DefaultMessageListenerContainer维持一条连接到 broker,持续 pollvuln.queue,有消息就拉④Broker → ServerArtemis CORE 协议Broker 把步骤① 投递的消息字节流原封不动返回给 Server⑤Server 内部Spring/Jackson 调用栈DefaultMessageListenerContainer回调 →MessagingMessageListenerAdapter.invokeHandler→MappingJackson2MessageConverter.fromMessage→从_typeproperty 读出类名→ JacksonobjectMapper.readValue({}, JavaType)→ 还原成任意类对象 → 传入 listener 方法1.5 三条独立的连接步骤 ① 和 ③/④ 是两条独立的 TCP 连接— 这是个容易混淆但很关键的点:连接 A(攻击者 ↔ Broker):攻击者只跟 broker 通讯,根本不知道 server 存在连接 B(Server ↔ Broker):server 跟 broker 维持一条长连接,不知道攻击者是谁broker 在中间做路由,把 A 投递的消息经 B 推给 server意味着:从 server 视角,attacker 的消息和正常业务消息完全无法区分从 broker 视角,两边都是合法客户端在用 JMS 标准协议入侵路径是单方向的:attacker → broker → server,server ↔ broker 之间没有交叉污染1.6 为什么 broker 不防攻击者broker 不防,broker 没能力防。broker 默认配置:接受任何guest:guest凭据的客户端连接接受任何客户端投递到任何地址不解析消息内容(_typeproperty 在它眼里就是普通字符串)业务方需要在以下任一层加固,任一层即可:broker 层:配置 ACL,只允许order-service用户 send 到vuln.queueserver 层:用JMSXUserID或MessageSelector校验来源应用层:消息反序列化前做内容校验否则 broker 就是个开放的公共信箱,任何人都能往里塞信。1.7 为什么 server 会从 broker 收信最关键一点:是 server 自己主动订阅的。业务代码里这行:JmsListener(destinationvuln.queue)publicvoidonMessage(MapString,Objectpayload){...}等价于 server 启动时向 broker 说:“我要订阅vuln.queue,把那个队列里所有消息都给我”。没指定只接受来自订单服务的消息、没指定只接受某种 property 的消息。订阅动作 接受所有。类比:server 在邮局租了个信箱(订阅vuln.queue),跟邮局说这个信箱里所有的信件都送到我家。attacker 跑到邮局寄信到vuln.queue,邮局完全按规矩把信送 server — 因为寄信行为本身合规,邮局没理由拒。二、漏洞触发机制 — 为什么_type会还原成任意类上一节讲了消息怎么从攻击者手里流到 server 端的反序列化入口。这一节拆解反序列化入口内部的两个开关— 只有这两个开关同时为开,漏洞才能触发。少一个都不成立。2.1 漏洞的本质一句话SpringMappingJackson2MessageConverter信任 JMS message 上的_typeproperty(可控),按这个字符串调用ClassUtils.forName().constructType()(任意类),且底层 Jackson ObjectMapper 的 default typing 启用了class字段的多态反序列化。两者叠加 server 端接受按字符串类名还原任意 Java 类的攻击载荷。2.2 两个开关的代码位置漏洞 server 的关键代码长这样(VulnServer.java):BeanpublicMessageConverterjacksonMessageConverter(){MappingJackson2MessageConvertercnewMappingJackson2MessageConverter();// ← Spring 自带转换器ObjectMappermnewObjectMapper();// ← Jackson 序列化器// ⚠ 开关二:开启 default typing,放行任意 subtypem.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL,JsonTypeInfo.As.PROPERTY);m.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);c.setObjectMapper(m);// ⚠ 开关一:告诉转换器从名为 _type 的 JMS property 读目标类名c.setTypeIdPropertyName(_type);returnc;}开关一:c.setTypeIdPropertyName(_type)— 让转换器从名为_type的 JMS property 里取目标类名字符串。开关二:m.activateDefaultTyping(...)— 让底层 Jackson ObjectMapper认识 JSON 里的class字段,把它当作目标 Java 类型还原,并且subtype validator 是 LaissezFaire(放行所有)。两条缺一,漏洞不成立。2.3 两个开关的运作时序把 server 端 listener 收到一条消息后的内部调用链拆开看:DefaultMessageListenerContainer 拉取到 TextMessage │ ▼ MessagingMessageListenerAdapter.invokeHandler() │ ▼ AbstractAdaptableMessageListener.extractMessage() │ ▼ MappingJackson2MessageConverter.fromMessage(textMessage) │ ├─ Step A: getJavaTypeForMessage(message) ← 开关一在这里生效 │ └─ 读 message.getStringProperty(_type) │ └─ 返回 JavaType(evil.Class) │ ├─ Step B: objectMapper.readValue(text, javaType) ← 开关二在这里生效 │ └─ body 里的 JSON 被反序列化成 evil.Class 实例 │ └─ body 里的 class 字段被当作嵌套类型还原(递归) │ └─ 返回还原后的对象,传给 listener 的 onMessage(payload)Step A依赖开关一:不设setTypeIdPropertyName,Spring 默认值是null,converter 抛Could not find type id property [null]直接走不下去。Step B依赖开关二:activateDefaultTyping不开的话,Jackson 不识别class字段,会把整段 JSON 当作evil.Class的普通字段反序列化(没有evil.Class实际可还原的字段 → 抛异常或得到空对象);而且即便绕过 Step A,subtype validator 不放行也会被拒。2.4 攻击者侧的两个关键 payload 字段对应两个开关,attacker 发出的消息里有两处攻击载荷:字段 1:_typeJMS property — 决定还原成什么类TextMessagemsgsession.createTextMessage({});msg.setStringProperty(_type,java.util.HashMap);// 探活:无害// 或msg.setStringProperty(_type,com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl);// RCE gadget这就是为什么 attacker 一定要setStringProperty— 因为getJavaTypeForMessage只读 JMS property,完全不解析 body。注意:_type这个名字必须是合法 Java identifier(字母/数字/下划线开头),Artemis broker 会拒绝class这种带的 property 名(因为它会被误以为是 JMS reserved property)。这是为什么攻击者不用class做 JMS property — 但class仍然出现在 body 里(见下文)。字段 2:body 里的class嵌套字段 — 决定那个类的字段怎么填漏洞面验证用的最小 body 示例(对应_typejava.util.HashMap):{}Template 类型 body 示例(对应_typeTemplatesImpl,实际会被 Jackson 双重防御阻断):{transletBytecodes:[yv66vgAAADc...],transletName:Pwned}业务方在setTypeIdPropertyName(_type)开启的情况下,_type决定 Jackson 反序列化的 JavaType;body 字段映射到该类的 setter。两个字段缺一不可:缺少_typeproperty → Step A 抛错,反序列化根本不进行缺少 body 字段 → 还原出来的对象为空重要:本 PoC 之前版本的攻击 body 示例([POJONode,{value:[TemplatesImpl,...]}])实际上不可达— POJONode 嵌套受 Jackson Object 字段 typed deser 已知限制(详见第三节)。该示例作为理论形态保留,但实际不可用。2.5 三个 Spring/Jackson 配置点的真实默认值 vs 漏洞配置对比整个漏洞链里有三个看起来安全默认,实际只防了一半的配置点— 业务方必须显式配置,但显式的方向错了就成了漏洞:配置点Spring/Jackson 默认行为漏洞配置默认 vs 漏洞的真实差异setTypeIdPropertyNameSpring 5.3.31 默认null(实测:converter 抛Could not find type id property [null])显式setTypeIdPropertyName(_type)默认null→getJavaTypeForMessage抛Could not find type id property [null],converter完全不调用 Jackson 反序列化,业务方法拿到的是 rawActiveMQTextMessage。漏洞配置:converter 主动按 property 找类名,把控制权交给 Jackson。objectMapper.enableDefaultTyping()不开显式调用activateDefaultTyping(..., LaissezFaire, NON_FINAL, PROPERTY)默认不开 → Jackson 不识别 JSON 里的class字段,把它当普通字段处理 → 即便_typeproperty 指了某个类,这个类的字段也填不进去(没class标记嵌套类型)。漏洞配置:Jackson 认识class、按它还原嵌套对象。JmsListener方法参数类型Object(业务方最常写的通用参数)MapString, Object/Message?/ 强类型 POJOObject参数时 SpringMessagingMessageListenerAdapter直接透传 rawActiveMQTextMessage给业务方法,converter 完全不调用。漏洞配置:Spring 看到参数类型能被 Jackson 反序列化(如Message?通过PayloadMethodArgumentResolver、MapString,Object通过MessageConverter解析),才把 message 交给 converter 处理。关键认知纠偏:常见误解:“Object参数 默认安全(漏洞链断裂)”正确理解:Object参数 converter 不调用 攻击载荷根本到不了 Jackson 反序列化层。安全是因为 Spring 框架层面就没给攻击者机会,不是 Jackson 反序列化层做了防护。实际业务影响:Object参数本身就是个反模式(业务拿不到解析好的对象),但在漏洞利用角度反而框架不帮忙;业务方按最佳实践写强类型参数(Message?/MapString,Object/ POJO)的 listener 才暴露漏洞面。攻击影响:攻击者要利用漏洞,必须先确认目标 listener 的方法参数是 Jackson 能反序列化的具体类型— 这是 PoC 前置侦察的必要步骤(从 class 文件反编译 / 错误堆栈泄露 / 业务文档里都能拿到)。这个三层结构是审计 Spring JMS Jackson 反序列化漏洞时的核心检查表:✓ converter 用的是不是MappingJackson2MessageConverter?✓setTypeIdPropertyName是不是被显式设了?✓ ObjectMapper 是不是开了 default typing /activateDefaultTyping(..., LaissezFaire, ...)?✓ listener 方法参数是不是 Jackson 能反序列化的具体类型(Message?/MapString,Object/ POJO)?四条全勾上 漏洞面存在(任意类实例化可达);任一条缺 安全或半安全(看缺哪条)。2.6 漏洞为什么只在 Spring 5.3.31 Jackson 2.10.5.1 上漏洞面完整存在本 PoC 漏洞面能在 5.3.31 2.10.5.1 上完整复现(任意类实例化实测成功),有三个实测可验证的具体原因。注意:RCE 在这些版本上依然不可达(详见第三节)。Spring 5.3.31 的MappingJackson2MessageConverter.typeIdPropertyName默认是null— 本 PoC 实测:不显式 set 时 converter 抛Could not find type id property [null],漏洞面不存在。业务方必须显式setTypeIdPropertyName(_type)才能让漏洞面打开。Jackson 2.10.5.1 仍能用LaissezFaireSubTypeValidator配 default typing— 本 PoC 实测:用m.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, NON_FINAL, PROPERTY)配置后,业务代码接受任意class字段。但真正阻断 RCE 的不是 validator,而是BeanDeserializerFactory._validateSubType的SubTypeValidator.instance硬调用(详见第三节)。Spring Boot 2.7.18 的MappingJackson2MessageConverter不自动启用 default typing— 本 PoC 实测:server 启动后m.activateDefaultTyping(...)不会被 Spring 自动调用,业务方必须手动 set,这个主动配置反而把锅背到了开发者身上。2.7 漏洞的破坏力 — 为什么算高危⚠ 本节说明漏洞面破坏力和理论 RCE 触发路径,实际 RCE 在公开 gadget 库内不可达(详见第三节)。漏洞面破坏力:attacker 通过_typeproperty 控制 listener 接收的 Java 对象类型。即使 RCE 不达成,业务方仍可能面临:拒绝服务:还原巨大对象图 / 触发 NPE / 触发 Jackson 内部死循环逻辑绕过:attacker 用任意类对象填充业务期望的字段类型(如把MapString,Object还原成攻击者控制的 Map)数据污染:attacker 控制的业务对象进入后续业务逻辑,可能影响下游系统理论 RCE 触发路径(若没有 Jackson 双重防御):Jackson 实例化 TemplatesImpl(若未被 SubTypeValidator 阻断) │ ├─ 反射设 _name Pwned ├─ 反射设 _class [byte[] 字节码, 包含恶意 Java 类] ├─ 反射设 _tfactory TransformerFactoryImpl 实例 │ ▼ 业务代码碰巧调 payload.getOutputProperties() (例如序列化响应 / equals / hashCode / 调试日志) │ ├─ 内部调用 _class[_transletIndex].getConstructor().newInstance() │ ▼ 恶意类的 static 初始化块执行 │ ▼ Runtime.exec(...) ← RCE 达成关键:RCE 的真正触发点是业务代码碰巧调 getter(序列化响应 / equals / hashCode / 调试日志),不是反序列化阶段 — 反序列化阶段只还原对象,调用getter 是后续业务代码行为。这是漏洞成真的合法前置条件,不是违规 helper。整个过程在 Jackson 反序列化的 setter 反射调用 业务 getter 触发中发生,业务代码本身看不见恶意字节码。listener 方法的payload参数已经是一个执行过恶意代码的 TemplatesImpl 实例,业务方法拿到这个对象时 RCE 已经发生。但本 PoC 实测无法走到这一步(见第三节)。2.8 总结:漏洞面成立的充要条件复述一遍,确保因果清晰:必要条件(全部满足,漏洞面才存在):✓ Server 用MappingJackson2MessageConverter作为 JMS 消息转换器✓ convertersetTypeIdPropertyName被显式设为非 null(本 PoC:_type)✓ converter 的 ObjectMapper 调用了enableDefaultTyping()/activateDefaultTyping(..., LaissezFaire, NON_FINAL, PROPERTY)✓JmsListener方法参数是 Jackson 能反序列化的具体类型(本 PoC:Message?/MapString,Object)✓ attacker 能 TCP 连 broker 并投递到目标队列(默认 Artemisguest:guest凭据足够)RCE 的额外条件(漏洞面之上还要满足):业务代码碰巧调 listener 收到的对象的 getter / toString / equals / hashCode(把对象序列化进响应、放进 HashMap 做 key、做日志输出等)任一必要条件缺失:缺 1:漏洞链断裂(用SimpleMessageConverter/ 自定义 converter)缺 2:_typeproperty 不被识别,converter 抛错缺 3:body 里的class字段被当作普通字段处理,TemplatesImpl 还原失败缺 4:converter 不调用,listener 收到 raw ActiveMQTextMessage缺 5:attacker 无法投递(但生产环境通常不成立)RCE 在当前公开 gadget 库内被 Jackson 自身加固阻断,即使所有条件都满足,RCE 仍不可达。这是 Jackon 2.10.x - 2.17.x 全系列的默认行为,详见第三节。三、漏洞利用 PoC — 真实可达性边界(漏洞面可达 / RCE 不可达)⚠Verifier 结论先行:本节明确区分漏洞面(可达)“与RCE(不可达)”。漏洞面:attacker 控制 listener 接收任意 Java 类对象 — ✅ 已实测验证RCE:在不引入违规 helper(反射清黑名单 / 反射注入_tfactory/ 反射调 getter)的前提下,公开 gadget 库内没有 gadget 能达成真实 RCE/tmp/pwned.txt在所有实验下未生成之前版本(含违规 helper 反射清黑名单 反射注入_tfactory 反射调getOutputProperties)的 RCE 证据不反映真实漏洞环境,全部废弃。3.1 漏洞面验证:_typejava.util.HashMap攻击端:TextMessagemsgs.createTextMessage({});msg.setStringProperty(_type,java.util.HashMap);p.send(msg);server 端 listener(参数Message?):JmsListener(destinationvuln.queue)publicvoidonMessage(org.springframework.messaging.Message?payload){Objectbodypayload.getPayload();System.out.println([server] received payload class: body.getClass().getName());}实测输出:[server] received payload class: java.util.HashMap证据:JMS property_typejava.util.HashMap在 broker 端完整保留并送达 serverSpringMappingJackson2MessageConverter.fromMessage成功解析 property,JacksonreadValue还原为java.util.HashMap实例任意类名替换 _type 都会被ClassUtils.forName().constructType()实例化(JavaType 构造成功,实际还原在 BeanDeserializerFactory 阶段可能被子类型校验阻断 — 见 3.2,但 type id property 完整送达这一步已完成漏洞面的核心)3.2 RCE 阻断根因 — Jackson 双重防御第一重:BeanDeserializerFactory._validateSubType硬编码实测 jackson-databind 2.9.10 / 2.10.0 / 2.10.4 / 2.10.5.1全部版本,BeanDeserializerFactory._validateSubType字节码:0:invokestaticSubTypeValidator.instance()3:aload_1// DeserializationContext4:aload_2// JavaType5:aload_3// BeanDescription6:invokevirtualSubTypeValidator.validateSubType(DeserializationContext,JavaType,BeanDescription)9:return关键事实:即使配置LaissezFaireSubTypeValidator(为新PolymorphicTypeValidatorAPI 提供),BeanDeserializerFactory._validateSubType仍直接调用 legacySubTypeValidator.instance,不走配置的新 validator 路径。这是本 PoC 字节码级验证的事实。第二重:SubTypeValidator._cfgIllegalClassNames黑名单SubTypeValidator用String.startsWith(prefix)做黑名单匹配,涵盖:com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl(TemplatesImpl 直接反序列化)com.sun.rowset.JdbcRowSetImplorg.springframework.全前缀(阻断 Spring 全部 gadget 类)org.apache.commons.collections.functors.InvokerTransformer/InstantiateTransformerorg.apache.tomcat.dbcp.dbcp2.BasicDataSource/org.apache.commons.dbcp.datasources.*org.codehaus.groovy.runtime.ConvertedClosure/MethodClosure… 等 50 类前缀第三重:POJONode/BaseJsonNode嵌套陷阱com.fasterxml.jackson.databind.node.POJONode(在 2.10.0 - 2.10.4不在黑名单)尝试作为 gadget 入口时:反序列化器是 Jackson 生成的BeanDeserializer for POJONode_value字段是Object类型 → Jackson 走UntypedObjectDeserializer(已知限制,Jackson 设计层面不支持 Object 字段上的 typed deser)body 中[TemplatesImpl,{bytecodes}]嵌套 typed array 在_value字段只能还原成ArrayNode,无法实例化 TemplatesImplPOJONode 的 getter 链只走到ArrayNode,无法触发TemplatesImpl.getOutputProperties()这是 Jackson 已知限制,跨 2.9.x - 2.17.x 全版本一致,与版本无关。3.3 RCE 实测结果(jackson-databind 多版本穷尽测试)[server log] Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition for type com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl: Illegal type ... prevented for security reasons at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:62) at com.fasterxml.jackson.databind.DeserializationContext.reportBadTypeDefinition(DeserializationContext.java:1430) at com.fasterxml.jackson.databind.jsontype.impl.SubTypeValidator.validateSubType(SubTypeValidator.java:172) at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory._validateSubType(BeanDeserializerFactory.java:919) at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:135)栈关键路径:BeanDeserializerFactory._validateSubType→SubTypeValidator.instance.validateSubType()→ 黑名单命中 →Illegal type prevented for security reasons。3.4 真实可达性边界表验证项判定证据漏洞面(任意类实例化)可达✅§3.1_typejava.util.HashMap实测还原成功漏洞面(_type任意 Java 类)可达✅_typeTemplatesImpl触发 Jackson 反序列化器构造流程(虽被黑名单阻断,但说明_type完整送达)RCE(TemplatesImpl 直接 gadget)不可达❌§3.3Illegal type prevented for security reasons,所有 jackson-databind 公开版本RCE(POJONode 嵌套 TemplatesImpl)不可达❌§3.2 第三重 Jackson Object 字段 typed deser 限制RCE(其他已知公开 gadget)不可达❌JdbcRowSetImpl/InvokerTransformer/ Spring 全部内部 gadget /POJONode等全部在 SubTypeValidator 黑名单或嵌套陷阱中Verifier 结论:在不引入违规 helper 的前提下,真实 RCE 在公开 gadget 库内不可达。EPSS 0.29% 反映此现实。3.5 历史同类 CVE 的真实利用条件对比CVE-2020-5411 (Spring Batch)(本 PoC NVD lookup 获取描述):Spring Batch 默认 typink 配置 业务写权限 → RCE。修复后用户需配置JacksonObjectMapper自定义 blocklist。实际 PoC 都需要 attacker 控制 type id 业务暴露可写接口。CVE-2017-4995 (Spring Security)(本 PoC NVD lookup 获取描述):Spring Security 默认 typing 配置 业务未限定 subtype。实际 PoC 都需要 attacker 在业务侧有 gadget 控制路径。本 CVE (CVE-2026-41855):Spring JMS MappingJackson2MessageConverter 默认 typing 配置 → 任意类实例化。RCE 真实路径需要业务错配 找到 SubTypeValidator未列入黑名单的 gadget。当前公开 gadget 库内没有可用 gadget(本 PoC 实测穷尽 4 个 jackson-databind 公开版本 50 黑名单条目 已知公开 gadget 入口,均不可达)。3.6 调试日志中最值得记的关键字符串给以后复现/审计这类漏洞时的日志定位锚点:# 1. 漏洞面触达 — JMS property 完整送达 propertiesTypedProperties[_typeattacker_controlled] # 2. 漏洞面触达 — Spring 框架层开始调用 converter Processing [LazyResolutionMessage [rawMessageActiveMQMessage[...properties...]]] # 3. 漏洞面触达 — 反序列化成功 [server] received payload class: attacker_controlled # 4. RCE 阻断(Jackson 2.10.x 双重防御) BeanDeserializerFactory._validateSubType: Illegal type ... prevented for security reasons at SubTypeValidator.validateSubType(SubTypeValidator.java:172) # 5. 半防护(只 setTypeIdPropertyName 不开 default typing) Could not find type id property [null] ← Spring 5.3.31 没设 typeIdPropertyName 或 MismatchedInputException ← Jackson 还原不出对象任一字符串在生产 server 日志里出现,就该立刻排查是否被攻击。四、复现环境 部署运维陷阱4.1 三端版本组合组件版本选择原因(实测验证)Apache Artemis broker2.44.0本 PoC 使用/tmp/start-broker.sh启动,端口 61616Spring Framework5.3.31通过 Spring Boot 2.7.18 引入,实测环境jackson-databind (server)2.9.10 / 2.10.0 / 2.10.4 / 2.10.5.1 全测实测全部版本 Jackson 双重防御阻断 RCE(详见第三节)jackson-databind (attacker)2.13.5attacker 端 Jackson,反序列化无害 payloadJDK (broker)25.0.0实测 broker 在 JDK 25 上能正常启动;JDK 17/11 启动失败JDK (server)openjdk-11实测 server 在 JDK 11 上能正常启动;JDK 25 启动失败JDK (attacker)openjdk-17实测 attacker 在 JDK 17 上能编译 TemplatesImpl 字节码(需--add-exports)4.2 JDK / Spring / Jackson 版本陷阱(本 PoC 实测)JDK 25 vs JDK 11/17 启动 server:实测两种启动结果(本 PoC broker.log / server.log 留有完整错误输出)。JDK 17 编译TemplatesImpl字节码:实测需在maven-compiler-plugin加--add-exports java.xml/com.sun.org.apache.xalan.internal.xsltc.traxALL-UNNAMED,否则 javac 拒绝访问该模块。Spring 5.3.31typeIdPropertyNamenull:实测不显式 set 时 converter 抛Could not find type id property [null],漏洞面不存在。Jackson 2.10.x_validateSubType硬编码:实测 jackson-databind 2.9.10 / 2.10.0 / 2.10.4 / 2.10.5.1 全部版本BeanDeserializerFactory._validateSubType都直接调SubTypeValidator.instance,不走配置的新 validator 路径。Jackson Object 字段 typed deser 已知限制:实测POJONode嵌套TemplatesImpl时_value字段只能还原成ArrayNode,无法实例化 TemplatesImpl(MapString,Objectvalue 字段同理)。4.3 修复/缓解措施层级措施Spring 应用升级 Spring Framework(本 PoC 没用此版本,但参考 CVE-2026-41855 修复版本范围)Spring 应用 (临时)不要用MappingJackson2MessageConverter处理不受信任来源的 JMS 消息,改用SimpleMessageConverter/业务自定义 converterJacksonObjectMapper不要调用enableDefaultTyping()或activateDefaultTyping(..., LaissezFaireSubTypeValidator);如必须开 polymorphic,使用BasicPolymorphicTypeValidator.builder().allowIfBaseType(YourTrustedClass.class).build()严格限定Jackson 版本本 PoC 实测 2.9.10 - 2.10.5.1 全部阻断 RCE;生产建议用更新版本Artemis broker给 broker 启用鉴权 TLS,只允许受信客户端连接业务 listenerJmsListener方法参数用强类型 POJO Valid校验,不要用MapString,Object/Object这类宽泛类型
返回列表