CVE-2025-60691

...

1. Ghidra

file-download
8KB

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):

2. Analysis of the source code

2.1 Analysis of the apply_cgi function

  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

  1. String value@0x49f270 -> need_reboot

  • No requirements

  1. String value@0x49f188 -> commit

  • No requirements

  1. String value@0x49f288 -> change_action

  • Must not be the value gozila_cgi

  1. String value@0x49dec0 -> submit_button

    • No constraints, since both will eventually lead to the LAB_00421BC0 routine eventually

  • param_6 and param_7 must NOT be a NULL pointer

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

2.2 Analysis of the index.asp file

Refer to the Exploit research page for more information on the index.asp file

  1. apply.cgi action

The following shows a code snippet of the form field involving the apply.cgi action in the index.asp file

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

  • submit_button

  • change_action

  • submit_type

  • action

  • etc.

Refer to the notes under the Exploit research page to understand how we can control the web page to trigger the apply_cgi function

2.3 Likely HTTP POST request format

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

  • No constraints

  1. param_6

  • Must NOT be a NULL pointer

    • Either tmBlock.cgi or hdnBlock.cgi

  1. param_7

  • Must NOT be a NULL pointer

    • Can be an empty string

3. Network analysis + reverse engineering

3.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

3.1.1 Manually send form request from the webpage

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

Refer to the notes under the Exploit research page to understand more about each function discussed below

Payload analysis

  • The following outlines the payload for each of the specific forms:

(1) selWAN() function

  • Relevant code snippet

    • Search "FUNNAME1" in web browser code inspector tool

  • We can find the following form section on the web page:

  • Next, we can experiment with a few values from the selection options, and record the Payload parameters (form-data) that are automatically generated by the code, and sent with the request

NOTE: we require proper authentication with the Authorization: Basic xxxx request headers, and the response should have a 200 OK status code

a. Automatic Configuration - Static IP

b. Automatic Configuration - DHCP

...

3.2 Analysis with GDB+gdbserver

Now that we have identified the HTTP POST request format and the payload for each specific requests, we can attempt to identify the likely parameters corresponding to the param_6 and param_7 parameters

Using the GDB+gdbserver setuparrow-up-right, we can set breakpoints on relevant portions of the code to analyze the behavior of each HTTP request with different payloads

  1. Set software (and also hardware) breakpoints in GDB

issues likely due to the router limitations...

  1. Manual patching of the httpd program with a break point (start of apply_cgi function)

  • Notice the start address value 0x400000

Points to note:

  • \x0d\x00\x00\x00 represents the value 0x0000000d in little-endian, which is a break instruction

  • We willl overwrite the address at 0x4000000 - 0x421a10 = 0x21a10

    • Offset from the start address

    • Indicated by the seek option

Next, we can use the UART console on the router to retrieve the patched binary, and update the process

  • We also need to kill the current httpd process and start a new one with the patched version

From the host machine running gdbserver:

Note on commands

  • The httpd web server forks each time a new request is received, such as when a call to apply.cgi is made

  • The set follow-fork-mode child and set detach-on-fork off commands are used to aid us in separating the child (fork) and parent process when we are debugging

Error

  • An error right after the continue command is executed, even before any HTTP request is being sent. This is likely due to a certain health check on the apply_cgi function right at the initialization of the program

  1. Manual patching of the httpd program with an infinite loop

  • From the previous analysis, we know that a break point at the start of the apply_cgi function causes the program to crash early

  • Instead, we can use an infinite loop

Points to note:

  • \xA8\x44\x10\x08 represents the MIPS instruction j 0x00421a10 in machine code

Following the same steps as above, the program simply hangs when a request is being sent (refer to the Testing payload section below).

This is expected behavior from the infinite loop. A subsequent ctrl+c command is expected to pause the execution and allow us to interact with the register and stack at the current instruction. However, the program simply exits when ctrl+c is called

This is also likely due to the certain health check mentioned earlier

  1. Manual patching of the httpd program with a break point (middle of apply_cgi function) + attach gdbserver to PID

Let's attempt to set a break point slightly later in the apply_cgi function, in hopes that the initialization process (health check, etc.) will not reach that particular instruction

Attaching gdbserver to an already running process prevents any health check during the initialization steps from interfering with the patched instructions

...

  1. Manually send SIGSTOP from the router console

For this to work, we need to have 2 remote shell instances on the device: one for gdbserver, and the other to send the SIGSTOP signal. Refer to the following sectionarrow-up-right for more information

5.1 Patch with break instruction

5.2 Attach gdbserver to running process and connect from host machine

...

...

5.3 Send HTTP POST request and subsequently, the SIGSTOP signal

  • Next, we can send the HTTP POST request

  • Send the SIGSTOP signal (device console)

  • Right after the kill signal is sent, the gdb-multiarch instance will immediately halt and display values such as the stack, registers, threads, trace, etc. similar to when a breakpoint or manual ctrl+c is entered

    • GDB will give the control back to the console, where we can run commands to view register, memory contents, etc.

  • Notice the following values

    • Stopped 0x421a6c in apply_cgi()

    • reason: SIGSTOP

5.4 Analyze register and memory content

Before we continue, let's discuss some important concepts regarding how arguments are passed in the MIPS architecture

  1. The apply_cgi function accepts 7 arguments. The first 4 are passed via the a0 to a3 registers, while the last 3 are via the stack offset memory at 0x10, 0x14 and 0x18 respectively:

  1. The value 0x860 is added as an offset to properly address the SP offset from the following instruction at the start of the function:

  • This will affect how we view contents on the stack when working with relative addressing

Analysis of register contents

  1. View contents for param_1 to param_4

  1. View contents for param_5 toparam_7

  • The following instructions provides us a clue on how to view the contents of param_6 and param_7:

Recall why the value 0x860 is added in the calculation (refer to the explanation above)

5.5 Further analysis

  1. Breakpoint @0x421b28

v0, a0 -> gozila_cg (works)

  • But request to /tmBlock.cgi and /hndBlock.cgi does not work to properly load the value from the change_action parameter

  • But how to get param_6 to be tmBlock.cgi or hndBlock.cgi if it the request needs to be passed to /apply.cgi?

  • Does restarting gdb-multiarch on each program restart have any effects? (probs not)

...

Figure out what process or daemon is re-initializing the httpd process. Disable it temporarily as it may be causing issues with debugging

...

3.1 Testing payload

Other possible payloads

4. Analysis in Ghidra

4.1 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

4.2 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

...

5. 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