某次打点时偶遇Yapi管理平台资产,发现其存在版本下 (< 1.12.0) 的nday漏洞,整个攻击过程感觉挺巧妙的,以此记录下对其白盒测试的分析和思考

环境搭建

YApi v1.10.2

1
docker compose up -d

漏洞复现

MongoDB的基础使用

MongoDB是一种非关系型数据库 (NoSQL) 是一类不采用传统表格结构来存储数据的数据库,它将数据以类似 JSON 的格式存储,每条记录就是一个文档。这种结构非常适合存储复杂、嵌套的数据

虽然这有点脱离这篇文章的重心,但我觉得还是可以简单了解一下这种数据库的特性

简单来说就是在一些游戏活动场景下,并不是每个用户都会参与活动,如果像关系型数据库那样,给每个用户都添加该活动名的字段,那有很多用户的该字段下一定是空的,这样就会造成大量空间浪费,况且游戏迭代非常频繁,每次都会修改表结构

ID Name 装备 春节活动 七夕活动 夏日活动
1 Hong
2 Ming
3 Bai

但非关系型数据库就不会存在这样的情况,因为每一个文档就是一行数据,那多个文档对应的就是一个集合

1
2
3
4
5
6
{
"ID": 1,
"Name": "Hong",
"装备": "剑",
"春节活动": "是"
}

show dbs;找到yapi数据库,我们可以看到token集合的具体内容

image-20250831154102449

对token的类型检验不严格导致nosql注入

在YApi中,每一个项目都有唯一标识的token,用户可以使用这个 token 值(加密后)来请求开放接口(无需登录),注意这不是用户登录的那个token;其项目的api接口就是需要这个项目token鉴权

我们关注server\controllers\base.js, line 62

image-20250831163956391

首先会从请求包中获取query和body参数,并且检查token是否为空,这里也是漏洞点,没有检查token的类型 (修复版本中在这里强制检查要求token是字符串类型),接着通过parseToken()函数来解析token,这里我们跟进到server\utils\token.js

可以看到采用了aes192的加解密算法,并且如果没有在config.js中配置盐,则会使用const defaultSalt = 'abcde';

image-20250831170208124

这里我们可以看到token的具体结构,uid + '|' + token,具体来说这里是你的项目token

返回到server\controllers\base.js, line 87,跟进getProjectIdByToken(),这里会使用MongoDB数据库中的findone方法查询project_id

image-20250831170513398

整个逻辑分析完了,接下来就是在刚才的漏洞点进行json格式的注入

首先是对project的token注入,这里我们要找到一些可用于接收application/json参数类型的接口,/api/interface/up就是一个很好的选择,这里使用正则表达式语法

image-20250831172917124

1
{"id": -1, "token": {"$regex": "^1"}}

vulhub中提供的Poc可以看到脚本原理也是如此

image-20250831181333659

当匹配时image-20250831181354832

不匹配时image-20250831181410791

形成了布尔盲注

之后就是遍历UID去构造encrypted token,不过这里前提在于使用了defaultSalt

image-20250831181550411

image-20250831182705498

上传并运行vm2逃逸脚本造成RCE

有了加密token就可以干很多事情了,当然最直接严重的就是命令执行,其中请求配置可以上传自定义 js 脚本,其中会用到VM2沙箱,在未登录的情况下可以使用加密token通过/api/project/up路由上传

image-20250831185104025

image-20250831185302437

接着通过/api/open/run_auto_test?token=&id=project_id&mode=json运行

image-20250831185748215

至于VM2沙箱逃逸原理以前分析过一个Proxy递归栈溢出异常处理来实现逃逸的AWDP代码,但是逃逸方法种类繁多,以后再统一写篇文章分析一下吧