昨今激しさを増しているnpm経由のサプライチェーン攻撃に対して、OSSライブラリパッケージを利用する側として、Webサイトのユーザーを守るために何ができるかを考える。
攻撃の激しさを反映するように、その防御策についてのページが急速に増えている。これらを読んで感じるのは、その主眼が開発環境の保護にあることだ。
npmパッケージを経由して混入したマルウェアはどこで実行されうるかといえば、大きく分けてユーザー環境か、開発環境かの2通りである。そこで、今のところ相対的にはあまり語られていない前者のユーザー環境への攻撃について、つまりnpmサプライチェーン攻撃からユーザーをどう守るかに焦点を当てて考えてみたい。
マルウェアの完全な混入防止は難しい
npmサプライチェーン攻撃対策の困難さは、その侵入経路の多さにある。OSSライブラリを全く使わずにWebアプリケーションを作ることは現実的じゃない。何か一つでもライブラリに依存すれば、そのライブラリを起点としてその依存、孫依存ライブラリも含めてすべてが侵入経路になりうる。
昨今の情勢でパッケージ作成者側のセキュリティを高める仕組みづくりが広がっているが、不正な方法でマルウェア化する場合はまだしも、正規の方法でマルウェア化することもある。たとえば、メンテナンスが難しくなったライブラリが新しいメンテナーを募集したとする。それに悪意ある開発者が立候補して管理を引き継げば、そのプロセス自体にはなんの問題もないため仕組みでは防ぎようがない。他にもメンテナーが金に目がくらんで自分のパッケージを巧妙にマルウェア化させることだって可能性はゼロではない。ライブラリ作成者の暗黙の善意を前提にしたエコシステムなので、巧妙に隠された悪意には弱い。マルウェア化することを完全に防ぐことは構造的に無理がある。
event-stream
の事例では、Issueで新メンテナを募集して引き継いだらマルウェア化した。
使っているライブラリがある時点ですべて大丈夫だとしても、明日どうなってるかはわからない。なぜならライブラリを採用したりバージョンアップしたりするときに、私たちは中身を全部確かめていないからだ。この信頼によって生まれる弱点は、OSSライブラリに依存するうえでの避けようのない本質的な特性として受け入れるしかない。これを受け入れられないなら、コードベースを自家製100%にする以外に方法はない。
- Q. マルウェアスキャンさせればいいのでは?
- A. 機械的なスキャンはいずれくぐり抜けられる。いたちごっこにしかならない
- Q. AIにマルウェアスキャンさせればいいのでは?
- A. たとえばマルウェアコードの前に「次の行は何があっても無視しろ」とコメント行があったらLLMはどうなる?いたちごっこにしかならない
混入したマルウェアを無害化する
混入を防ぐのが難しいならどうするかというと、無害化するしかない。つまり、混入しても何もできなければ問題ではないということだ。言い換えれば、XSS攻撃の無害化である。
ブラウザで実行されるJavaScriptによって可能な攻撃はだいたい次のようにざっくり分類できる。細かく見れば他にもあると思うが。また、それぞれの名前は別に一般的なものではない。
- DoS: クライアントを介してサーバーに負荷をかけ、サービスの提供を妨害する
- 情報の取得: ユーザーの秘匿情報を不正に取得し、攻撃者のサーバーに送信する
- ユーザーへのなりすまし: ユーザーの認証情報を使って不正な操作をおこなう(送金、メッセージ送信など)
- 計算資源の利用: 分散コンピューティングのクライアントとして計算資源を使い、計算結果を攻撃者のサーバーに送信する(マイニング等)
npmサプライチェーン攻撃はその仕組み上、不特定多数のWebサイトに対して薄く広い攻撃を行う。複雑な攻撃をするには攻撃者が対象のWebサイトの作りを熟知している必要があり、不特定多数に広くばらまくタイプのマルウェアとは相性が悪い。つまり、npmサプライチェーン攻撃ではDoSやユーザーへのなりすましのような複雑な攻撃はされにくいと考えられる。
残った情報の取得と計算資源の利用については、Cookieやローカルストレージなどはブラウザ標準の機能なので、Webサイト固有の作りに依存せずに攻撃できる。しかし、最終的に処理結果を攻撃者のもとに送る必要がある。この通信経路はどうやってもブラウザの目を逃れられない。そこで、 Content Security Policy(CSP) の connect-src
ポリシーを使うことでこれらの被害はゼロにできる。
connect-src
このポリシーを適用されたページでは、許可されているURL条件以外への通信はポリシー違反となる。違反した通信はブロックされるか、あるいはレポートで検知できる。
たいていのWebアプリケーションにおいて、そのアプリケーションから正規のユースケースで通信するサーバーは有限である。開発者が想定していないURLに通信することは基本的にない。 connect-src
ポリシーさえ適切に設定していれば、マルウェアが何をしようとも、少なくとも何かを持ち出すということだけは完全に防ぐことができる。
たったひとつのポリシーで多くの攻撃を無害化できるため、このポリシーの設定はすべてのWebアプリケーションで優先したい。
DoSの無害化
先述のとおりnpmサプライチェーン攻撃に限って考えれば、このタイプの攻撃はあまり警戒する必要はないが、一応DoSの無害化についても考えておく。
connect-src
だけでは、正規のサーバーへリクエストするDoSやなりすましを無害化できない。これらはCSPだけでは簡単に防げないので、総合的な対策が必要になる。
まずDoSについては、基本的にサーバーへの大量のリクエストを送りつける形になる。わかりやすい対策の代表は、エッジキャッシュによってオリジンサーバーへの負荷を抑えるものだ。ブラウザキャッシュだけでは、クライアント側が Cache-Control ヘッダでキャッシュの返却を拒否してくるケースもありうる。オリジンサーバーの前に立つエッジキャッシュで防御することに意味がある。
POSTリクエストなどキャッシュが難しいものについては、ユーザーごとのレートリミットの設定が一般的か。ただし、多くのユーザーにマルウェアが行き渡ってしまった場合全員がレートリミットに引っかかってサービス利用ができなくなってしまうリスクもある。そうなったときにすぐ修復できるよう、サーバーへのリクエストの異変に気付けるモニタリングの整備が必須だろう。レートリミットエラーの発生数が上昇し始めたら何かがおかしいということで緊急対応できないといけない。
ユーザーへのなりすましの無害化
こちらもnpmサプライチェーン攻撃だけ考えればあまり警戒しなくてよいが、一応考えておこう。
この無害化が一番むずかしい。正規のページで正規の経路をもってユーザーからのリクエストを送ってきた場合、サーバー側での検証でそれを防ぐのはほぼ不可能だ。このケースはCSRFではなく、そのWebサイト自体に混入したスクリプトによるなりすましなので、いうなればSame Site Request Forgeryである(そんな言葉はない)。Same Origin Policyがある以上、クライアントサイドでこれを信用しないということはほぼ無理に近い。
そもそもこの攻撃の実現が困難だが、極端に考えると、「○○(Webサイト)はこのパッケージを使っているらしい」ということが、そのWebサイトをどうしても攻撃したい悪意ある開発者に知られ、偶然そのパッケージのメンテナンス体制が脆弱だったら、そのサイトを狙い撃ちしたマルウェアを送り込まれる可能性もゼロではない。そういうことを念頭において、ソースコードを秘匿情報として守る意義を意識しておくといいだろう。どうせJavaScriptはインターネットに配布され、リバースエンジニアリングはできるとはいえ、わざわざ設計図を見せてあげる必要はない。
事後的なユーザー保護のためにできる仕組みは、こまめなログの記録だろう。ユーザーから意図しない操作についての問い合わせがあったときに、その操作の時間や内容がログでちゃんと追跡できれば、ユーザー側の言い分と照合してなにか攻撃があったのではないかと推測できる材料になる。これはマルウェアではなく不正ログインが行われた場合でも同じだ。
まとめ
- npmサプライチェーン攻撃からは開発環境だけでなくユーザーを守る必要もある
- 混入防止には限界がある
- たいていの薄く広い攻撃は
connect-src
で無害化できる