本期参考文档:
漏洞验证-PaperCut NG 认证绕过 (CVE-2023-27351) – Domren – 博客园
CVE-2021-20090(华硕DSL-AC3100)身份验证绕过漏洞分析 – IOTsec-Zone
CVE-2024-5035 TP-Link Archer C5400X RCE 漏洞分析
这里我也是成功下载到了有漏洞版本的固件,如果直接下载的链接有权限问题,可以访问Download TP-Link Archer C5400X V1 Router Firmware 1.1.6 Build 20231010 EU for OS Independent – Softpedia
参考:CVE-2024-5035 TP-Link Archer C5400X RCE 漏洞分析
1.执行 /etc/init.d/wireless 初始化脚本。该脚本执行命令 /sbin/wifi init。

2.wifi脚本导入文件 /lib/wifi/tplink_brcm.sh 并执行该文件中的 wifi_init 函数。



3.在 /lib/wifi/tplink_brcm.sh 中,发生以下函数调用树:wifi_init -> wifi_start -> wifi_start_calibrate -> wifi_start_rftest -> rftest。





4./lib/wifi/tplink_brcm.sh 中的 rftest 函数启动 /usr/sbin/rftest。

其实这里我真没太看懂为什么启动的是/usr/sbin/rftest


这里是绑定端口的代码,询问AI就可以知道,这里实际监听的是8888端口

然后就是漏洞链,这里原文写的有点问题,我们接下来来分析一下:
首先说简单的,如果命令格式为nvram get xxx,那么会执行如下链路,利用popen函数的命令拼接进行RCE,这个逻辑比较简单,不多说了

然后是如果命令格式为wl xxx,这里的逻辑就比较麻烦
if ( !strncmp(v16, "wl", 2u) )
{
wl_args = v16 + 2;
strcpy(s, "wl");
if ( strstr(wl_args, "-a") || strstr(wl_args, "-i") )
{
v40 = 3;
strcpy(&s[25], "-i");
v22 = strstr(wl_args, "eth");
wl_args = v22 + 4;
strncpy(&s[50], v22, 4u);
}
else
{
v40 = 1;
}
v23 = *wl_args;
if ( *wl_args )
{
do
{
v25 = v23 > ' ';
if ( v23 != ' ' )
v25 = v23 - 9 > 1;
if ( v25 )
break;
v24 = *++wl_args;
v23 = v24;
}
while ( v24 );
}
strcpy(v50, wl_args);
v26 = strtok(v50, " ");
if ( v26 )
{
v27 = v40;
v28 = &s[25 * v40];
do
{
++v27;
strcpy(v28, v26);
v28 += 50;
v26 = strtok(0, " ");
}
while ( v26 );
v40 = v27;
}
v29 = s;
v30 = &v43;
do
{
v30[1] = v29;
++v30;
v29 += 25;
}
while ( &v45 != v30 );
v31 = "counters";
v42 = v6;
v32 = 0;
for ( i = 8; ; i = strlen((&off_12CD0)[2 * v32]) )
{
if ( !strncmp(v31, wl_args, i) )
{
v35 = &(&off_12CD0)[2 * v32];
v6 = v42;
(v35[1])(v40, command, v41);
goto LABEL_38;
}
++v32;
v31 = (&off_12CD0)[2 * v32];
v34 = v32 == 'd';
if ( v32 != 'd' )
v34 = v31 == 0;
if ( v34 )
break;
}
v6 = v42;
sub_1133C(v40, command, v41);
}
把这段逻辑喂给AI,答复是这样的:
这段代码实现了一个针对
wl命令的简单命令行解析器和调度器。它解析用户输入,根据子命令(如counters,dump)调用相应的处理函数。下面是其逻辑的详细分析:
🧩 代码逻辑分析
- 命令识别
if ( !strncmp(v16, "wl", 2u) ):首先检查输入字符串v16是否以 “wl” 开头。如果不是,则跳过整个代码块。- 参数预处理
wl_args = v16 + 2;:将wl_args指针指向 “wl” 后面的参数字符串部分。strcpy(s, "wl");:初始化一个名为s的缓冲区,用于构建最终的命令数组,首先放入 “wl”。- 特殊标志处理:代码检查参数中是否包含
-a或-i标志。
- 如果存在,它会寻找
eth字符串(例如eth0),并将-i标志和找到的接口名(如eth0)预先添加到s缓冲区中。变量v40作为参数计数器,此时被设为 3(即 “wl”, “-i”, “eth0″)。- 如果不存在,参数计数器
v40被设为 1(只有 “wl”)。- 参数分割与整理
- 跳过前导空白:一个
do-while循环用于跳过wl_args开头的所有空白字符(空格、制表符等)。strcpy(v50, wl_args);:将跳过空白后的参数字符串复制到一个临时缓冲区v50。strtok分词:使用strtok(v50, " ")以空格为分隔符,将参数字符串分割成一个个独立的词(token)。- 构建参数数组:通过一个
do-while循环,将strtok分割出的每个词依次复制到s缓冲区中。s缓冲区被设计成一个二维数组(每行25字节),用于存储最终的argv风格参数列表。v40变量在此过程中持续计数,记录最终的参数总个数。- 构建
argv结构
- 一个
do-while循环将s缓冲区中每个参数的地址填充到另一个数组&v43中。这实际上是在构建一个char *argv[]结构的指针数组,方便后续函数调用。- 命令分发(核心逻辑)
v31 = "counters";:初始化要查找的子命令为 “counters”。- 代码进入一个
for循环,该循环遍历一个定义在.rodata段的函数指针数组off_12CD0。这个数组的结构是{命令字符串, 处理函数地址}的交替序列。if ( !strncmp(v31, wl_args, i) ):在循环中,它将当前要查找的命令(初始为 “counters”)与用户输入的参数wl_args进行比较。- 匹配成功:如果找到匹配的命令(例如 “counters”),代码会获取对应的函数指针
v35[1](例如sub_11E1C),然后调用该函数(v35[1])(v40, command, v41);,并将之前构建好的参数传入。调用后通过goto LABEL_38跳出。- 循环继续:如果未匹配,
v32递增,v31被更新为数组中的下一个命令字符串(例如 “pavars”),然后继续循环。- 默认处理:如果循环遍历完所有预定义命令仍未找到匹配项,则调用一个默认处理函数
sub_1133C。📋 总结
这段代码的核心功能是:
- 接收一个以 “wl” 开头的命令字符串。
- 解析其后的参数,处理一些特殊情况(如
-i eth0)。- 将参数分割并构造成一个标准的
argv数组。- 在一个预定义的命令表中查找匹配的子命令。
- 根据查找结果,调用相应的处理函数或默认函数。
这是一个非常典型的命令行工具的实现模式,通过一个调度表将字符串命令映射到具体的函数实现上。

