Skip to content

5_1 Accessing files outside export

Michael Eder edited this page Dec 29, 2024 · 1 revision

If subtree_check is not enabled and the export is not the root of a file system, it is possible to read other files on the same file system using the following method. This attack is based on the NFS File Handle Security Technical Report and has been adapted to work on modern Linux systems.

Preconditions

Some preconditions are required for this attack to be successful.

Preconditions on the server side

  • The server has to run on Linux. Based on the report other operating systems have also been affected, however the attack is based on the internal structure of the file handle which is specific to the server's operating system. We have only looked at the modern Linux implementation.
  • NFSv3 is enabled (default). Based on manual testing, this attack also works with NFSv4, but our tools only support NFSv3.
  • The export option subtree_check has not been explicitly enabled. By default it is disabled since nfs-utils version 1.1.0 released in 2007.
  • The export is not the root directory of a file system. With this attack, it is not possible to read files that are located on another file system than the exported directory. This is most likely the point where this attack will fail on a typical storage system. However there might also be an impact if there are multiple exports with different security settings on the same file system.
  • The file system must have predictable inode and generation numbers. This is the case for ext4, xfs and btrfs, which are the default file systems on Debian, Fedora and Suse.

All of these preconditions are met if a directory is exported on a standard installation of one of the mentioned distributions without explicitly setting the subtree_check option.

Preconditions on the attacker side

  • The attacker must be authenticated and allowed to perform operations on the export. This is only relevant when Kerberos authentication is required.
  • If access to the export is limited to certain hosts, the attacker must be able to spoof/use an allowed IP address.
  • The attacker can send manipulated NFS requests to the server. When AUTH_SYS is used, Linux only allows connections from ports < 1024. Attackers must have a way to send manipulated NFS traffic from a privileged port which requires root access or cap_net_bind_service on a machine on the network.

Potential impact

If this attack is successful, all files that are on the same file system as the exported directory can be accessed, except those that are only accessible to the root user unless the no_root_squash option is explicitly enabled. This is especially problematic if the exported directory is located on the same file system as the operating system installation or the home directories because this could allow an attacker to get code execution on the server. If the exported directory is the root directory of a dedicated file system, which is typical for NAS systems, the attack has no effect because it only grants access to files on the same file system.

How it works

To perform the attack, we first need to get a real file handle from the server by mounting an export because we have to know the file system UUID. To build a file handle that points to the file system root directory we can just take any file handle generated by the server and replace the fileid part with the one of the root directory while keeping the file system identifier part the same. For ext4 and xfs, we can use a fileid structure of type 2. This means that in addition to the inode and generation number we also have to provide the parent directory's inode and generation which is not a problem because the root directory is the parent of itself. The root directories of ext4 and xfs always use the same inode number and have a generation of 0.

The following fileid structure gives access to the file system root directory on ext4 and xfs.

ext4 xfs Len Meaning
0x02 0x80 4 inode number
0x00 0x00 4 generation
0x02 0x80 4 parent inode number (root directory is parent of itself)
0x00 0x00 4 parent generation

For ext4, the file id for the root directory would be 0x02000000000000000200000000000000.

The following example shows how a file handle from a server can be modified to get access to the file system root directory.

              meta                      fsid                                     fileid
            |======||==============================================||==============================|
                                                                       ino     gen    p ino   p gen
                                                                    |======||======||======||======|
original: 0x0100070201fd05000000000065e9e814e14742e0b4b70279933adc3778e006009b9d890402fd0500706bf7e7
ext4:     0x0100070201fd05000000000065e9e814e14742e0b4b70279933adc3702000000000000000200000000000000
xfs:      0x0100070201fd05000000000065e9e814e14742e0b4b70279933adc3780000000000000008000000000000000

These steps can be repeated for every export reported by mountd. There might be one that shares a file system with some interesting data but does not have the subtree_check enabled. Our tool nfs_analyze does this automatically and lists the root directory if it is successful. You can then pass the obtained file handle to our other tool fuse_nfs in order to mount and access it. The tool will allow you to perform operations on the server while automatically using the right uid and gid to get access to most files.

If you want to experiment with this and need to know the generation number of a file or directory on your system, you can use the code from this Stackoverflow thread.

BTRFS

