CVE Wednesday - CVE-2022-43973

What is CVE Wednesday?

CVE Wednesday is something I am starting today. When there are no other topics to write about for a week, I’m going to choose a recent CVE and give my analysis of it.

Here is the NVD Link for this Week’s CVE Wednesday:

https://nvd.nist.gov/vuln/detail/CVE-2022-43973

This NVD advisory contains links to three different youtube videos:

This 3-part, 4 hour long video series is an educational offering from Trellix. It looks like an in depth methodology overview for those that are new to vulnerability research. I didn’t have the time to watch four hours of youtube to suss out the details of this vulnerability. I instead want to offer my perspective on this vulnerability in written form so that it might be more readily accessible for those of us who cannot invest the time in watching the full length video series.

Credit

The discovery and reporting of this vulnerability was handled entirely by researchers at Trellix. I had nothing to do with the original research, the YouTube videos, or discovery for this vulnerability and full credit is due to the Trellix researchers.

Having said that, all of the screenshots and reverse engineering in this blog post are my own from seeing the CVE come up in my CVE feed. It is worth noting all analysis comes from static approaches - I do not have a copy of this model to actually test against dynamically.

Background

According to the latest release notes, the latest firmware version is Firmware 4.30.18 (build 6). This firmware was published Jan 22, 2016 and there have been no updates since. Interestingly enough, I looked through all 8 pages of linksys end of support documents and did not see WRT54GL listed in those documents as end of life (EOL). At the same time, I do not think it is likely this model will see any further firmware updates.

Aside

Always remember to update the TLS cert when it expires for your download server!

From the NVD Advisory:

An arbitrary code execution vulnerability exisits in Linksys WRT54GL Wireless-G Broadband Router with firmware <= 4.30.18.006. The Check_TSSI function within the httpd binary uses unvalidated user input in the construction of a system command. An authenticated attacker with administrator privileges can leverage this vulnerability over the network via a malicious POST request to /apply.cgi to execute arbitrary commands on the underlying Linux operating system as root.

What do we know?

Here is what we know about the vulnerability from NVD:

1) It affects the latest published firmware version (4.30.18 Build 6) which means there is no patch (yet?).

2) The vulnerability exists in the Check_TSSI function in the httpd binary.

3) The vulnerability is triggered via HTTP POST request to the /apply.cgi URL path.

Reverse Engineering

Here is the Check_TSSI function as output by Ghidra’s Decompiler.

undefined8 Check_TSSI(undefined8 _wl_atten_bb)
{
  FILE *pFVar1;
  char *pcVar2;
  uint __seconds;
  FILE *pFVar3;
  undefined8 _wl_atten_radio;
  undefined8 uVar4;
  undefined8 uVar5;
  longlong lVar6;
  char acStack_210 [80];
  char _wl_atten_cmd_str [80];
  char acStack_170 [80];
  char acStack_120 [80];
  char acStack_d0 [80];
  char acStack_80 [112];
  undefined4 local_10;
  
  local_10 = 0x1000dd50;
  pFVar1 = fopen("/dev/console","w");
  if (pFVar1 != (FILE *)0x0) {
    fprintf(pFVar1,"%s: init, Check_TSSI=[%s]\n","Check_TSSI",_wl_atten_bb);
    fclose(pFVar1);
  }
  _wl_atten_radio = get_cgi("WL_atten_radio");
  uVar4 = get_cgi("WL_atten_ctl");
  uVar5 = get_cgi("WL_delay");
  nvram_set("wl_atten_bb",_wl_atten_bb);
  nvram_set("wl_atten_radio",_wl_atten_radio);
  nvram_set("wl_atten_ctl",uVar4);
  nvram_set("wl_delay",uVar5);
  nvram_get("wl_atten_bb");
  nvram_get("wl_atten_radio");
  nvram_get("wl_atten_ctl");
  pcVar2 = (char *)nvram_get("wl_delay");
  if (pcVar2 == (char *)0x0) {
    pcVar2 = "";
  }
  __seconds = atoi(pcVar2);
  pFVar1 = fopen("/dev/console","w");
  if (pFVar1 != (FILE *)0x0) {
    fprintf(pFVar1,"%s: wl_atten_bb=[%s], wl_atten_radio=[%s], wl_atten_ctl=[%s]\n","Check_TSSI",0);
    fclose(pFVar1);
  }
  lVar6 = validate_xss(_wl_atten_radio);
  if ((lVar6 == 0) || (lVar6 = validate_xss(uVar4), lVar6 == 0)) {
    pFVar1 = fopen("/dev/console","w");
    if (pFVar1 != (FILE *)0x0) {
      fprintf(pFVar1,"%s: parameter error!\n","Check_TSSI");
      fclose(pFVar1);
    }
    return 0;
  }
  memset(_wl_atten_cmd_str,0,0x50);
  sprintf(_wl_atten_cmd_str,"wl atten %s %s %s",_wl_atten_bb,_wl_atten_radio);
  _execute_command(_wl_atten_cmd_str);
  pFVar1 = fopen("/dev/console","w");
  if (pFVar1 != (FILE *)0x0) {
    fprintf(pFVar1,"%s: Will delay %d seconds\n","Check_TSSI",__seconds);
    fclose(pFVar1);
  }
  if (__seconds != 0) {
    sleep(__seconds);
  }
  _execute_command("wl tssi > /tmp/get_tssi");
  memset(_wl_atten_cmd_str,0,0x50);
  memset(acStack_d0,0,0x50);
  memset(acStack_d0,0,0x50);
  pFVar1 = fopen("/tmp/get_tssi","r");
  if (pFVar1 == (FILE *)0x0) {
    pFVar3 = fopen("/dev/console","w");
    if (pFVar3 == (FILE *)0x0) goto LAB_004450e8;
    fprintf(pFVar3,"%s: \nFile error!\n","Check_TSSI");
  }
  else {
    fgets(_wl_atten_cmd_str,0x50,pFVar1);
    strcpy(acStack_210,_wl_atten_cmd_str);
    pFVar3 = fopen("/dev/console","w");
    if (pFVar3 == (FILE *)0x0) goto LAB_004450e8;
    strlen(_wl_atten_cmd_str);
    fprintf(pFVar3,"%s: \nGot:\n%s\nlen=%d\n","Check_TSSI",_wl_atten_cmd_str);
  }
  fclose(pFVar3);
LAB_004450e8:
  memset(acStack_170,0,0x50);
  pcVar2 = strtok(_wl_atten_cmd_str,",");
  strcpy(acStack_170,pcVar2);
  memset(acStack_120,0,0x50);
  pcVar2 = strtok(acStack_170,"CCK");
  strcpy(acStack_120,pcVar2);
  pcVar2 = strtok((char *)0x0,"CCK");
  strcpy(acStack_120,pcVar2);
  pFVar3 = fopen("/dev/console","w");
  if (pFVar3 != (FILE *)0x0) {
    fprintf(pFVar3,"%s: CCK:[%s]\n","Check_TSSI",acStack_120);
    fclose(pFVar3);
  }
  strcpy(acStack_d0,acStack_120);
  atoi(acStack_120);
  memset(acStack_170,0,0x50);
  pcVar2 = strtok(acStack_210,",");
  strcpy(acStack_170,pcVar2);
  pcVar2 = strtok((char *)0x0,",");
  strcpy(acStack_170,pcVar2);
  memset(acStack_120,0,0x50);
  pcVar2 = strtok(acStack_170,"OFDM");
  strcpy(acStack_120,pcVar2);
  pcVar2 = strtok((char *)0x0,"OFDM");
  strcpy(acStack_120,pcVar2);
  pFVar3 = fopen("/dev/console","w");
  if (pFVar3 != (FILE *)0x0) {
    fprintf(pFVar3,"%s: OFDM:[%s]\n","Check_TSSI",acStack_120);
    fclose(pFVar3);
  }
  strcpy(acStack_80,acStack_120);
  atoi(acStack_120);
  pFVar3 = fopen("/dev/console","w");
  if (pFVar3 != (FILE *)0x0) {
    fprintf(pFVar3,"%s: CCK=[%s](%d),OFDM=[%s](%d)\n","Check_TSSI",acStack_d0);
    fclose(pFVar3);
  }
  fclose(pFVar1);
  nvram_set("wl_cck",acStack_d0);
  nvram_set("wl_ofdm",acStack_80);
  nvram_set("wl_tssi_result",acStack_d0);
  nvram_commit();
  pFVar1 = fopen("/dev/console","w");
  if (pFVar1 != (FILE *)0x0) {
    fprintf(pFVar1,"%s: done\n","Check_TSSI");
    fclose(pFVar1);
  }
  return 1;
}

