Talos Vulnerability Report

TALOS-2023-1778

peplink Surf SOHO HW1 data.cgi xfer_dns OS command injection vulnerability

October 11, 2023
CVE Number

CVE-2023-34356

SUMMARY

An OS command injection vulnerability exists in the data.cgi xfer_dns 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.

CONFIRMED VULNERABLE VERSIONS

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)

PRODUCT URLS

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

CVSSv3 SCORE

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

CWE

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

DETAILS

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/data.cgi endpoint, which are intended to interact with the DNS zone transfer operations. This endpoint is accessible only after successfully authenticating as a user with write privileges on the device. The HTTP POST request must have its parameter option set to xfer_dns and parameter step set to view_domain in order to reach the vulnerable code.

The vulnerable function is located in the file data.cgi at offset 0x491814 in firmware version 6.3.5, and we refer to it as view_domain. In order to reach it, the request first transits through a dispatcher function located at 0x491f14, which we refer to as xfer_dns. Annotated decompilations of the dispatcher function and vulnerable function are included for reference.

int xfer_dns()
{
    char *xfer_session = cgi_safe_param("xfer_session");
    
    // [1] This must be set to `view_domain` in order to be dispatched to the vulnerable function
    char* step = cgi_safe_param("step");  

    if (xfer_session[0] == 0)
    {
        fprintf(*stdout, "<%s>%s</%s>", "error", "Missing session", "error");
        return 0;
    }
    
    // [2] Observe that some parameters are subject to (a degree of) scrutiny and validation before use
    if (contains_dangerous_shell_characters(xfer_session) == 0)  
    {
        fprintf(*stdout, "<%s>%s</%s>", "error", "Unauthorized session", "error");
        return 0;
    }

    // [3] Dispatch code follows
    if (strcmp(step, "zone_transfer") == 0)
    {
        return zone_transfer(xfer_session);
    }
    if (strcmp(step, "status") == 0)
    {
        return status(xfer_session);
    }
    if (strcmp(step, "view_domain") == 0)
    {
        return view_domain(xfer_session, cgi_safe_param("domain"));
    }
    if (strcmp(step, "import") == 0)
    {
        return import("session");
    }
    fprintf(*stdout, "<%s>%s</%s>", "error", "Unknown step", "error");
}

The dispatch functionality is relatively straightforward and is included to show that at [2] certain parameters are subject to a limited degree of scrutiny. The implementation of what we refer to as contains_dangerous_shell_characters is limited to checking whether the parameter contains grave mark, double quote or dollar sign characters. At [3] the dispatcher hands control off to the target function, which in this case is view_domain.

int view_domain(char* xfer_session, char* domain)
{
    char cmd[0x400] = {0};
    
    // [4] Craft the command using the unchecked, attacker-controlled domain parameter
    snprintf(&cmd, 0x400, "/usr/local/ilink/bin/dns_import view \"%s\" \"%s\" 2>/dev/null", xfer_session, domain);
    
    // [5] Execute the command with root privileges
    FILE* stream = popen(&cmd, "r");
    
    ...  // The remainder of this function is responsible for parsing the output of dns_import and is unrelated to this vulnerability
    }
}

Observe that when dispatched (at [3]) the HTTP POST param domain is passed as the domain parameter to view_domain. An OS command is crafted at [4] by directly injecting the attacker-controlled domain value. Finally, at [5], the command is executed with root privileges. A properly formatted request can escape the intended command and execute arbitrary commands.

TIMELINE

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

Credit

Discovered by Matt Wiseman of Cisco Talos.