Exploiting CVE-2021-26708 (Linux kernel) with sshd
Kernel vulnerability CVE-2021-26708
Alexander Popov has published an article Four Bytes of Power: Exploiting CVE-2021-26708 in the Linux kernel which use a four-byte overwrite vulnerability to do privilege escalation. It’s really an excellent article that you can find all the details of reproducing privilege escalation. In short, the exploit has several steps as follow:
- Alloc a good msg at a predictable address: win a race on vsock; Read heap address leaked from the /dev/kmsg and then use msgsnd() to alloc good msg at that address.
- Arbitary-free good msg: win a race on vsock; overwrite msg->security with the leaked address; use failed msgsnd alloc a corrupt msg and free msg->security. Plz noted that the good msg can be freed but it’s still in msg queue, which is the spot where UAF become feasible.
- Arbitary-write good msg: Arbitary read primitives could be forged by allocating the objects via setxattr at the address of good msg and then overwrite it.
- Arbitray-read from good msg: leak content of vsk structure by using msgrcv().
- Alloc a sk_buff with special flags in its buff.
- Similar to step-4: Search that special flags in the heap to get the address of sk_buff.
- Similar to step-2: Free sk_buff.
- Similar to step-3: Overwrite sk_buff function pointer, control-flow can be hyjacked.
- ROP to do privilege escalation.
This is the way: sshd
You might noticed that step-4 is able to get leaked address of owner_cred, while step-2 can achieve arbitary free. The cred structure is allocated in the special slab cache cred_jar. If multiple privileged processes are created and the cred pointer being freed occurs at the same time (or the privileged processes are created a bit little later than cred pointer being freed), then the cred of one of the processes can be located at the freed address, which make privilege escalation possible. sshd seems the privileged process we need. An ssh connection with login completed has two processes:
$ ps -axu|grep ssh
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
...
root 924 0.6 0.5 43876 10576 ? Ss 01:47 0:00 sshd: victim [priv]
victim 939 0.0 0.3 43776 6196 ? S 01:47 0:00 sshd: victim@pts/0
...
An ssh connection to the target without login would look like this:
$ ps -axu|grep ssh
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
sshd 988 0.0 0.2 30564 5676 ? S 01:54 0:00 sshd: victim [net]
The privilege of client ssh won’t matter because sshd, whose user is root by default, will establish the connection. sshd will wait for the password input until timeout. These mechaism might make exploit writer’s job easier. The following steps should be:
- Alloc a good msg at a predictable address: win a race on vsock; Read heap address leaked from the /dev/kmsg and then use msgsnd() to alloc good msg at that address.
- Arbitary-free good msg: win a race on vsock; overwrite msg->security with the leaked address; use failed msgsnd alloc a corrupt msg and free msg->security. Plz noted that the good msg can be freed but it’s still in msg queue, which is the spot where UAF become feasible.
- Arbitary-write good msg: Arbitary read primitives could be forged by allocating the objects via setxattr at the address of good msg and then overwrite it.
- Arbitray-read from good msg: leak content of vsk structure by using msgrcv().
- Prepare 20 “ssh user@target&” on your host, but don’t run them.
- Similar to step-2, arbitrary free owner_cred. After the owner_cred was freed, run those ssh right now. One of those sshd’s cred would locate at owner_cred. The privilege escalation would be done!
We can see the result:
[victim@hardenedvault ~]$ uname -a
Linux hardenedvault 5.10.11-200.fc33.x86_64 #1 SMP Wed Jan 27 20:21:22 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
[victim@hardenedvault ~]$ id
uid=1001(victim) gid=1001(victim) groups=1001(victim) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
[victim@hardenedvault ~]$ ./cve-2021-26708
[+] finish init userfaultfd:
page fault addr: 5004000
[+] try to get addr of good msg_msg...
[+] 1598'th finished get addr of good msg_msg
[+] get the address of good msg after 1598 trys
[+] addr_of_good_msg: 0xffff977a85f71340
[+] addr_of_vsk: 0xffff977a8555e600
[+] adapt the msg_msg spraying payload:
msg_ptr 0x5003fd8
m_type 1337 at 0x5003fe8
m_ts 6096 at 0x5003ff0
msgseg next 0xffff977a8555e600 at 0x5003ff8
[+] prepare setxattr threads...
[+] start read userfault...
[+] Start free good msg...
[+] msgsnd failed: msg->security maybe freed
[+] intriguring userfaultfd page fault(msg)
page fault address: 5004000 flags: 0
[+] try to read kernel leak...
[+] 8177'th finished read the leak
[+] read leak:
sk_def_write_space: ffffffff939851b0
owner_cred: ffff977a83e15600
sk_memcg: ffff977a85549000
[+] Start free cred...
[+] msgsnd failed: owner cred maybe freed !!!
[+] 100'th free owner_cred, waitting for sshd
[root@hardenedvault ~]# id
uid=0(root) gid=0(root) groups=0(root) context=system_u:system_r:unconfined_t:s0-s0:c0.c1023
Some details about sshd help cred-jar heap spraying
Is it possible to exploit in one single step?
Actually, it’s possible, but you need to craft a special process which create a process before arbitrary-free owner_cred and waiting until owner_cred is freed, starts ssh to localhost.
ssh with login or not
sshd may terminate because of timeout. ssh and login would be better. In this case, a kernel panic appears when sshd is terminated, but the time is enough to do lots of evil things! LoginGraceTime in /etc/ssh/sshd_config is for configuring it.
multiple ssh would be better
Since cred_jar cache may be affected by other processes. Running more than one ssh would be more stable.
Limitation
The ssh-server should be enabled by the target
Obviously, the ssh-server running on the target machine is necessary for the privilege escalation. We haven’t investigate if there is another way that an unprivileged process can intrigue creating privileged processes to help cred_jar heap spraying. If a process satisfies the following:
- Unprivileged process can let the target create a privileged process.
- One privileged process response to one unprivileged process.
- The privileged process has a long lifetime.
Then it can be used to do cred_jar heap spraying.
This may affect by other processes
If the target is a server with lots of processes created and terminated, the privilege may be affected by them.
PoC and defense
What GNU/Linux distor and kernel version has been tested?
Federa 33 and 5.10.11-200.fc33.x86_64.
Is PoC/Exploit open source?
Yes, you can find the PoC on project Vault Range PoC.
Any suggestion on mitigation/defense?
There are quite a lot of effective mitigations for this exploit but we’d like to point out that some mitigations are more important than others while data-only attack can be achieved by arbitrary writes, which is likely bypass most mitigation in two stages: exploitation and post-exploitation. Noted that PaX/GRsecurity’s KERNSEAL/AUTOSLAB is an exceptional outstanding mitigation because it’s targeting the Pre-exploitation stage.
Mitigation | Pre-exploitation | Exploitation | Post-exploitation | Bypassable |
---|---|---|---|---|
PaX RAP | N | Y | N | L4: Hardcore |
PaX KERNSEAL/AUTOSLAB | Y | N | N | L5: Nightmare |
VED wCFI | N | Y | N | L3: Hurt Me Plenty |
Metadata integrity | N | N | Y, AKO/LKRG/VED | L2: Bring It On |
VED self-protection | N | Y | N | L3: Hurt Me Plenty |
With all due respect, the bypassable level of most fancy name security product (e.g: EDR/XDR, HIDS, etc) is only able to holding “L1: I can win” if the threats from platform security level according to our client’s report.