
Recovery notes for proxmox advisory ID: PSA-2026-00043-1 (CVE-2023-54391)
Recovering from a wiper that presented as ransomware, after an attack against internet-exposed Proxmox infrastructure (PSA-2026-00043-1 / CVE-2023-54391).
The attacker wiped guests and left the filesystems damaged. Two recovery tracks depending on what the guest was:
These are the tools and the methodology, generalized so they're useful to anyone in the same spot. Read-only against the source, CC0. No real recovered data is included; example output uses placeholders.
The damaged disk is now your only copy of what's left. Do not fix it. Image it.
blockdev --setro /dev/<source> # 1. read-only, immediately
dd if=/dev/<source> of=/safe/disk.img bs=4M conv=noerror,sync # 2. image to safe storage
# 3. do NOT fsck, mount rw, or "repair"; that overwrites blocks a carver needs
# 4. wrap every source pass in scripts/source_guard.sh
# 5. recover from the image only, never the original
Shared discipline, split carvers. Run any with -h. Python 3.8+ / bash, no
third-party packages. needs Linux .
source_guard.sh/proc/diskstats| script | track | what it does |
|---|---|---|
scripts/source_guard.sh | shared | Wraps a recovery command; proves the source stayed read-only via a write-counter diff. Fails on any source write. |
scripts/wt_leaf_carve.py | database | Carves BSON out of raw WiredTiger leaf pages. Page size by decode, not fit; mandatory positive control. |
scripts/bson_frame.py | database | Counts/validates BSON documents by on-disk framing. Whole-file by default; a sample is flagged a FLOOR. |
scripts/fs_carve_classify.py | filesystem | Classifies carved file fragments by content, not filename; writes a per-file manifest; exhaustive secret scan. |
scripts/sensitivity_scan.py | shared | Tiers recovered data RED/AMBER/GREEN for credential/PII exposure. Prints counts, never values. |
scripts/integrity_verify.py | shared | Proves a reorganized copy is faithful to the source. Content-keyed (survives renames); reads OK/FAILED columns, not exit codes. |
scripts/make_demo.py | — | Generates synthetic BSON fixtures (no real data) to exercise the toolkit. |
python3 scripts/make_demo.py
python3 scripts/bson_frame.py --total demo/*.bson
python3 scripts/sensitivity_scan.py demo/*.bson
python3 scripts/integrity_verify.py --compare demo/source demo/copy
Ran in this order. The carve step branches by guest; the setro/image bookends and the content-keyed verify are the same either way.
damaged disk ──setro+image──► read-only image
│
database ──────────►│◄────────── filesystem
carve WT pages │ carve files
count docs │ classify by content + manifest
│
tier exposure (RED/AMBER/GREEN)
verify copy (content-keyed)
│
recover
find | sort | xargs sha256sum | sha256sum hashes paths and locale too. Use LC_ALL=C, or a per-file manifest.sha256sum -c exits 0 while printing FAILED. Read the columns, not $?.Starting points, not turnkey. Production WiredTiger uses page compression (snappy/zstd) and cell encoding that the generic carver stubs out. Wire your own strong decoder for compressed pages. Cross-check every number against a positive control and a second, different instrument: two tools that share an assumption agree because of the assumption, not because it's right; reading the bytes is what catches that.
If your data isn't the same (different database, app, or filesystem), the methodology still carries: set the source read-only, image it, carve from the copy, positive-control before believing a zero, count the whole thing, verify by content. Swap the carver; keep the discipline.
Harden the root cause: the Proxmox web UI on :8006 should not be reachable from
the internet. Follow the official Proxmox security advisories:
https://forum.proxmox.com/threads/proxmox-virtual-environment-security-advisories.149331/
A more user-friendly writeup with the author's general advice on recovery and hardening: https://claude.ai/code/artifact/621495ce-4972-405a-ad90-f7703f7dc2f1
CC0 / public domain. See LICENSE.