可以看到,如果wl后面的命令参数能够匹配如上字符串,确实可以调用不同的处理函数

而且也可以看到,popen函数也确实在这几个函数中被调用,这几个函数的逻辑基本一样,我们随便来看一个就好

但问题是,原文中的命令执行截图,很明显没有跟这几个合法参数,所以这里的RCE肯定不是popen函数调用的,所以我们注意到,AI的回复中,有一个默认处理函数 sub_1133C,这个函数才是用来处理wl后面没跟合法参数的情况

跟进到sub_1133C中,可以发现调用了sub_11138

继续跟进sub_11138

发现这里调用了execve命令,拼接了[“sh”, “-c”, s, 0]这样一个命令,并且从printf输出的调试信息可知,真正的注入点确实在这个函数,所以实际上wl命令和nvram命令最终的RCE其实是完全不同的两个函数导致的,并非都是popen函数导致的,至此漏洞也是分析结束了
CVE-2020-15633 DIR-878认证绕过漏洞
DIR-878需要解密,下载地址在这,网上有很多教程,可以去找中间件解密,但是找起来挺麻烦的,这里找到了大佬的github,使用他的脚本可以直接解密
参考:CVE-2020-15633 dir-878中strstr导致的登录认证绕过 | OneShell
D-Link路由器HNAP协议系列漏洞披露 – 奇安信技术研究院
查看lighttpd.conf可以看到,无论是HNAP1还是.fcgi结尾的,都会由/bin/proc.cgi处理,所以核心要分析的是prog.cgi

FirmAE模拟不起来,推测有这么几个原因

路径硬编码有问题,多了个“/”,patch之后确实ok了

缺少文件夹,需要手动创建

默认开启ssl,但是没有秘钥
修改上述问题之后,程序没有报错,但是也没有开启监听,不知道为什么,直接就跑死了

由于这里重点研究漏洞成因,并且已经有了对823G的研究经验,这个件就不模拟了,我们还是来分析一下后端代码的认证逻辑吧

