Talos Vulnerability Report


peplink Surf SOHO HW1 admin.cgi USSD_send OS command injection vulnerability

October 11, 2023
CVE Number



An OS command injection vulnerability exists in the admin.cgi USSD_send functionality of peplink Surf SOHO HW1 v6.3.5 (in QEMU). A specially crafted HTTP request can lead to command execution. An attacker can make an authenticated HTTP request to trigger this vulnerability.


The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

Peplink Surf SOHO HW1 v6.3.5 (in QEMU)


Surf SOHO HW1 - https://www.peplink.com/products/soho-series-surf/


7.2 - CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H


CWE-78 - Improper Neutralization of Special Elements used in an OS Command (‘OS Command Injection’)


The Surf series of SOHO routers is marketed as an entry-level router for use at home. It provides networking via USB cellular modems, ethernet and Wi-Fi. The device can host a VPN and supports Wi-Fi meshing.

The device hosts a web interface for administrative configuration. An OS command injection vulnerability exists in the handling of requests destined for the /cgi-bin/MANGA/admin.cgi endpoint that are intended to interact with the GSM module. This endpoint is accessible only after successfully authenticating as a user with write privileges on the device. The HTTP POST request must have a parameter, section, whose value is set to USSD_send in order to reach the vulnerable code. This vulnerability only impacts systems with a functioning GSM module.

The vulnerable function is located in the file admin.cgi at offset 0x4b124c in firmware version 6.3.5, and we refer to it as USSD_send. An annotated decompilation of the function is included for reference.

int USSD_send(taglist_t* taglist) {
    char cmd[0x400] = {0};
    FILE* stream;

    int conn_id = strtol(cgi_safe_param("conn_id"), 0, 10);
    int sim_id = strtol(cgi_safe_param("sim_id"), 0, 10);
    char* ussd_code = cgi_safe_param("ussd_code");  // [1] Extract ussd_code parameter into ussd_code variable
    char* status;

    if (is_wan_connection_available(taglist, conn_id) == 0)  // [2] Device must have functioning GSM module
        status = "Selected WAN connection is not available at this moment.";
        return 1;
    } else {
        snprintf(&cmd, 0x400, "mdstatus -W%d -Ci", conn_id);
        stream = popen(&cmd, "r");
        ... // [3] Perform some data extraction on response

        snprintf(&cmd, 0x400, "mdstatus -W%d -u"%d,%s"", conn_id, sim_id, ussd_code);  // [4] Craft the command using unchecked, attacker-controlled ussd_code parameter
        stream = popen(&cmd, "r");  // [5] Execute the command with root privileges
        ... // [6] Perform some data extraction and business logic on response

    return 0;

Observe at [1] that a pointer to the HTTP POST param ussd_code is placed into the ussd_code stack variable. If the network status check at [2] is passed, the command is crafted at [4] by directly injecting the attacker-controlled value. Finally, at [5], the command is executed with root privileges. A properly formatted request can escape the intended command and execute arbitrary commands.


2023-06-26 - Initial Vendor Contact
2023-06-27 - Vendor Disclosure
2023-10-11 - Public Release


Discovered by Matt Wiseman of Cisco Talos.