PRIME CALC
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.