ni.zsh: npmインストール時のサプライチェーン攻撃を検知する機能を追加
npm/yarn/pnpm/bunを同じコマンドで扱えるni.zshに、npmで配布されているマルウェアを間違ってインストールするのを防ぐ機能を追加しました。
ni.zshについては、次の記事を参照してください。
npmパッケージのマルウェア
npmパッケージとしてマルウェアをpublishして開発者を狙うサプライチェーン攻撃が最近多くなっています。
たとえば、次の記事ではemails-helper
というもっともらしいパッケージ名でマルウェアが配布されていました。
他にもtyposquattingという有名なパッケージ名のtypoを狙ってマルウェアを配布しているケースも多いです。
npmは利用者が多いため、こういったマルウェアのパッケージが月に数百件とかの単位で増えています。
最近では、1つのパッケージではなく、複数のパッケージを組み合わせて発動するようなマルウェアもあります。 これは、検知の回避などが目的だと思いますが、攻撃がどんどん複雑になってきています。
現状のマルウェアの多くはローカルの環境変数やAPIキー、秘密鍵などを盗むようなものが多いです。 そのため、これらを空振りさせることも一つの対策で、それについては次の記事で書きました。
こういったマルウェアパッケージはpostinstall
などのインストール時にスクリプトを実行する仕組みを使って、攻撃を行うことが多いです。(普通にrequire
された時に動くものがあります)
そのため、--ignore-scripts
などでpostinstall
を実行しないようにすることで、一定の攻撃を防ぐことができます。
ただ、--ignore-scripts
はまだデフォルトにするのが難しい部分があります。
- Make npm install scripts opt-in by tolmasky · Pull Request #488 · npm/rfcs
- Package Distributions · Issue #642 · npm/statusboard
そもそもとして、こういったマルウェアっぽいパッケージをインストールしないようにするのが一番です。 しかし、パッケージ名を間違って入れてしまったりすることもあるので、これを意識して防ぐのは難しいです。
最近もrss-parserというパッケージを入れようとして、コピペしたらrss-parse
になっていて、別のパッケージがインストールされそうになったことがあります。
この時は、今回紹介するni.zsh
に追加したマルウェアっぽいパッケージを検知する機能で防ぐことができました。
(実際にはrss-parse
はマルウェアではないですが、全く別のパッケージで依存関係に問題があったのでインストール前に気づけたのはよかった)
ni.zshのマルウェア検知機能
ni.zshのマルウェア検知機能は、Socketというサービスを利用しています。
Socketは、npmなどのサプライチェーン攻撃を検知するサービスで、特定のパッケージのリスクスコアやパッケージの中身を分析した結果を返してくれます。
たとえば、先ほどの記事でも登場したemails-helper
というマルウェアパッケージの分析結果は次のページで見られます。
AIベースのリスク評価、postinstallでインストール時に何か実行するリスク、バージョン間で大きな変更が入っているリスク、特定のネットワークリスクのリスクなどがあることがわかります。
これらの結果や総合的なリスクスコアが、API経由でも取得できるので、これを使ってni add <package>
でパッケージを追加するときに、マルウェアっぽいパッケージを検知することができます。
Malware checks using @SocketSecurity added to ni.zsh.
— azu (@azu_re) August 26, 2023
No matter which package manager (npm, yarn-classic, yarn-berry, pnpm, bun) you are using, you can check that the package is safe before installing it.https://t.co/8mohOUoact pic.twitter.com/WkUTyAqpTk
先ほどの、rss-parser
をrss-parse
とtypoしたケースも、typosquattingのリスクが検知され、実際にはインストールする前に気づくことができました。
Socketでは、著名なパッケージとよく似た名前のパッケージのダウンロード数からtyposquattingの判定しているようです。(必ずしも問題があるパッケージではないが意図しないパッケージの可能性が高い)
ni.zshのマルウェア検知機能の使い方
この機能を使うには、SocketのAPIキーが必要です。 次の手順でAPIキーを取得してください。
- https://socket.dev/ にアクセスして、アカウントを作成します
- プロジェクトを作成します(適当なリポジトリにGitHub Appをインストールするだけです、どのリポジトリでもOKです)
https://socket.dev/dashboard/org/gh/{user}/settings/api-tokens
にアクセスして、APIキーをコピーします
取得したAPIキーを、NI_SOCKETDEV_TOKEN
という環境変数に設定すると、自動的にni.zshのマルウェア検知機能が有効になります。
export NI_SOCKETDEV_TOKEN="<socket-token>"
基本的には、APIで取得できるリスクスコアの値が一定以上の場合に、マルウェアっぽいパッケージとして検知します。 マルウェアっぽいものと判定された場合は、インストール前に本当にインストールするかの確認を求められます。 複雑なことをやってるパッケージは誤検知されることが多い(bundleされているやUnicodeの特殊な文字を使ってるなど)ので、実際に確認してからインストールするかを判断すると良いと思います。
詳しい使い方はREADMEを参照してください。
おわりに
Socket自体も同じようなことをするnpm
コマンドのラッパーを提供しています。
ただこれは、npm
しか対応していないので、yarn
やpnpm
などのパッケージマネージャーを使っている場合には使えません。
ni.zsh
は、npm
/yarn
/pnpm
/bun
を同じコマンドで扱えるので、これらのパッケージマネージャーを使っている場合にも使えます。
元々、プロジェクトごとにパッケージマネージャーが違う場合にその違いを意識せずに使えるようにするためにni.zshを作りました
今では、普段使うようなコマンドは、大体ni.zshで使えるようになっています。
ni -- install current package.json
ni add <pkg> -- add package
ni remove <pkg> -- remove package
ni run <script> -- run scripts
ni test -- run test script
ni upgrade -- upgrade packages
ni upgrade-interactive -- upgrade package interactively
ni exec <command> -- execute command
ni dlx <pkg> -- download package and execute command
仕組み的にただのパッケージマネージャーのラッパーなので、こういったマルウェア検知機能を追加することができてよかったです。
お知らせ欄
JavaScript Primerの書籍版がAmazonで購入できます。
JavaScriptに関する最新情報は週一でJSer.infoを更新しています。
GitHub Sponsorsでの支援を募集しています。