The attack is also possible for btrfs which uses a different file handle structure. A difference between btrfs and ext/xfs is that there is not a single root directory but many different subvolumes and snapshots. If subtree_check is disabled, it is possible to access data on all subvolumes and snapshots even if they are not mounted. The file id structure for the root directory of a subvolume looks like this:

Data Len Meaning
0x0001000000000000 8 Object ID, always 256 for all root directories
0x0001000000000000 8 Subvolume ID, starts at 256, incrementing
0x00000000 4 Generation, always 0

We can just take this file handle and increment the subvolume ID until we find interesting data. Our tool nfs_analyze can automatically try a given number of subvolume IDs and print a listing of the root directory.

On the server, you can use btrfs subvolume list MOUNTPOINT to see the IDs of all available subvolumes and snapshots.

There is a special subvolume with number 5 that contains all other subvolumes and snapshots as subdirectories. It can be accessed using the following file handle.

Data Len Meaning
0x0001000000000000 8 Object ID, always 256
0x0500000000000000 8 Subvolume ID, 5
0x00000000 4 Generation, always 0

ZFS

The attack also works on ZFS but there are some limitations. While it is possible to access any subvolume on btrfs when subtree_check is disabled, on ZFS you can only access data on the same dataset as the exported directory. The file id structure for the root directory of a dataset looks like this

Data Len Meaning
0x0a00 2 ?
0x2200000000 4 Inode, root directory always has value 0x22
0x0000 2 ?
0x4300000000 4 Generation, usually a small value

Since the generation number varies, some brute force is required. When the export is configured using the zfs set sharenfs=... command, it is impossible to export a subdirectory. This command does not write to /etc/exports, it communicates directly with exportfs.

This means that accessing data outside of the export is only possible if a subdirectory of the ZFS file system is exported in the /etc/exports file without subtree_check. Based on our testing, TrueNAS Scale automatically enables subtree_check when a subdirectory of a dataset is exported. Due to the very limited impact of this and the need for brute force we have not implemented this attack in our tools and only tested it by manually manipulating the file handles.

Reading /etc/shadow on Suse and Debian based systems

If the attack was successful and it is possible to access the operating system partition, we still don't have root access unless the no_root_squash option was enabled for the vulnerable export. However there is still a way to read /etc/shadow on Debian and Suse. On Debian, the file /etc/shadow has the following permissions set:

-rw-r----- 1 root shadow 1421 Feb 19 17:48 /etc/shadow

The permissions indicate that a group called shadow has read access to /etc/shadow. On a normal Debian system, there should not be any users in this group. There are only some setgid binaries owned by this group, which allows them to read /etc/shadow without having to run with full root privileges. One of these binaries is chage which can be run by any user to see their password expiration date which is stored in /etc/shadow.

The shadow group provides a security benefit by not having to run these programs with full root privileges, which could lead to privilege escalation in case a vulnerability is found in one of them. In this scenario however, it allows an attacker to read /etc/shadow on a misconfigured NFS server even if no_root_squash is not explicitly enabled. This is because the NFS server only squashes the uid and gid 0 by default. It is possible to send an NFS request with the gid of shadow to read the file. Writing is not possible because the shadow group only has read access. However, the obtained password hashes can be used for an offline cracking attempt which could be successful if one of the accounts has a very weak password.

nfs_analyze automatically tries to print the contents of /etc/shadow if it successfully escapes an export.

Accessing files on other file systems

WARNING: Deleting the export directory will cause a major disruption! Don't perform this attack unless you absolutely know what you are doing

If certain conditions are met, there is an attack that can be used to access all other file systems on the machine. It has been described in this thread on the linux-nfs mailing list. As an example, assume that the file system root is /mnt/storage and the exported directory is /mnt/storage/export. Using the attack described previously, it is possible to access /mnt/storage. Now, delete or rename the directory /mnt/storage/export and instead create a symlink that points to any directory on the server. This requires write permissions on the parent directory. If no_root_squash is enabled for the export, this is not a problem but otherwise the permissions of the directory might be configured in a way that makes this attack impossible. The major drawback of this attack is that the change is not applied immediately and a restart of the NFS service or the entire server is necessary. When the server starts and sets up the exports, it will follow the symlink and export the directory it points to. The attack is also possible without any file handle manipulation if an export is a subdirectory of another export. In this case you can mount the parent export, delete/rename the child export and replace it with a symlink without any special tools.

Clone this wiki locally