Log4j 2 提供了属性替换特性,在配置文件中可以使用 ${name}
这样的语法,从文件任意
位置引用属性值。而且为了从不同上下文环境中引用,还支持 ${prefix:name}
, 用 prefix
来指定上下文,然后通过 lookups 特性从远程获取需要的值进行属性替换
官方文档:
- https://logging.apache.org/log4j/2.x/manual/configuration.html#PropertySubstitution
- https://logging.apache.org/log4j/2.x/manual/lookups.html
由于 prefix 支持 JNDI, 而且地址(name)是完全可控的,而且 log4j 2 在记录日志时对
${} 语法进行了解析,综上原因导致了极其容易利用的 JNDI 注入
poc:
相关执行流程
关键流程的调用栈:
MessagePatternConverter#format 关键代码:
判断条件中的 this.noLookups 由后面提到的 formatMsgNoLookups 属性来设置
遇到 ${
开头的字符串会进入 StrSubstitutor 对模板语法进行解析,解析逻辑和 waf
bypass 的构造有关联,然后在 Interpolator#lookup 处理并发送 JNDI 请求
修复
Log4j 提供了 formatMsgNoLookups 属性来禁用 lookups 特性,可以通过下述方式设置:
- jvm 参数 -Dlog4j2.formatMsgNoLookups=true
- 配置文件设置 log4j2.formatMsgNoLookups=True
- 环境变量 FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS 设置为 true
高版本 JDK 提供了 com.sun.jndi.ldap.object.trustURLCodebase 属性且默认为 false,
JVM 不会信任 LDAP 对象反序列化过程中从远程 codebase 加载的类。但是依然可以利用本
地类的反序列化 gadget 进行利用
CVE-2021-45046
Log4j 2 有一个概念是线程上下文(Thread Context), 主要是用来区分和追踪不同客户端的
相关日志,并通过 Thread Context Map 和 Thread Context Stack 两个机制来存储相关标
记,而 PatternLayout 提供了相关的机制来获取线程上下文中存放的键值
官方文档:https://logging.apache.org/log4j/2.x/manual/thread-context.html
在 PatternLayout 获取 ThreadContext 的值时,如果包含 ${}
表示的变量就会进行解析,
而且解析过程不受 formatMsgNoLookups 属性的限制,造成 JNDI 注入。但前提是攻击者有
办法控制 ThreadContext 中的数据
关键流程:
LiteralPatternConverter 的关键代码如下:
如果 literal 属性包含 ${
, 就进入 StrSubstitutor#replace 方法导致注入
相关项目:https://github.com/Cybereason/Logout4Shell