身份验证绕过漏洞分析
liebian365 2024-11-20 18:24 32 浏览 0 评论
0x01 前言
最近Tenable 披露了Arcadyna 网络设备身份验证绕过漏洞,并且很多的厂商都采用产生漏洞的组件,由于Arcadyan 设备固件厂商并没有开源出来,在官网支持里面下载的文件是window和linux 下和设备连接的客户端软件,无法对漏洞点开展分析,这里我们使用同样受影响的华硕产品DSL-AC3100 的固件来进行设备分析。并且复现在网络设备中网络检测ping 功能的远程命令执行漏洞,从而开启设备telentd。
0x02 华硕DSL-AC3100 固件
我们从华硕的官网中下载固件。
设备名称: DSL-AC3100
固件版本: DSL-AC3100_v1.10.05_build503
0x03 身份验证绕过漏洞分析
提取固件包
从华硕的官网下载到固件包DSL-AC3100_v1.10.05_build503.w ,这是一个是用.w 为后缀的固件文件,使用binwalk 可以提取出来。根据漏洞信息,可以确定这是一个在http服务中存在的漏洞,可以确定到httpd 文件,本固件的httpd 文件在 /usr/sbin/httpd 中。
httpd 二进制文件分析
在ghidra 导入httpd 文件,自动对文件进行分析,识别文件的各种函数。
由于漏洞是身份认证绕过漏洞,因此首先要确定设备的身份验证相关的函数有哪些,在ghidra对httpd文件中的字符串进行搜寻,根据字符串 “check_auth” ,定位到函数 FUN_0001d0c0(),
undefined4 FUN_0001d0c0(int iParm1)
{
int iVar1;
undefined4 uVar2;
int iVar3;
undefined4 local_52c;
undefined4 local_528;
undefined4 local_524;
undefined4 uStack1312;
undefined4 local_51c;
char acStack1304 [1024];
char acStack280 [260];
memset(acStack280,0,0x100);
memset(acStack1304,0,0x400);
local_52c = 0;
local_528 = 0;
local_524 = 0;
uStack1312 = 0;
local_51c = 0;
iVar1 = FUN_00017df0();
if (iVar1 == -1) {
uVar2 = 1;
}
else {
iVar3 = mapi_ccfg_match_str(iVar1,"ARC_SYS_LogEnable",&DAT_00046b48);
iVar1 = mapi_ccfg_match_str(iVar1,"ARC_SYS_MPTEST",&DAT_00046b48);
if (iVar1 == 0) {
if (iVar3 != 0) {
iVar3 = 1;
}
if (iVar3 != 0) {
FUN_00017738(iParm1 + 0x76f0,&local_52c);
}
if (*(int *)(iParm1 + 0x774c) == 0) {
uVar2 = FUN_0001b6f4(iParm1 + 0x771e,*(undefined4 *)(iParm1 + 0x76ec));
FUN_0001b8c8(iParm1,uVar2);
}
iVar1 = FUN_0001ce8c(*(undefined4 *)(iParm1 + 0x774c),*(undefined4 *)(iParm1 + 0x76b0),
*(undefined4 *)(iParm1 + 0x76b4),*(undefined4 *)(iParm1 + 0x76b8),
*(undefined4 *)(iParm1 + 0x76bc),*(undefined4 *)(iParm1 + 0x76c0),
*(undefined4 *)(iParm1 + 0x76c4),*(undefined4 *)(iParm1 + 0x76c8),
*(undefined4 *)(iParm1 + 0x7b34));
if (iVar1 == 1) {
printf("[%s] %s login time out, reauth\n","check_auth",iParm1 + 0x76f0);
FUN_00039088(1);
snprintf(acStack1304,0x400,"Location: /relogin.htm\n\n");
}
else {
if (iVar1 == 2) {
printf("[%s] new user %s(%s) comes to login, check user and auth\n","check_auth",
iParm1 + 0x76f0,iParm1 + 0x4c);
snprintf(acStack1304,0x400,"Location: /relogin.htm\n\n");
}
else {
if (iVar1 == 0) {
printf("[%s] %s has already granted, pass\n","check_auth",iParm1 + 0x76f0);
return 0;
}
}
}
if (iVar3 != 0) {
snprintf(acStack280,0x100,"User from %s(%s) authentication fail.",&local_52c,iParm1 +0x76f0
);
append_to_file("/tmp/security_log.txt",acStack280);
}
FUN_00015338(iParm1,acStack1304);
uVar2 = 1;
}
else {
uVar2 = 0;
}
}
return uVar2;
}
根据函数代码的一些细节,可以看出这个函数检查认认证是否符合的功能函数,其中FUN_0001ce8c 函数的返回值iVar1,在函数中 iVar1 的值为2 时,说明是新用户登录,需要检查用户名和验证。iVar1 的值为 0 的时候,则显示验证通过。iVar1 的值为 1 的时候,则表示说明验证超时,并且重新返回到登录界面。
接下来,查看FUN_0001d0c0() 函数在FUN_0001d578() 中被引用。而FUN_0001d0c0() 函数就是漏洞的evaluate_access() 函数。
// evaluate_access()
undefined4 FUN_0001d578(undefined4 uParm1,undefined4 uParm2,int iParm3)
{
int iVar1;
undefined4 uVar2;
if (iParm3 == 0) {
return 0;
}
iVar1 = FUN_0001d2e0(iParm3);
if (iVar1 != 0) {
if (*(int *)(iParm3 + 0x76a8) != 0) {
return 0;
}
uVar2 = FUN_0001d0c0(iParm3);
return uVar2;
}
FUN_00014510(iParm3,0x193,"Unauthorized.");
return 1;
}
FUN_0001d578() 函数中的 FUN_0001d2e0() 是使用正则表达式来校验URL中的IP,端口是否符合规范。以及FUN_0001d0c0() 函数也在其中,因此这个函数是httpd 中来做身份验证的函数,也就是漏洞分析中的evaluate_access()。
接下来我们来查看调用evaluate_access() 函数的地方,真正的漏洞点在这个函数,我们来看漏洞点是如何绕过身份验证的。我们来到了FUN_00015058函数,这就是process_request 的函数。
void FUN_00015058(int iParm1)
{
undefined4 uVar1;
char *pcVar2;
char *__src;
int iVar3;
char *__dest;
iVar3 = iParm1 + 0xd5;
uVar1 = FUN_00016a84(iVar3,0xd);
*(undefined4 *)(iParm1 + 0x27f0) = uVar1;
*(undefined4 *)(iParm1 + 0x76a4) = 0xffffffff;
*(undefined4 *)(iParm1 + 0x76ac) = 0xffffffff;
__src = (char *)FUN_00016a84(iVar3,0x20);
*(int *)(iParm1 + 0x7b18) = iVar3;
pcVar2 = (char *)FUN_00016a84(__src,0x20);
uVar1 = FUN_00016a84(__src,0x3f);
*(undefined4 *)(iParm1 + 0x7b14) = uVar1;
__dest = (char *)(iParm1 + 0x7994);
strncpy(__dest,__src,0xff);
*(undefined *)(iParm1 + 0x7a93) = 0;
FUN_00016e3c(__dest);
printf("[%s] url=[%s], args=[%s], method=[%s]\n","process_request",__dest,
*(undefined4 *)(iParm1 + 0x7b14),*(undefined4 *)(iParm1 + 0x7b18));
iVar3 = FUN_00018c70(iParm1);
if (iVar3 < 0) {
return;
}
if (*pcVar2 == '\0') {
*(undefined4 *)(iParm1 + 0x7988) = 1;
}
else {
*(undefined4 *)(iParm1 + 0x7988) = 0;
iVar3 = FUN_00018cb8(iParm1);
if (iVar3 < 0) {
return;
}
iVar3 = strncasecmp(*(char **)(iParm1 + 0x7620),"multipart/form-data",0x13);
if ((((iVar3 != 0) && (*(char **)(iParm1 + 0x7b24) != (char *)0x0)) &&
(__src = strcasestr(*(char **)(iParm1 + 0x7b24),"FirmwareUpload"), __src == (char *)0x0))&&
(0 < (int)(*(int *)(iParm1 + 0x7984) + (uint)(64000 < *(uint *)(iParm1 + 0x7980))))) {
FUN_0000bef4(iParm1,*(undefined4 *)(iParm1 + 0xc));
FUN_00014510(iParm1,0x193,"The Content-length is extreme large!");
return;
}
}
uVar1 = FUN_0000deb0(__dest);
*(undefined4 *)(iParm1 + 0x76a8) = uVar1;
// evaluate_access()
if (((*(code **)(PTR_PTR_DAT_00054fac + 0x14) == (code *)0x0) ||
(iVar3 = (**(code **)(PTR_PTR_DAT_00054fac + 0x14))(iParm1), iVar3 != 2)) &&
((*(int *)(iParm1 + 0x76a8) != 0 || (iVar3 = FUN_0001d578(__dest,0,iParm1), iVar3 == 0)))) {
*(undefined4 *)(iParm1 + 0x798c) = 0;
__src = *(char **)(iParm1 + 0x7b18);
iVar3 = strcmp(__src,"HEAD");
if (iVar3 == 0) {
*(undefined4 *)(iParm1 + 0x798c) = 1;
if (*(int *)(iParm1 + 0x7988) == 0) {
FUN_0000eb98(iParm1);
}
else {
*(undefined4 *)(iParm1 + 0x798c) = 0;
FUN_00014510(iParm1,400,"Invalid HTTP/0.9 method.");
}
}
else {
iVar3 = strcmp(__src,"GET");
if (iVar3 == 0) {
FUN_0000eb98(iParm1);
}
else {
iVar3 = strcmp(__src,"POST");
if (iVar3 == 0) {
FUN_00014c30(iParm1);
}
else {
FUN_00014510(iParm1,400,"Invalid or unsupported method.");
}
}
}
}
return;
}
&& : 逻辑与,前后条件同时满足表达式为真;
|| : 逻辑与,前后条件只要有一个满足表达式为真。
如下面的代码,因为逻辑运算符&& 的优先级大于 || ,因此会先计算 && 的值。所以要先判断 iParm1 + 0x76a8 的值。如果值不为0 ,则接着执行 逻辑运算符的|| 的表达式。
((((code )(PTR_PTR_DAT_00054fac + 0x14) == (code )0x0) ||
(iVar3 = ((code )(PTR_PTR_DAT_00054fac + 0x14))(iParm1), iVar3 != 2)) &&
(((int )(iParm1 + 0x76a8) != 0 || (iVar3 = FUN_0001d578(__dest,0,iParm1), iVar3 == 0))))
根据 FUN_00015058() 函数的代码,可以看到 iParm1 + 0x76a8 的值是从 FUN_0000deb0(__dest) 获取到的,而 “_dest” 的值在前面可以看出来是用户请求的 URL。
如果 iParm1 + 0x76a8 不为0 ,那么就能跳过身份验证的函数evaluate_access(),来直接执行处理POST请求的FUN_00014c30函数。
接下来进入 FUN_0000deb0() 函数,来查看是怎么处理 URL
undefined4 FUN_0000deb0(char *pcParm1)
{
size_t __n;
int iVar1;
char *__s;
undefined **ppuVar2;
ppuVar2 = &PTR_s_/images/_00054f70;
__s = PTR_s_/images/_00054f70;
if (PTR_s_/images/_00054f70 == (undefined *)0x0) {
return 0;
}
do {
__n = strlen(__s);
iVar1 = strncasecmp(pcParm1,__s,__n);
if (iVar1 == 0) {
return 1;
}
ppuVar2 = ppuVar2 + 1;
__s = *ppuVar2;
} while (*ppuVar2 != (char *)0x0);
return 0;
}
函数会将 url 和 &PTRs/images/00054f70 字符串进行比较,直到符合为止,而 &PTR_s/images/_00054f70 的值是 “/images/” ,所以只需要请求的URL中带有 “/images/” 字符串,就可以绕过身份认证函数访问其他页面。
前面已经分析出来了身份验证绕过的漏洞点,但是并不能绕过验证访问任意界面,因为在访问的时候,需要正确的httoken值。接下来我们来分析设备的httoken 是怎么获取和生成的,在这个设备里,httoken 是设备的token值,并且访问设备的页面需要带有给定的httoken 值。根据漏洞披露来看,httoken 是在服务端进行生成,然后前端js 中进行解密,最终向服务器请求的时候,将httoken加入到请求数据中,但是漏洞披露并没有说明httoken 是那一段字符串生成的。。
我在httpd 的逆向工程中找到了生成httoken 的函数,
undefined4 FUN_00022520(int iParm1)
{
int iVar1;
undefined4 uVar2;
undefined *puVar3;
size_t sVar4;
char cStack120;
undefined auStack119 [107];
memset(&cStack120,0,0x65);
iVar1 = FUN_00017df0();
if (iVar1 == -1) {
uVar2 = 0;
}
else {
puVar3 = (undefined *)(iParm1 + 0x7994);
if (puVar3 == (undefined *)0x0) {
puVar3 = &DAT_0003d274;
}
uVar2 = FUN_000393e0(puVar3);
sprintf(&cStack120,"%lu",uVar2);
sVar4 = strlen(&cStack120);
FUN_00017e78(&cStack120,sVar4,auStack119 + sVar4,100 - sVar4);
uVar2 = so_printf(iParm1,
"<img title=spacersrc=\"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7%s\" border=0>"
,auStack119 + sVar4);
}
return uVar2;
接下来我们来分析FUN_00022520函数。在函数中我们可以看到最终生成了一个img 标签,并且src 的值是一段“data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7 + auStack119 + sVar4 ”字符串,而其中 auStack119 + sVar4 的值是从FUN_00017e78 函数中进行base64 以及其他的方式进行处理后的字符串,并且这段字符串就是httoken的值。
生成的img 标签会在设备的login.html 中html 代码中出现,如下图所示,
根据生成httoken函数拼接这段字符串的方式,使用脚本对把token 解密出来,可以确定如下图的“372646849” 为设备的 token。
ArcBase.decode(“image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7MTU2OTQzNDE0OA==”)
根据解密出来的信息,“;” 后面的字符串 “ 372646849” 就是设备解密后的httoken 值。
0x04 ping 命令注入+配置选项
这一部分,漏洞披露的相对来说比较详细,很多的网络设备中在ping网络诊断 这个功能中,出现过大量的历史漏洞,比如NetGear,D-Link等都出现过此类漏洞,因此关于ping 这一步部分命令拼接就不展开来讲述,但是本漏洞的不同点在于使用设备内部的配置选项ARC_TELNETD_ENABLE 来开启设备的telentd,这一点可以在以后的漏洞挖掘中遇到无法执行命令的时候,提供了不同的执行命令的方式。
我们来重点的关注一下ARC_TELNETD_ENABLE 这个配置。在文件 /sbin/arc_telnetd 文件中可以看到文件内容。文件可以获取ARC_TELNETD_ENABLE的值,当ARC_TELNETD_ENABLE的值为1的时候,设备会开启telnetd。
漏洞复现
0x05 总结
这个身份验证绕过漏洞产生的根本原因在于对请求的URL 验证不严格,本来”/image”是用来用户请求前端静态资源时,默认不需要通过验证,最终导致通过“/image/” 绕过登录。
另外在前一阵子的披露的NetGear DGN2200v1 设备中同样存在通过前端静态资源的路径,来绕过身份验证。并且我在其他的一款网络设备的固件中发现类似的问题。
【2021最新整理网络安全\渗透测试/安全学习(全套视频、大厂面经、精品手册。必备工具包)请关注我,私信回复“资料”获取
- 上一篇:Linux系统日志文件的分析与管理技巧
- 下一篇:2021最新Apache漏洞分析
相关推荐
- go语言也可以做gui,go-fltk让你做出c++级别的桌面应用
-
大家都知道go语言生态并没有什么好的gui开发框架,“能用”的一个手就能数的清,好用的就更是少之又少。今天为大家推荐一个go的gui库go-fltk。它是通过cgo调用了c++的fltk库,性能非常高...
- 旧电脑的首选系统:TinyCore!体积小+精简+速度极快,你敢安装吗
-
这几天老毛桃整理了几个微型Linux发行版,准备分享给大家。要知道可供我们日常使用的Linux发行版有很多,但其中的一些发行版经常会被大家忽视。其实这些微型Linux发行版是一种非常强大的创新:在一台...
- codeblocks和VS2019下的fltk使用中文
-
在fltk中用中文有点问题。英文是这样。中文就成这个样子了。我查了查资料,说用UTF-8编码就行了。edit->Fileencoding->UTF-8然后保存文件。看下下边的编码指示确...
- FLTK(Fast Light Toolkit)一个轻量级的跨平台Python GUI库
-
FLTK(FastLightToolkit)是一个轻量级的跨平台GUI库,特别适用于开发需要快速、高效且简单界面的应用程序。本文将介绍Python中的FLTK库,包括其特性、应用场景以及如何通过代...
- 中科院开源 RISC-V 处理器“香山”流片,已成功运行 Linux
-
IT之家1月29日消息,去年6月份,中科院大学教授、中科院计算所研究员包云岗,发布了开源高性能RISC-V处理器核心——香山。近日,包云岗在社交平台晒出图片,香山芯片已流片,回片后...
- Linux 5.13内核有望合并对苹果M1处理器支持的初步代码
-
预计Linux5.13将初步支持苹果SiliconM1处理器,不过完整的支持工作可能还需要几年时间才能完全完成。虽然Linux已经可以在苹果SiliconM1上运行,但这需要通过一系列的补丁才能...
- Ubuntu系统下COM口测试教程(ubuntu port)
-
1、在待测试的板上下载minicom,下载minicom有两种方法:方法一:在Ubuntu软件中心里面搜索下载方法二:按“Ctrl+Alt+T”打开终端,打开终端后输入“sudosu”回车;在下...
- 湖北嵌入式软件工程师培训怎么选,让自己脱颖而出
-
很多年轻人毕业即失业、面试总是不如意、薪酬不满意、在家躺平。“就业难”该如何应对,参加培训是否能改变自己的职业走向,在湖北,有哪些嵌入式软件工程师培训怎么选值得推荐?粤嵌科技在嵌入式培训领域有十几年经...
- 新阁上位机开发---10年工程师的Modbus总结
-
前言我算了一下,今年是我跟Modbus相识的第10年,从最开始的简单应用到协议了解,从协议开发到协议讲解,这个陪伴了10年的协议,它一直没变,变的只是我对它的理解和认识。我一直认为Modbus协议的存...
- 创建你的第一个可运行的嵌入式Linux系统-5
-
@ZHangZMo在MicrochipBuildroot中配置QT5选择Graphic配置文件增加QT5的配置修改根文件系统支持QT5修改output/target/etc/profile配置文件...
- 如何在Linux下给zigbee CC2530实现上位机
-
0、前言网友提问如下:粉丝提问项目框架汇总下这个网友的问题,其实就是实现一个网关程序,内容分为几块:下位机,通过串口与上位机相连;下位机要能够接收上位机下发的命令,并解析这些命令;下位机能够根据这些命...
- Python实现串口助手 - 03串口功能实现
-
串口调试助手是最核心的当然是串口数据收发与显示的功能,pzh-py-com借助的是pySerial库实现串口收发功能,今天痞子衡为大家介绍pySerial是如何在pzh-py-com发挥功能的。一、...
- 为什么选择UART(串口)作为调试接口,而不是I2C、SPI等其他接口
-
UART(通用异步收发传输器)通常被选作调试接口有以下几个原因:简单性:协议简单:UART的协议非常简单,只需设置波特率、数据位、停止位和校验位就可以进行通信。相比之下,I2C和SPI需要处理更多的通...
- 同一个类,不同代码,Qt 串口类QSerialPort 与各种外设通讯处理
-
串口通讯在各种外设通讯中是常见接口,因为各种嵌入式CPU中串口标配,工业控制中如果不够还通过各种串口芯片进行扩展。比如spi接口的W25Q128FV.对于软件而言,因为驱动接口固定,软件也相对好写,因...
- 嵌入式linux为什么可以通过PC上的串口去执行命令?
-
1、uboot(负责初始化基本硬bai件,如串口,网卡,usb口等,然du后引导系统zhi运行)2、linux系统(真正的操作系统)3、你的应用程序(基于操作系统的软件应用)当你开发板上电时,u...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- go语言也可以做gui,go-fltk让你做出c++级别的桌面应用
- 旧电脑的首选系统:TinyCore!体积小+精简+速度极快,你敢安装吗
- codeblocks和VS2019下的fltk使用中文
- FLTK(Fast Light Toolkit)一个轻量级的跨平台Python GUI库
- 中科院开源 RISC-V 处理器“香山”流片,已成功运行 Linux
- Linux 5.13内核有望合并对苹果M1处理器支持的初步代码
- Ubuntu系统下COM口测试教程(ubuntu port)
- 湖北嵌入式软件工程师培训怎么选,让自己脱颖而出
- 新阁上位机开发---10年工程师的Modbus总结
- 创建你的第一个可运行的嵌入式Linux系统-5
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)