C:\CTF\K17\PRIME_CALC.EXE _□X

PRIME CALC

web

Challenge

A Flask app searches for primes and Mersenne primes in a background worker, checkpointing that worker's entire process state to disk via DMTCP so it can be restarted later. Users can upload a config.json plus a timestamp that controls where the config gets written and where the search resumes from. Despite the math theming, the real bug has nothing to do with primes: it's a path traversal combined with a validate-after-save flaw that lets you overwrite the live DMTCP checkpoint file with your own.

The bugs

Path traversal: the upload endpoint resolves the destination path and only checks that it still lands somewhere under the app's base data directory:

name = timestamp if Path(timestamp).suffix else timestamp + ".json"
destination = (CONFIG_DIR / name).resolve()
if destination != BASE_DIR.resolve() and BASE_DIR.resolve() not in destination.parents:
    return jsonify(error="invalid config path"), 400

That check allows escaping the intended configs/ subdirectory as long as the result is still under the base dir — a timestamp of ../checkpoint.dmtcp resolves straight to the live checkpoint file and sails through.

Validate-after-save: the uploaded bytes are written to disk before being parsed as JSON:

uploaded.save(destination)
try:
    values = json.loads(destination.read_text())
except (OSError, ValueError, TypeError, KeyError):
    return jsonify(error="invalid config contents"), 400

Uploading a binary DMTCP snapshot instead of JSON makes the parse fail and the endpoint answer with a 400 — but the file is already saved by that point, silently overwriting the real checkpoint underneath the error response.

Exploitation

Rebuild the same container locally (matching Python and DMTCP versions), write a tiny script that dumps the flag to a world-readable output path, run it under dmtcp_launch, and trigger a checkpoint to produce a malicious .dmtcp snapshot. Upload that snapshot with timestamp=../checkpoint.dmtcp to overwrite the live checkpoint via the path-traversal bug, then hit the app's own /api/run endpoint — which blindly runs dmtcp_restart on that file. Restoring a DMTCP checkpoint restores the entire captured process state, registers included, so restarting an attacker-supplied checkpoint is equivalent to arbitrary code execution as the worker process. The flag script's output shows up on the app's own output page afterward.