I want to call out the following two lines (labeling mine):

sprintf(_wl_atten_cmd_str,"wl atten %s %s %s",_wl_atten_bb,_wl_atten_radio);
_execute_command(_wl_atten_cmd_str);

The _execute_command function, which I labeled here, points to the function at memory address 0x00443150. This sub function of Check_TSSI is where the command injection vulnerability calls out to system, passing in the first argument the string to execute.

int _execute_command(char *cmd_str)
{
  FILE *__stream;
  int iVar1;
  
  __stream = fopen("/dev/console","w");
  if (__stream != (FILE *)0x0) {
    fprintf(__stream,"cmd: [%s]\n",cmd_str);
    fclose(__stream);
  }
  iVar1 = system(cmd_str);
  return iVar1;
}

The command being passed to this function in the lines of Check_TSSI called out above is wl atten $1 $2 $3. From the dd-wrt man pages:

Set the transmit attenuation for B ban. Args: bb radio txctl1. auto to revert to automatic control

Input Tracing

// from apply_cgi
// [...]
lVar12 = get_cgi("WL_atten_bb");
// [...]
Check_TSSI(lVar12);
// [...]

The Check_TSSI function is called from only one other function. The caller function is called apply_cgi. The first argument to Check_TSSI, which I have labeled _wl_atten_bb. When Check_TSSI is called from apply_cgi, it is passed the lVar12 value which is assigned by calling get_cgi. The get_cgi function is responsible for retrieving HTTP request parameters; most notably HTTP POST body parameters and HTTP GET query string parameters. It looks like the WL_atten_bb HTTP request parameter, which will be of type char *, is passed from apply_cgi to Check_TSSI and then from Check_TSSI to _execute_command after being concatenated into the OS command wl. This is the command injection vulnerability outlined in the CVE.

But Wait, There’s More!

Check out the variable I have labeled as _wl_atten_cmd_str. Ghidra notes that this local stack variable is of type char[80] and we even see this:

memset(_wl_atten_cmd_str,0,0x50);

So my question to you, dear reader, is what happens if the value of the character string _wl_atten_bb is 100 bytes in length?

The answer is most likely a stack-based buffer overflow, since the value of _wl_atten_bb is concatenated into the _wl_atten_cmd_str buffer using sprintf. There you go, there is my invaluable contribution to this research.

As an aside, the researchers responsible for discovering this CVE also discovered this CVE, which is a stack-based buffer-overflow in the Start_EPI function.

Home