CVE-2022-35401
An authentication bypass vulnerability exists in the get_IFTTTTtoken.cgi functionality of Asus RT-AX82U 3.0.0.4.386_49674-ge182230. A specially-crafted HTTP request can lead to full administrative access to the device. An attacker would need to send a series of HTTP requests to exploit this vulnerability.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
Asus RT-AX82U 3.0.0.4.386_49674-ge182230
RT-AX82U - https://www.asus.com/us/Networking-IoT-Servers/WiFi-Routers/ASUS-Gaming-Routers/RT-AX82U/
9.0 - CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H
CWE-324 - Use of a Key Past its Expiration Date
The Asus RT-AX82U router is one of the newer Wi-Fi 6 (802.11ax)-enabled routers that also supports mesh networking with other Asus routers. Like basically every other router, it is configurable via a HTTP server running on the local network. However, it can also be configured to support remote administration and monitoring in a more IOT style.
In order to enable remote management and monitoring of our Asus Router, so that it behaves just like any other IoT device, there are a couple of settings changes that need to be made. First we must enable WAN access for the HTTPS server (or else nothing could manage the router), and then we must generate an access code to link our device with either Amazon Alexa or IFTTT. These options can all be found internally at http://router.asus.com/Advanced_Smart_Home_Alexa.asp
.
As a high level overview, upon receiving this code, the remote website will connect to your router at the get_IFTTTtoken.cgi
web page and provide a shortToken
HTTP query parameter. Assuming this token is received within 2 minutes of the aforementioned access code being generated, and also assuming this token matches what’s in the router’s nvram, the router will respond back with an ifttt_token
that grants full administrative capabilities to the device, just like the normal token used after logging into the device via the HTTP server.
0002863c int32_t do_get_IFTTTToken_cgi(int32_t arg1, FILE* arg2)
00028660 char* r0 = get_UA_Type(inpstr: &user_agent) // [1]
00028668 char* r0_2
00028668 if (r0 != 4) // asusrouter-Windows-IFTTT-1.0
000286b0 r0_2 = get_UA_Type(inpstr: &user_agent)
// [...]
000286cc void var_30
000286cc memset(&var_30, 0, 0x20)
000286d8 char* r0_4 = check_if_queryitem_exists("shortToken") // [2]
000286e0 if (r0_4 == 0)
000286e4 r0_4 = &nullptr
000286ec int32_t r0_5 = gen_IFTTTtoken(token: r0_4, outbuf: &var_30) // [3]
00028700 fputs(str: &(*"\tif (disk_num == %d) {\n")[0x15], fp: arg2)
00028708 fflush(fp: arg2)
0002871c fprintf(stream: arg2, format: ""ifttt_token":"%s",\n", &var_30) // [4]
00028724 fflush(fp: arg2)
00028738 fprintf(stream: arg2, format: ""error_status":"%d"\n", r0_5)
00028740 fflush(fp: arg2)
00028750 fputs(str: &data_81196, fp: arg2)
00028760 return fflush(fp: arg2)
At [1], the function pulls out the “User-Agent” header of our HTTP GET request and checks to see if it starts with “asusrouter”. It also checks if the text after the second dash is either “IFTTT” or “Alexa”. In either of those cases, it returns 4 or 5, and we’re allowed to proceed in the code path. At [2], the function pulls out the shortToken
query parameter from our HTTP GET request and passes that into the gen_IFTTTtoken
function at [3]. Assuming there is a match, gen_IFTTTtoken
will output the ifttt_token
authentication buffer to var_30
, which is then sent back to the HTTP sender at [4]. Looking at gen_IFTTTtoken
:
0007b5c8 int32_t gen_IFTTTtoken(char* token, uint8_t* outbuf)
0007b5d4 int32_t r0 = uptime()
0007b5fc memset(&ifttt_token_copy, 0, 0x20)
0007b614 int32_t r0_8
0007b614 int32_t arg3
0007b614 int32_t arg4
0007b614 if (r0 - nvram_get_int("ifttt_timestamp") s> 120) // [5]
0007b6ec if (isFileExist("/tmp/IFTTT_ALEXA") s> 0)
0007b710 Debug2File("/tmp/IFTTT_ALEXA.log", "[%s:(%d)][HTTPD] short token timeout\n", "gen_IFTTTtoken", 0x3ff, token, outbuf, arg3, arg4)
0007b714 r0_8 = 1
0007b630 else if (nvram_get_and_cmp("ifttt_stoken", token) == 0) // [6]
0007b72c if (isFileExist("/tmp/IFTTT_ALEXA") s> 0)
0007b760 Debug2File("/tmp/IFTTT_ALEXA.log", "[%s:(%d)][HTTPD] short token is not the same: endp…", "gen_IFTTTtoken", 0x402, token, p2_nvram_get(item: "ifttt_stoken"), arg3, arg4)
0007b764 r0_8 = 2
0007b64c else if (get_UA_Type(inpstr: &user_agent) != 4)
0007b77c if (isFileExist("/tmp/IFTTT_ALEXA") s> 0)
0007b7a0 Debug2File("/tmp/IFTTT_ALEXA.log", "[%s:(%d)][HTTPD] user_agent not from IFTTT/ALEXA\n", "gen_IFTTTtoken", 0x405, token, outbuf, arg3, 0xf1430)
0007b7a4 r0_8 = 3
0007b668 else
0007b668 int32_t r2
0007b668 uint8_t* r3
0007b668 r2, r3 = nvram_set("skill_act_code", p2_nvram_get(item: "skill_act_code_t"))
0007b674 generate_asus_token(dst: &ifttt_token_copy, len: 0x20, r2, readsrc: r3) // [7]
0007b684 strlcpy(dst: outbuf, src: &ifttt_token_copy, len: 0x20)
0007b694 nvram_set("ifttt_token", &ifttt_token_copy)
0007b698 nvram_commit()
0007b6ac if (isFileExist("/tmp/IFTTT_ALEXA") s> 0)
0007b6d0 Debug2File("/tmp/IFTTT_ALEXA.log", "[%s:(%d)][HTTPD] get IFTTT long token success\n", "gen_IFTTTtoken", 0x408, token, outbuf, arg3, 0xf1430)
0007b6d4 r0_8 = 0
0007b7ac return r0_8
Right at the beginning there is a check [5] to see if the uptime of the device is more than two minutes after the ifttt_stoken
has been generated. Assuming we are within that timeframe, the ifttt_stoken
nvram item is grabbed and compared with our shortToken
at [6]. If there’s a match, we end up hitting the code branch around [7], where the device generates a new ifttt_token
and copies it to the output buffer on the next line of code. As a reminder, this token grants the same admin access as the normal HTTP login token.
While nothing really seems out of place at the moment, let’s take a look over at the code which actually generates the ifttt_stoken
:
00074210 uint8_t* do_ifttt_token_generation(uint8_t* output)
// [...]
000742c0 char ifttt_token[0x80]
000742c0 memset(&ifttt_token, 0, 0x80)
000742d0 char timestamp[0x80]
000742d0 memset(×tamp, 0, 0x80)
000742e0 char rbinstr[0x8]
000742e0 rbinstr[0].d = 0
000742e8 int32_t* randbinstrptr = &rbinstr
000742f4 rbinstr[4].d = 0
00074308 srand(x: time(timer: nullptr))
0007431c // takes the remainder...
00074324 int_to_binstr(inp: __aeabi_idivmod(rand(), 0xff), cpydst: randbinstrptr, len: 7) // [8]
// [...]
00074608 snprintf(s: &ifttt_token, maxlen: 0x80, format: &percent_o, binary_str_to_int(randbinstrptr)) // [9]
0007461c nvram_set("ifttt_stoken", &ifttt_token)
00074638 snprintf(s: ×tamp, maxlen: 0x80, format: &percentld, uptime()) // [10]
00074648 nvram_set("ifttt_timestamp", ×tamp)
00074658 strlcpy(dst: output, src: &skill_act_code, len: 0x48)
0007465c nvram_commit()
0007466c return output
With the unimportant code cut out, we are left with a somewhat clear view of the generation process. At [8] a random number is generated that is then moded against 0xFF. This number is then transformed into a binary string of length 8 (e.g. ‘00101011’). A lot further down at [9], this randbinstrptr
is converted back to an integer and fed into a call to snprintf(&ifttt_token, 0x80, "%o", ...)
, which generates the octal version of our original number. With this in mind, we can clearly see that the keyspace for the ifttt_stoken
is only 255 possibilities, which makes brute forcing the ifttt_stoken
a trivial matter. While normally this would not be a problem, since the ifttt_stoken
can only be used for two minutes after generation, we can see a flaw in this scheme if we take a look at the ifttt_timestamp
’s creation. At [10] we can clearly see that it is the uptime()
of the device in seconds (which is taken from sysinfo()
). If we recall the actual check from before:
0007b5d4 int32_t r0 = uptime()
// [...]
0007b614 if (r0 - nvram_get_int("ifttt_timestamp") s> 120)
// [...]
0007b630 else if (nvram_get_and_cmp("ifttt_stoken", token) == 0)
We can see that the current uptime is used against the uptime of the generated token. Unfortunately for the device, uptime
starts from when the device was booted, so if the device ever restarts or reboots for any reason, the ifttt_stoken
suddenly becomes valid again since the current uptime will most likely be less than the uptime()
call at the point of ifttt_stoken
generation. Neither the ifttt_timestamp
or the ifttt_stoken
are ever cleared from nvram, even if the Amazon Alexa and IFTTT setting are disabled, and so the device will remain vulnerable from the moment of first generation of the configuration.
2022-08-01 - Initial Vendor Contact
2022-08-09 - Vendor Disclosure
2022-11-16 - Vendor Patch Release
2023-01-10 - Public Release
Discovered by Lilith >_> of Cisco Talos.