#前文回顾
我们跟随cgi执行流程来到了label 47,然后分析了cJSON_Parse和websGetVar函数的功能
接下来我们继续分析cgi是如何在这里分发路由器web界面众多按钮对应的众多功能的
#正文
首先我们看这部分结构,通过strstr函数来判断v27中是否包含get字样
这里的v27来自于json数据中的topicurl字段,即执行的函数名
如果要执行的函数名中带有get字样,则会进入一个while循环,不断的在一个函数表结构中去对比函数名和函数表中的字符串,如果相等则跳转到函数表中对应的函数地址去
接下来我们道函数表中查看一下结构如何:
可以看到函数表中充斥着这种一块一块的数据,通过之前的分析我们得到的结论是程序会在一个while循环中不停的把topicurl参数和函数表中的函数名做比较,如果比较成功则选择到对应的函数地址并跳转过去,所以函数表中应该存有两个东西,一个是函数名一个是函数地址,只不过IDA中并没有恢复出来,所以我们看到的是单字节的数据,可以通过快捷键d手工的改变一下数据类型来恢复出结构
恢复完的效果如上,而类似的函数表结构共有三个,分别代表了路由器函数中的三大类:
-
get类函数族
-
set类函数族
-
del类函数族以及杂项函数
首先来分析一下get类函数的运行机制,从最基本的函数getInitCfg入手,首先我们要清楚这个函数在什么情况下会被调用:
当我们进入到路由器的基本设置的首页时,会发现这里显示了很多路由器的基本设置信息,比如固件版本,运行时间,MAC地址等等,那么这些信息是如何从固件中传到web界面来的呢?我们抓个包试试
POST /cgi-bin/cstecgi.cgi HTTP/1.1
Host: 192.168.0.1
Content-Length: 25
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://192.168.0.1
Referer: http://192.168.0.1/basic/index.html?time=1660284636980
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: SESSION_ID=2:1591951493:2
Connection: close
{"topicurl":"getInitCfg"}
可以看到,程序通过POST请求,向cstecgi程序提交了一个请求,其中函数名参数topicurl为getInitCfg,再来看看响应包中有什么信息:
可以看懂啊响应包中有一个json格式的数据,其中包含了大量的路由器基本信息,也就是说我们在web界面看到的信息,经历了这样一个流程:
用户点击首页,web界面设置好所用方法以及正确的参数,构造请求包,发给cgi程序,cgi程序接收到请求之后,通过topicurl,确定要执行的函数,执行对应的函数后,构造响应包产生响应,给前端提供所需信息。
接下来我们来分析getInitCfg是如何执行的:
首先程序调用了cJSON_CreateObject创建了一个json对象,这个函数具体在哪实现如何实现,上篇文章已经说过了如何看,有兴趣的话可以看看。
然后初始化了几个字符串变量,接下来的几乎所有字符串都通过它来处理
然后通过apmib_get函数获取所需数据到指定变量中
接下来通过cJSON_CreateString函数和cJSON_AddItemToObject函数将装有指定数据的字符串添加到json对象中
cJSON_AddItemToObject(Object, "model", String)
可以通过参数很明显的看出来,第一个参数应该是要添加到哪个json对象中,第二个参数则是添加的item的key,第三个参数则是item的value
一个变量用过之后可能会通过memset函数重复使用
getInitCfg函数不断的重复上述过程,将所有所需数据形成一个json对象,这就是我们在响应包里看到的这个东西
分析到这里其实我们可以发现,所有get族的函数,其实都是在获取信息,那么意味着什么?我们可操作的空间很小,即用户的数据基本无法对这些get族的函数产生什么影响,所以漏洞一般来说很少发生在get族的函数中,接下来我们看看set族的函数又是如何做的
这里选择一个短小精悍的修改密码功能:
通过抓包来分析一下请求包结构:
POST /cgi-bin/cstecgi.cgi HTTP/1.1
Host: 192.168.0.1
Content-Length: 80
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://192.168.0.1
Referer: http://192.168.0.1/advance/changepwd.html?time=1660289682741
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: SESSION_ID=2:1591956512:2
Connection: close
{
"admuser":"admin",
"admpass":"bbb",
"origPass":"aaa",
"topicurl":"setPasswordCfg"
}
这里发现,其实请求包只有用户名,原密码,新密码以及执行函数名,并没有前端页面中所谓的确认密码,所以可以看出来这里的确认密码部分是由前端来完成的。
然后来看看setPasswordCfg函数长什么样:
可以看到程序首先通过websGetVar函数获取请求包json数据中的各个变量,然后通过apmib_get函数获取存储在后端的密码,放到v9变量里,然后比较一下用户在前端输入的origPass,是否能和后端存储的密码匹配上,如果不匹配说明用户输入了错误的原密码,所以跳到else的逻辑中,创建一个json对象,添加一个item,其key为success,value是false,这里我们可以来试一下是不是我们分析的这样,首先随便输入个原密码和新密码,然后获取响应包:
和我们分析的结果一致,然后再来看看当输入的原密码和存在本地的密码匹配上之后,程序做了什么:
程序通过apmib_set函数,将用户名和新的密码写入到本地,然后调用了一个函数,点进去可以看到实际上调用的是apmib_update:
到这里已经成功更新了admin用户的密码,接下来是构造响应包:
所以可以看到,set族的函数是受到用户输入影响比较大的,同时也意味着是比较容易出现各类溢出以及命令注入等漏洞的,笔者从买来这款路由器到现在两周左右的时间,已经上报了十余个漏洞,涵盖溢出,信息泄露,命令注入等等方面,基本上都是出现在set族函数中。
del族的函数以及杂类的函数就不再展开分析了,除了具体的处理逻辑之外整体上的分析方法和上面两种区别不太大,留给读者自行尝试,下一篇文章将拿出一部分我上报的漏洞,从固件分析入门走向漏洞挖掘实战。