前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >log4j2 JNDI 注入漏洞分析

log4j2 JNDI 注入漏洞分析

作者头像
p4nda
发布2023-01-03 14:44:02
8500
发布2023-01-03 14:44:02
举报
文章被收录于专栏:技术猫屋技术猫屋

0x01 写在前面

2021 年 12 月 9 号注定是一个不眠之夜,著名的Apache Log4j 项目被爆存在远程代码执行漏洞,且利用简单,影响危害巨大,光是引入了 log4j2 依赖的组件都是数不清,更别提项目本身可能存在的风险了,如下图所示,mvnrepository搜索引用了 log4j-core version 2.14.1的项目就 十几页了:

1.png
1.png

本文就来简单分析一下该漏洞的原理。

0x02 影响范围

引用了版本处于2.x < 2.15.0-rc2的 Apache log4j-core的应用项目或组件

0x03 漏洞分析

根据官方的修订信息:https://issues.apache.org/jira/projects/LOG4J2/issues/LOG4J2-3201?filter=allissues

2.png
2.png

可以明确知道,是通过 jndi 中 LDAP 注入的方式实现了 RCE,然后查看其补丁的更改记录:

3.png
3.png

可以发现对lookup函数进行了修改判断

知道了漏洞类型,那么就好入手了,首先翻阅官方文档中关于lookup的说明:

4.png
4.png

lookup提供了一种在任意位置向 Log4j2 配置添加值的方法,是实现StrLookup接口的特殊类型的插件 ,查看官方文档发现log4j2 支持的方法有很多:

5.png
5.png

总计有:base64datactxmainenvsyssdjavamarkerjndijvmrunargsmapbundlelog4j

由于这里主要说明的是关于 JNDI lookup 的用法,其他的不再赘述。

关于 JNDI lookup官方文档有说明:

6.png
6.png

JndiLookup 允许通过 JNDI 检索变量,然后给了示例:

代码语言:javascript
复制
<File name="Application" fileName="application.log">
    <PatternLayout>
        <pattern>%d %p %c{1.} [%t] $${jndi:logging/context-name} %m%n</pattern>
    </PatternLayout>
</File>

实际上通过 log4j2 支持的方法那张图中就可以发现log4j 中 jdni 的用法格式如下:

代码语言:javascript
复制
${jndi:JNDIContent}

既然明确了lookup是触发漏洞的点,并且找到了可以触发 lookup的方法 ,那么就可以找入口点,只要找到入口点,然后传入 jndi 调用 ldap 的方式,就能够实现 RCE。

那么,哪一个入口点可以传入${jndi:JNDIContent}呢?

没错了,就是LogManager.getLogger().xxxx()方法

在log4j2中,共有8 个日志级别,可以通过LogManager.getLogger()调用记录日志的方法如下:

代码语言:javascript
复制
LogManager.getLogger().error()
LogManager.getLogger().fatal()

LogManager.getLogger().trace()
LogManager.getLogger().traceExit()
LogManager.getLogger().traceEntry()
LogManager.getLogger().info()
LogManager.getLogger().warn()
LogManager.getLogger().debug()
LogManager.getLogger().log()
LogManager.getLogger().printf()

上述列表中,error()fatal()方法可默认触发漏洞,其余的方法需要配置日志级别才可以触发漏洞。因为在logIfEnabled方法中,对当前日志等级进行了一次判断:

7.png
7.png

只有当当前事件的日志等级大于等于设置的日志等级时,才会符合条件,进入logMessage()方法

8.png
8.png

知道这些基本信息后,就可以进一步了解漏洞的触发原理了。

测试 case 如下:

代码语言:javascript
复制
public class log4j {  
  
    private static final Logger logger = LogManager.getLogger();  
  
    public static void main(String[] args) {  
        Collection<org.apache.logging.log4j.core.Logger> current = LoggerContext.getContext(false).getLoggers();  
        Collection<org.apache.logging.log4j.core.Logger> notcurrent = LoggerContext.getContext().getLoggers();  
        Collection<org.apache.logging.log4j.core.Logger> allConfig = current;  
        allConfig.addAll(notcurrent);  
        for (org.apache.logging.log4j.core.Logger log:allConfig){  
            log.setLevel(Level.ALL);  
        }  
        logger.error(Level.ALL,"payload");  
//        logger.warn("payload");  
//        logger.info("payload");  
//        logger.debug("payload");  
//        logger.traceExit("payload");  
//        logger.trace("payload");  
//        logger.fatal("payload");
//        logger.printf(Level.ALL,"payload");  
//        logger.traceEntry("payload");  
//        logger.log(Level.ALL,"payload");  
 }    
}

由于这些调用方法触发漏洞的原理都是一样的,所以本文就以 error 举例说明。

查看 error 的类继承关系可以发现,实际上会调用AbstractLogger.java中的public void error()方法:

9.png
9.png

在该方法中会调用logIfEnabled判断是否符合日志记录的等级要求,如果符合,那么会进行logMessage操作:

10.png
10.png

后续不关键调用路径如下:

logMessage ----> logMessageSafely ----> logMessageTrackRecursion ----> tryLogMessage ----> log

----> DefaultReliabilityStrategy.log ----> loggerConfig.log ----> processLogEvent ----> callAppenders ----> tryCallAppender ----> append ----> tryAppend ----> directEncodeEvent ----> encode ----> toText ----> toSerializable ---->format----> PatternFormatter.format

第一个关键点在PatternFormatter.java中的 format方法:

11.png
11.png

如果检测到$字符后跟了一个{字符,那么会对直到}中间的内容进行解析并replace

replace --> substitute --> StrSubstitutor.substitute --> resolveVariable --> Interpolator.lookup

Interpolator.lookup方法中,首先会获取字符串的前缀值:

12.png
12.png

如果匹配到内置方法,那么就进入对应的处理方法,这里是 JNDI 方法,那么就会由JndiLookup类进一步处理:

13.png
13.png

最终加载由攻击者传入的LDAP服务端地址,然后返回一个恶意的JNDI Reference对象,触发漏洞,实现 RCE。

0x04 漏洞复现

14.png
14.png

0x05 写在最后

log4j2涉及的组件之多、牵扯的范围之广,造成的结果,恐怕是漏洞发现者或者是某个公开 poc 的安全公众号都始料未及的。 其实这个漏洞带给我们的不仅仅是一个新的吃饭技能,更多的是一些思考:

第一,安全发展至今,为什么一个 java底层依赖出现漏洞,却导致国内所有大厂全军覆没?虽然个别厂商提前修复,但同样说明了一件事,供应链级别的0day攻击,依旧是无法第一时间防御的。是否存在一种新的机制或方案能够防御供应链级别的0day攻击?

第二,log4j2 项目引入JNDI lookup 已有 7 年之久,但是应用范围如此之广、利用复杂难度之低的漏洞,长达 7 年未被发现,实在有些惭愧(更惭愧的是官方文档还有 jndilookp 的使用说明)

第三,以后挖0day思路真如 skay 说的一样加一: find *.jar => add as library => shift+shift => find log4j => RCE 实际上不仅是 log4j,通常这也是一个挖掘组件依赖漏洞的一种思路

第四

15.png
15.png

参考

https://logging.apache.org/log4j/2.x/manual/lookups.html https://github.com/apache/logging-log4j2/pull/608/commits/755e2c9d57f0517a73d16bfcaed93cc91969bdee

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-12-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0x01 写在前面
  • 0x02 影响范围
  • 0x03 漏洞分析
  • 0x04 漏洞复现
  • 0x05 写在最后
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档