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