在websSecurityHandler函数里会调用security_router函数,我们跟进分析


首先,会判断是否是登录逻辑,如果我们访问的是/HNAP1/,就不需要执行如上逻辑

然后,程序会进行一下session验证,会判断当前携带的session是否合法,是否在有效期内, 返回0表示认证成功,作为攻击者,我们自然是认证失败的,函数返回1,会接着判断是否是特定的url


既然也不是,那么程序就会进入sub_423ECC函数

这个函数会执行如下操作:循环遍历访问白名单中的action,并将其拼接成为完整的soapaction格式

然后,针对每一个action,都调用strstr函数查找,看访问url中是否携带了对应的路径,以GetCAPTCHAsetting为例,由于他在”/HNAP1/”的前面,所以在循环中,会优先尝试匹配GetCAPTCHAsetting,此时,如果我们构造url=/HNAP1/?GetCAPTCHAsetting,此时,就会进入第19行的if判断,该语句判断匹配到的字符串是否为”/HNAP1/”或者请求方式是否为POST,如果有一项不满足,就会直接放行,函数返回0
这样,在接下的handle句柄中,由于访问路径仍然是/HNAP1/,GetCAPTCHAsetting只是作为参数携带,就会成功匹配到websFormHandler函数,从而执行/HNAP1/的访问逻辑

而这里面的逻辑基本就是根据SOAPAction查找对应处理函数了

CVE-2021-35973 NETGEAR WAC104认证绕过漏洞
CVE-2021-35973 netgear wac104登录认证绕过再分析 | OneShell
奇安信攻防社区-CVE-2021-35973-IOT认证绕过分析
固件版本:V1.0.4.13
对main函数分析一通,我们可以定位到core函数,这个函数是程序在接受请求之后,fork出来的子进程在执行

进入core函数之后,程序会先读取请求头的一些字段

然后,在经过1000多行代码乱七八糟的逻辑之后,在代码的最后,有一个check_auth函数

我们跟进这个函数

这个函数一进入,就会判断当前是否登录以及访问的文件是否需要认证,need_auth变量默认为1,只有访问极少数特定文件会置0

如果我们访问一些普通接口,自然是需要鉴权的,就进入了HandleAdminLogin函数

这里有一个tag,如果tag为1,就可以绕过后续的BASIC鉴权逻辑

所以如何让tag为1呢?
1.如何有SOAPAction字段,且携带了“urn:NETGEAR-ROUTER:service:”,tag置1

2.如果请求文件携带setupwizard.cgi字符串,tag置1,但是后续会执行到exit函数,所以这里不能利用,看文章推测这里是跟初始化有关的操作

3.如果携带currentsetting.htm,tag置1

这也正是我们触发认证绕过的核心,如果构造url=/xxxx%00currentsetting.htm,则strstr成功匹配,此时tag置1,于是绕过了HandleAdminLogin函数

在设置完tag之后,程序才会执行urldecode的操作,把%00还原成\x00,所以后续我们访问的路径就是%00前面的路径,这个点也是上面几篇文章没提到的,我就一直在想到底是哪里进行了urldecode的操作
(这个urldecode函数我逆了一整天才找到,古法逆向领军人物了属于是,哎,驾驭不好AI真的很难在这个世界上存活)
至此整个漏洞的链路基本清晰了:
访问/xxxx%00currentsetting.htm路径,strstr成功匹配到关键字符串currentsetting.htm,随后进行urldecode,/xxxx%00currentsetting.htm被还原成了/xxxx\x00currentsetting.htm,在后续触发cgi的调用

cgi的调用也在check_auth函数里,再调用cgi之前,会先执行一个set_env函数,把cgi需要的环境变量全都设置好


在V1.0.4.15版本的固件中,可以看到已经修复了这个问题,增添了对%00的检验


HandleAdminLogin也没有tag了,直接对所有的都进行鉴权,除非能绕过上层的if判断


CVE-2019-17137——Netgear R6220 认证绕过漏洞也是这个问题,一模一样
接着还有一个问题,在setup.cgi中

如果请求方法是POST,系统就会尝试把sp字段拼接到/tmp/SessionFile构建路径,然后尝试读取文件

如果没有这个文件,就会返回0,那么如果我们把id输入为0,就可以绕过第55行的if判断,直接goto label21

直接进到了路由分发的逻辑,由此绕过了cgi中的身份校验