Talos Vulnerability Report

TALOS-2022-1586

Asus RT-AX82U get_IFTTTTtoken.cgi authentication bypass vulnerability

January 10, 2023
CVE Number

CVE-2022-35401

SUMMARY

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.

CONFIRMED VULNERABLE VERSIONS

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

PRODUCT URLS

RT-AX82U - https://www.asus.com/us/Networking-IoT-Servers/WiFi-Routers/ASUS-Gaming-Routers/RT-AX82U/

CVSSv3 SCORE

9.0 - CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H

CWE

CWE-324 - Use of a Key Past its Expiration Date

DETAILS

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(&timestamp, 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: &timestamp, maxlen: 0x80, format: &percentld, uptime())                           // [10]
00074648      nvram_set("ifttt_timestamp", &timestamp)
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.

TIMELINE

2022-08-01 - Initial Vendor Contact
2022-08-09 - Vendor Disclosure
2022-11-16 - Vendor Patch Release
2023-01-10 - Public Release

Credit

Discovered by Lilith >_> of Cisco Talos.