株式会社リゾーム 業務ソリューション事業グループの岡部です。BOND WORKSのフロントエンドエンジニアです。
今回は、昨今相次ぐnpmへのサプライチェーン攻撃に対し、BOND WORKSチームが実施している対策を紹介します。
はじめに:サプライチェーン攻撃とは
npmに対するサプライチェーン攻撃は次のような流れで行われます。
- 攻撃者は、フィッシングによるメンテナーアカウントの乗っ取りやCI/CD環境の脆弱性の悪用などを通じて、パッケージの公開権限やリポジトリへのコミット・マージ権限、
npm publishで使用するトークンなどの認証情報を取得する - 攻撃者は対象のライブラリに不正なコードを混入させ、それを含むバージョンを正規のリリースとして公開する
- 利用者が
npm installによって当該バージョンをインストールしたり、ライブラリのAPIを呼び出したりすると、悪意のあるコードが実行される。その結果、認証情報の窃取やデータ漏洩など、さまざまな被害が発生する
今回は上記の3番、パッケージ利用者の目線で、どこにリスクがありどんな対策が有効なのか見ていきます。
1. 悪意のあるパッケージを入れない
第一の防御層は、悪意のあるパッケージをダウンロード・インストールしないことです。そのための方法を2つ紹介します。
1-1. 悪意のあるパッケージを検出・ブロックする
1つ目は、悪意のあるパッケージを検出し、インストール前にブロックすることです。
脅威インテリジェンスによる検出・ブロックを行うツール・サービスとして、Aikido社のSafe Chain、GMO Flatt Security社のTakumi Guardが有名です。
Aikido Safe Chainはインストールして使うツールです。npm などパッケージマネージャのコマンドをラップし、ローカルに立てた軽量な検閲用プロキシサーバーを経由してパッケージをインストールさせるという仕組みです。一度導入すれば複数のパッケージマネージャに対して有効になるのが強みの1つです。
Takumi Guardも検閲のためのプロキシサーバーではありますがこちらはSaaSとして提供されます。パッケージマネージャの接続先をTakumi Guardに変えるだけで利用でき、非常に手軽です。
いずれも無料で利用でき、これらを導入するだけでも安心感はグッと上がります。
1-2. 公開から一定期間経過していないパッケージをブロックする
2つ目は、公開から一定期間が経過していないパッケージをブロックすることです。
悪意のあるパッケージは、攻撃に気付いた開発者によって数時間から1日ほどで取り下げられることが多いです。したがって、公開から数日経っているパッケージはそれだけでリスクが下がっているということです。
もちろん発見が遅れる場合もあるでしょうから、時間が経っていれば安全とは限りません。先ほど紹介した1つ目の方法と組み合わせて、多角的に防御することが重要です。
npmでは min-release-age を設定すると、公開後一定期間が経過していないパッケージをブロックできます。自作パッケージなど信頼できる場合は min-release-age-exclude を設定すれば制限をバイパスできます。このように、デフォルトでブロックしつつ一部のみ許可する、いわゆる許可リスト(Allowlist)方式で運用するのが安全です。
詳細は割愛しますが、Yarnやpnpmにも同様の設定があります。また、先ほど紹介したAikido Safe Chain側でも設定が可能です。
注意点としては、緊急で当てないといけないパッチまでブロックされる可能性があるということです。その場合は安全を確認した上で npm install package-name@1.2.3 --min-release-age=0 のように対象パッケージのみ一時的に許可するのが良いでしょう。
2. npm install 実行時のスクリプトを防ぐ
npmに対するサプライチェーン攻撃における最も恐ろしい点の1つは、インストールしただけで感染する可能性があることです。
依存パッケージに preinstall、install、postinstall、prepare などのライフサイクルスクリプトが設定されていると、npm install / npm ci の過程で自動実行されることがあります。コード内でimportしていないから安全、とは言えません。
preinstall: インストール処理より前に実行されるinstall: インストール処理の最中に実行されるpostinstall:installの後に実行されるprepare: ローカルプロジェクトでのnpm installや、Git URLからのインストールなどで実行されるnode-gyp: (ライフサイクルスクリプトではないがCやC++で書かれた処理を扱う仕組みで、こちらも攻撃に使われることがある)
したがって、このようなスクリプトの自動実行を抑止する設定が効果的です。npmでは ignore-scripts=true を設定することで、上記スクリプトの実行を禁止できます。こうしておけば、仮に悪意のあるパッケージをダウンロードしてしまった場合でも、被害発生の可能性を下げられます。
また、npmにはGitリポジトリをパッケージソースとして扱う機能があり、こちらも攻撃に使われることがあります。この機能が必要ない場合、--allow-git=none も併用して無効化するのがおすすめです。
npmはデフォルトでこれらを許可するようになっており、任意コード実行が非常にやりやすい(攻撃者にとっては嬉しい)環境といえます。この点に関してはnpmサイドも対策を進めており、npm v12からはデフォルトでスクリプトの実行やGit連携がブロックされ、許可リスト方式での運用が推奨されるようになります1。
このようなスクリプトが必要なパッケージもあります。v11ではブロック対象を警告表示してくれるので、今から許可が必要なパッケージを洗い出して準備しておくのがいいでしょう。
3. CI/CD環境を守る
ローカルだけでなくCI/CD環境を守ることも大切です。ローカルで気を付けていても、CIが別のポリシーで動いていると、そこが抜け穴になることがあります。できるだけ設定は揃えた上で、CI/CD環境では以下のような追加の対策を行うと効果的です。
- Dependabotでも、公開から一定期間待つクールダウンを設定できる2。自動PRを利用する場合はパッケージマネージャ側の設定と統一しておく
- CIスクリプトでは、意図しないパッケージのアップデートが混入しないように
npm ciやyarn install --immutable、pnpm install --frozen-lockfileを使う
おわりに
有名どころのパッケージだと、何万、何十万ものユーザーが使っています。自分のプロジェクトが直接依存していなくても、利用しているパッケージが間接的に依存しているパターンも多いです。ゆえに影響範囲が広く、またShai-Huludのように自己増殖能力を備えている場合は爆発的に被害が広がる傾向にあります。
最近ではAxiosやTanstackといった定番といえる人気ライブラリへの攻撃が話題になりました。もはやNode.js開発者全員にとって無関係ではいられない話題といえるでしょう。リスクは身近に迫っています。
BOND WORKSチームでも機能開発と並行して対策を進めました。定期的なパッケージ更新に安心して取り組めるようになりました!参考になれば幸いです🙏
