搜集相关信息
搜了一大圈只能找到同一句漏洞描述,随便摘了一个
https://www.cvedetails.com/cve/CVE-2021-29083/
Improper neutralization of special elements used in an OS command in SYNO.Core.Network.PPPoE in Synology DiskStation Manager (DSM) before 6.2.3-25426-3 allows remote authenticated users to execute arbitrary code via realname parameter. Publish Date : 2021-04-01 Last Update Date : 2021-04-07
可以得到以下信息
- 漏洞存在于6.2.3-25426-3之前的版本
- 这是一个命令注入漏洞
- 可能与”SYNO.Core.Network.PPPoE”字段有关
- 可能与”realname”字段有关
固件下载
在官网下载存在漏洞的固件版本以及漏洞修复后的固件版本
找到漏洞文件
binwalk解压一下
binwalk -Me DSM_DS918+_25426.pat
cd _DSM_DS918+_25426.pat.extracted
查找可能存在漏洞的文件
find . -type f | xargs strings -f | grep "SYNO.Core.Network.PPPoE"
找到了./usr/syno/synoman/webapi/SYNO.Core.Network.lib
是json格式的配置文件,在CyberChef上美化一下格式
找到”SYNO.Core.Network.PPPoE”字段
"SYNO.Core.Network.PPPoE": {
"allowUser": [
"admin.local",
"admin.domain",
"admin.ldap"
],
"appPriv": "",
"authLevel": 1,
"lib": "lib/libwebapi-PPPoE.so",
"maxVersion": 1,
"methods": {
"1": [
{
"get": {
"allowDemo": true,
"grantByDefault": true
}
},
{
"set": {
"grantByDefault": true
}
},
{
"list": {
"allowDemo": true,
"grantByDefault": true
}
},
{
"connect": {
"grantByDefault": true
}
},
{
"disconnect": {
"grantByDefault": true
}
}
]
},
"minVersion": 1,
"priority": -10
}
其配置文件的格式参考cq674350529师傅的ppt,api有对应的so文件,在so文件中不同的virsion(例如1、2)和methods(例如get、set)有对应的handle函数,查找对应的so文件
find . | grep "lib/libwebapi-PPPoE.so"
找到./usr/syno/synoman/webapi/lib/libwebapi-PPPoE.so
文件,应该是存在漏洞的文件
然而不知道是否我的理解有误,在文件系统中全局搜索”realname”字符串却没有找到有用的信息
分析漏洞成因
ida打开,搜索”SYNO.Core.Network.PPPoE”字符串
通过字符串的交叉引用找到对应api的函数
其结构体为
struct api{
char * apiname;
size_t virsion;
char * method;
func * handler;
}
lib文件中不同的api、viersion、method都对应了handler文件,但是不知道哪一个才是真正存在漏洞的函数
可以安装bindiff来比较补丁前后的函数变化
bindiff安装参考BinDiff安装使用教程 - 诸子流 - 博客园 (cnblogs.com)
在用ida分析后将两个i64文件进行differ
但是经过分析补丁前后两个文件差别不大,只是在一些函数上加上了canary检测
不能直观地从diff中找到漏洞修补点,只能先分析漏洞文件
根据漏洞描述,存在漏洞的api应该是”SYNO.Core.Network.PPPoE”,所以漏洞应该在get、set、list、connect四个method对应的函数中;
能够造成命令注入漏洞,set函数的可疑性较高,connect次之,毕竟get和list几乎没有输入的字段,先着重分析set对应的函数(其实漏洞就在该函数中)
进入InterfaceHandler<syno::network::PPPoEInterface>::Set
函数
整体逻辑是先判断一些字段参数是否存在,然后是如下的主要部分
按照函数逻辑,应该是判断如果某两个字段一样的话调用SYNO::APIResponse::SetSuccess
函数来返回true的响应,否则就会调用InterfaceHandler<syno::network::PPPoEInterface>::Set
(该函数虽然与上面的set函数名称相同,但是参数不相同,是该类中的重载函数)
进入这一个set函数,主要逻辑如下
这里全是指针的方式调用的,可能是通过虚表调用虚函数,经过动态调试分析可以发现此处调用的函数是libwebapi-Network-Share.so文件中的syno::network::PPPoEInterface::SetData
函数,进入该函数继续分析
发现此处又调用了一个函数指针,动态调试可以确定函数为syno::network::PPPoEInterface::LoadData
不过并不很重要,主要是看下面的逻辑,后面调用了syno::network::PPPoEInterface::Check
和syno::network::PPPoEInterface::Apply
函数,发现十分眼熟,看了一下陈前师傅的ppt
是同一个调用链,于是顺着ppt的思路在syno::network::PPPoEInterface::Apply
函数中找到调用PPPoEConfigSet
函数的指令,在文件系统中搜索”PPPoEConfigSet”字符串
找到实现其功能的so文件:libsynonetsdk.so.6,分析PPPoEConfigSet
函数
其主要逻辑与ppt中描述的一致,应该是同一个漏洞成因,只对”USER”字段的特殊字符进行了过滤而忽略了”MTU”字段,只不过触发漏洞的方式有所不同,ppt的漏洞中从EZ-internet套件调用漏洞函数,CVE-2021-29083从 “SYNO.Core.Network.PPPoE” api调用漏洞函数
该函数会按照”key=value”的格式保存数据到 /etc/ppp/pppoe.conf文件中,而在 /usr/sbin/pppoe-start脚本中将该conf文件作为配置文件,并调用了 /usr/sbin/pppoe-connect
只要将shell命令注入到 /etc/ppp/pppoe.conf文件中,在PPPoE服务连接的时候就可以命令注入
而 /usr/sbin/pppoe-connect 从配置文件中提取”USER”和”MTU”字段作为参数,可以通过插入” ` “字符进行命令注入
搭建实验环境
参考之前的发过的CVE-2021-27647漏洞复现搭建群晖虚拟机进行复现
触发漏洞
找到与pppoe有关的服务抓包分析
可以找到 控制面板->网络->网络界面 中有PPPoE选项
右键编辑,可以用burp抓到请求报文,报文的格式与逆向so文件中需要的格式十分符合
随意输入一些账号密码后确定,经过分析发现单击确定后会先发送一个获取公钥的请求获取token、公钥等数据
接收到公钥后通过前端将所有参数按照一定格式加密后,再将加密后的密文发送到后端
之前逆向so文件可以知道此处会被插入命令的字段只有用户账号和MTU值两项,但是用户账号经过了字符过滤,无法进行命令注入,而MTU值又在前端限制了只能输入数字
所以若要触发命令必须向MTU值的字段插入命令,想到了三种方案:
- 修改js代码,让只能输入数字的限制失效
- 对js进行逆向分析,找到加密方式,再伪造前端请求发包
- 对js进行调试,在加密前通过js控制台将MTU的值修改为需要注入的命令
然而由于前端太菜,既找不到限制只能输入数字的js代码,又逆向不出加密方法,并且复现漏洞的目的应该是学习了解设备的攻击面,所以采用最简单的方法3,等之后有时间了再慢慢研究一下其加密逻辑
学习一些js调试的基本操作,找到一篇介绍使用chrome调试的文章:js逆向技巧分享
但是chrome设置代理似乎比较麻烦,参考该教程用火狐浏览器调试也是大同小异
打开PPPoE配置界面,f12在前端代码中搜索”确定”
找到对应的html标签,右键单击->打断点于…->节点删除时
接着修改一下配置单击确定就可以停在断点处了,在调试器右侧的调用堆栈中单击”展开行”选项即可看到所有函数调用堆栈
在调用堆栈里找到onEncryptRequestAPI
函数
美化一下源代码
该函数就是对配置信息进行加密的函数,再加密后将密文发送到后端,所以在这个函数开始处设置一个断点
恢复运行后再重新修改配置跑一遍js代码,并一直单步运行到d = SYNO.Encryption.EncryptParam
函数,步入该函数,查看g变量
可以发现 g.configs 的内容正是我们配置的内容,在控制台修改这个变量的值
g.configs= "[{\"ifname\":\"pppoe\",\"real_ifname\":\"eth0\",\"username\":\"aaaaa2332a3\",\"is_default_gateway\":true,\"mtu_config\":\"`reboot`\"}]"
修改成功后恢复运行,但是发现系统并没有重新启动,应该是有些地方出现了问题
ssh连上去看一下/etc/ppp/pppoe.conf文件的内容
可以看到此时配置文件中MTU只有”`reboot”字符串,也就是我们修改的字符串并没有完整的写入配置文件中
再结合陈前师傅的ppt可以找到设置配置文件的处理逻辑是syno::network::PPPoEInterface::Check
函数对用户传入的参数检查后复制到该类的成员变量中,再由syno::network::PPPoEInterface::Apply
函数将成员变量中的配置信息保存到配置文件
所以上面执行reboot失败的原因是复制MTU参数时的长度存在限制,只能输入7字节的字符串
只能输入一些短小的命令进行概念验证,在控制台输入
g.configs= "[{\"ifname\":\"pppoe\",\"real_ifname\":\"eth0\",\"username\":\"aaaaa2332a3\",\"is_default_gateway\":true,\"mtu_config\":\"`id>aa`\"}]"
打开burp代理后再发送可以抓取poc报文
在发送报文后再发送连接报文就成功执行命令了
执行成功,根目录下多了一个aa文件
参考资料