CVE-2025-60691

...

6.1 Ghidra

apply_cgi function

  • Only the relevant code portions to the CVE is shown

undefined4
apply_cgi(FILE *param_1,undefined4 param_2,undefined4 param_3,undefined4 param_4,undefined4 param_5,
         char *param_6,char *param_7)

{
  char *pcVar1;
  int iVar2;
  char *__s1;
  char *pcVar3;
  FILE *__stream;
  undefined4 uVar4;
  undefined **ppuVar5;
  undefined1 *puVar6;
  uint __seconds;
  char local_838;
  undefined1 auStack_837 [2047];
  char *local_38;
  int local_34;
  int local_30;
  
  pcVar1 = (char *)get_cgi(0x49f270);
  if (pcVar1 == (char *)0x0) {
    pcVar1 = "0";
  }
  local_30 = atoi(pcVar1);
  pcVar1 = (char *)get_cgi(0x49f188);
  if (pcVar1 == (char *)0x0) {
    pcVar1 = "1";
  }
  local_34 = atoi(pcVar1);
  local_38 = (char *)get_cgi(0x49f27c);
  error_value = 0;
  pcVar1 = (char *)get_cgi(0x49f288);
  if (pcVar1 == (char *)0x0) {
    pcVar1 = "";
  }
  iVar2 = strcmp(pcVar1,"gozila_cgi");
  if (iVar2 == 0) {
    gozila_cgi(param_1);
    return 1;
  }
  __s1 = (char *)get_cgi(0x49edc0);
  if (__s1 == (char *)0x0) {
    __s1 = "";
    if (param_7 != (char *)0x0) goto LAB_00421bc0;
LAB_00421d74:
    puVar6 = (undefined1 *)0x0;
    __seconds = 0;
  }
  else {
    if (param_7 == (char *)0x0) goto LAB_00421d74;
LAB_00421bc0:
    if (param_6 != (char *)0x0) {
      iVar2 = strcmp(param_6,"tmBlock.cgi");
      if (iVar2 == 0) {
        local_838 = '\0';
        memset(auStack_837,0,0x7ff);
        iVar2 = get_cgi(0x4a2600);
        sprintf(&local_838,"http://%s",iVar2);
        nvram_set("TM_block_url",&local_838);
        iVar2 = get_cgi(0x4a35cc);
        nvram_set("TM_block_index",iVar2);
        iVar2 = get_cgi(0x4a1474);
        nvram_set("TM_block_hwaddr",iVar2);
        pcVar1 = (char *)get_cgi(0x49f2ec);
        if ((pcVar1 == (char *)0x0) || (iVar2 = strcmp(pcVar1,"1"), iVar2 != 0)) {
          do_ej("tmWTPBlock.asp",param_1);
        }
        else {
          do_ej("tmPCBlock.asp",param_1);
        }
        wfflush(param_1);
        return 1;
      }
      iVar2 = strcmp(param_6,"hndBlock.cgi");
      if (iVar2 == 0) {
        local_838 = '\0';
        memset(auStack_837,0,0x7ff);
        iVar2 = get_cgi(0x4a2600);
        sprintf(&local_838,"http://%s",iVar2);
        nvram_set("hnd_block_url",&local_838);
        iVar2 = get_cgi(0x49bfd0);
        nvram_set("hnd_block_policy",iVar2);
        iVar2 = get_cgi(0x4a1474);
        nvram_set("hnd_block_mac",iVar2);
        iVar2 = get_cgi(0x49f6a0);
        nvram_set("hnd_block_ip",iVar2);
        pcVar1 = (char *)get_cgi(0x49f2ec);
        if ((pcVar1 == (char *)0x0) || (iVar2 = strcmp(pcVar1,"0"), iVar2 != 0)) {
          do_ej("BlockSite.asp",param_1);
        }
        else {
          do_ej("BlockTime.asp",param_1);
        }
        nvram_set("hnd_password_deny",&DAT_0049ea84);
        wfflush(param_1);
        return 1;
      }
    }

...
  • From previous fuzzing steps, we know that we can reach the apply_cgi function with the simple HTTP request (other headers and parameters redacted):

6.1.1 Analysis of the source code

  1. From the apply_cgi function earlier, we can understand the following:

a. Function arguments

b. Values required for the variables, in order for the control flow to reach the vulnerable code portion

get_cgi calls

String value@0x49f270 -> need_reboot

  • No requirements

String value@0x49f188 -> commit

  • No requirements

String value@0x49f288 -> change_action

  • Must not be the value gozila_cgi

String value@0x49dec0 -> submit_button

  • Must be a NULL pointer (not defined in the request)

  • param_6 and param_7 must NOT be a NULL pointer

  • param_6 must contain the value of either tmBlock.cgi or hndBlock.cgi

  1. The following .asp file can be found from the location: /www/index.asp (UART console):

From the index.asp file, we can identify a few potential HTTP parameters:

  • submit_button

  • change_action

  • submit_type

  • action

  • etc.

6.1.2 Possible HTTP POST payload

POST parameter values

As we understand from the analysis before, we have to set the following values in order to control the flow in our way:

  1. change_action

  • Must NOT be equal to the value gozila_cgi

  1. submit_button

  • Must be a NULL pointer (not defined)

  1. param_6

  • Likely taken from the action parameter

  • Must NOT be a NULL pointer

    • Either tmBlock.cgi or hdnBlock.cgi

  1. param_7

  • Likely taken from the submit_type parameter

  • Must NOT be a NULL pointer

    • Can be an empty string

6.2 GDB, gdbserver

...

6.3 Further analysis (to confirm the HTTP POST payload)

1. Network analysis

It is likely that the index.asp file is loaded whenever a request is sent to the root URL of the webpage on port 80. We know that there exist a form element that calls the apply.cgi route, which triggers the apply_cgi function

1.1 Manually send form request

We can manually send a form request, and use the following methods to analyze the HTTP traffic to understand the parameters and values involved

a. Web browser network tool

b. Burp suite proxy

  • From the web browser: configure the HTTP proxy to point to the address of a machine (on the same LAN) running a Burp server

  • We are now able to modify the HTTP request on the go

1.2 Further analysis

  1. Perform a recursive search (eg. grep) to find all matching patterns for the files in the /www directory:

  • Other form elements

  • Likely indication of HTTP parameters that relates to param_6 and param_7

  • For better understanding of the source code

2. Analyis in Ghidra

2.2 Search for value "apply_cgi"

  • From header tab: Search

a. Search "For Strings..."

b. Search "For Direct References"

c. Search "Program Text"

d. Search "Memory"

e. XREFs

  • Finding all references to the apply_cgi function

  • Right click on function name -> References -> Find References to apply_cgi

The only useful reference is found as a DATA from the router_upgrade function

  • The apply_cgi function itself is not even called within this function

2.3 Search for value "apply.cgi"

  • From header tab: Search

a. Search "For Strings..."

  • Section under the .rodata

b. Search "For Direct Reference

  • None found

c. Search "Program Text"

  • None found

d. Search "Memory"

...

e. XREFs

...

3. Google dork

We can perform a Google dork to discover other vulnerabilities found on Linksys router for the httpd binary, specifically for any.cgi functions:

...

Last updated