パッケージマネージャで配布されるマルウェア、対策と課題について
はじめに

画像は記事に全く関係ないカニのフィギュアです👋
近年、善良なパッケージを騙ったマルウェアが配布されているケースが増えてきています。 これらのマルウェアはパッケージマネージャ上で配布され、開発者端末やそれをビルトインしたシステムを利用するユーザー端末で悪事を働きます。
これは俗にいうサプライチェーン型攻撃で、 これらの関連ニュースを目にする機会が増えてきていることを、多くの開発者が体感されていると思います。
ただ、これらのサプライチェーン型攻撃の記事は、 どうしてもエンドユーザー(パッケージを利用する開発者側・それらを組み込んだアプリを実行するユーザー側)の対策に焦点が当てられたものが殆どのように感じています。
そこで本記事では、このエンドユーザー側の対策だけではなく、 パッケージマネージャメンテナーたちがどう対策しているのかも含めて、 「パッケージマネージャ上で行われるマルウェア配布」と、 「これの攻撃からどう保護していくのか」について、現状の課題を含めて書いていこうと思います。
ライブラリマルウェアの概要
まず前提として、登場人物と一般的な攻撃フローを記載します。
登場人物:
- パッケージマネージャ
- パッケージマネージャのメンテナー
- 攻撃者(マルウェア配布者)
- エンドユーザー(パッケージの利用者、つまり「開発者」または「対象パッケージがビルトインされたアプリの利用者」)
- パッケージ開発者(ライブラリなどの開発者)
攻撃フロー:
- 攻撃者はパッケージマネージャ (npm, Maven, Packagist, PyPI 等) にマルウェアをアップロードする
- エンドユーザーはそのマルウェアのパッケージをインストール(実行)する
- 不正なコードが実行される。
基本的には、これらのステークホルダー・攻撃フローがベースとなってきます。
この攻撃フローを見ていただければわかりますが、一般的なマルウェア配布の流れと同様に、 「一見問題ないように見えるプログラムが実は悪性だった」という性質をもった攻撃になります。
この攻撃フローの特徴的な点を上げるとすると (2) のインストール部分は、被害者を騙す必要があるため、 以下のようなテクニックが利用される点です。
- TypoSquatting (普通のライブラリの1文字違いなどのタイプミス狙い)
- 公開されてるパッケージの管理者権限の奪取(開発者のクレデンシャル奪取)
- ソーシャルエンジニアリングと巧妙な攻撃(ノーマルのパッケージを配布し続け、ユーザー数が増えたタイミングでマルウェアにシフトさせる等)
TypoSquatting に馴染みのない方もいらっしゃると思うので、少し例を挙げると、
request というライブラリに似せて 4equest というマルウェアが配布されていた例などがあります。
( r の打ち間違えを狙い、キーボードの隣接した key 4 に置き換えられている)
攻撃手法
先ほど挙げた3種類の攻撃についてもう少し深掘りして見ます。
TypoSquatting
TypoSquatting は、打ち間違いをしたユーザーを狙う攻撃手法の一つで、 フィッシング詐欺で頻繁に利用される手法です。
フィッシングサイトの例を出しますが、 google.com の打ち間違いで gooogle[.]com (o が1文字多い)と入力した場合に、
別のドメインに行ってしまいます。
この打ち間違いをしそうなドメインにてフィッシングサイトを用意しておくことで、一定数間違えたユーザーが
ダミーサイトに誘導されるというのが攻撃シナリオとなります。
この手法は、パッケージ上のマルウェア配布でもそれなりに悪用されています。
例えば先ほども例に出した request などの正規パッケージに名前を似せて、4equest という名前でマルウェアが配布されていたことがありました。
この手法の恐ろしい点としては、打ち間違いに気がつきにくい点にあります。
被害者となるエンドユーザー(開発者)は、 npm 等の CLI でインストールを行うと思いますが、
打ち間違いをしたにも関わらず、パッケージのインストールが成功してしまいます。
つまり「成功した」と勘違いし、ライブラリが別のものであっても気がつかずに、別の作業等に移ってしまう可能性があります。
補足: 最近はインストールした時点で悪性コードが動くケースが多く、打ち間違いのインストールを行った時点でアウトのケースもあります。
公開されているパッケージメンテナーの管理者権限奪取
続いては、パッケージメンテナーのクレデンシャルが盗まれるようなケースです。
この手法はタイトルの通り、クレデンシャルが盗まれた場合にしか発生せず、難易度が高く、 そして発生した場合は強烈なインパクトがあります。
仮に http-client という架空のパッケージがあり、メンテナーのクレデンシャルが漏れ出たとします。
攻撃者は、クレデンシャルを元に、対象のパッケージのメジャーバージョン等にマルウェアを仕込んで新規にリリースが行います。
すると、この http-client を latest 等でインストールした際にマルウェアが実行されます。
他にも、 latest は検知されやすすぎるため、一つ下のメジャーバージョンのマイナーアップデート、
(つまり、 2.0.0 が最新とするならば、 1.9.12 等の一つ前のメジャーバージョンの最新版)に仕込む場合もあるかもしれません。
「クレデンシャルを盗むのは難易度が高い」と書きましたが、実はこの攻撃はそれなりに発生しています。 先月(記事執筆時 2021/11)も npm にてこの問題が発生しています。
https://blog.kaspersky.co.jp/uaparser-js-infected-versions/31874/
また、過去にはパッケージをサポートする人がいなくなり、それを攻撃者が 「俺がサポートするから権限くれ」と言って権限を盗み出し、 手に入れた権限を持って不正コードを入れ込んだバージョンをリリースするといった例もありました。
上記から分かる通り、難易度は高いですが、熱心な攻撃者はこのインパクトのでかい攻撃を狙うことはそれなりにあります。
ソーシャルエンジニアリングと巧妙な攻撃
この手法は、一般的なマルウェアの配布方法と同じで良く利用される手法です。
タイムリーなことに、本記事の1週間ほど前にマルウェアと判明し、警告が出された yiffparty という python マルウェアは、まさしくこの手法が利用されていました。
警告が出された日の3ヶ月程前に reddit にて「(18禁のサイト用の)ライブラリ作ったから使ってくれ!」という投稿がありました。
このライブラリが実際マルウェアで、インストールも数千件程度行われていたそうです。
不正コードの特徴と攻撃内容
これらのパッケージ型マルウェアは、以下の攻撃行為を行うことが多いです。
- 暗号通貨マイニング
- 内部情報・クレデンシャル情報の奪取
- C2サーバー(コマンド司令サーバー)とのコネクション確立(いわゆるバックドア)
このあたりは通常のマルウェアと同様なので、あまり解説できる点はありません。
では、少し視点を変えて、「これらのコードがいつ実行されるのか」について見ていきます。
結論から書いてしまいますが、大抵は「インストール段階で実行される(ことが多い)」です。
(もちろん言語によりけりだったり、攻撃の中身にも寄ってくるでしょうが、少なくともインタプリタ型言語はこの傾向が多いようです。根拠等は後ほど紹介する研究にて。)
このインストール段階で実行される主な理由としては以下があります。
- 不正コードが必ず実行されるため
- パッケージの中身に精通していなくても、不正コードを挿入しやすいため(楽に攻撃したいため)
「不正なコードが必ず実行される」というのはわかりやすい理由です。 では、「パッケージの中身に精通していなくても...」というのはどういうことでしょうか?
一つ例を出します。
私たちが攻撃者だった場合に、
大規模フレームワークのクローンを作成し、マルウェアを仕込むとします。
そして、検知されにくくするために、特定のメソッドが実行された際にのみ不正コードが起動するようにしようと考えたとします。
すると当たり前ですが、「この関数ってどこのタイミングで call されるの?」
「そもそもプロトコルの中身わかんね・・・」
「ここに仕込んでも普通に動くんかな・・・」
等、パッケージ自体のドメイン知識が必要になってきます。
つまり、「楽で、インパクトのある攻撃がしたい」攻撃者的にはあまりうま味がない攻撃になってしまいます。 そのため、攻撃者は「不正コードを入れても動く」「必ず call される」箇所に不正コードを挿入する傾向があります。
例えば、Python の setup.py 、npm の package.json 等です。
(Ruby の場合だと extconf.rb にマルウェアが入っていた例がありましたが、Ruby 自体も詳しくないので、これがインストール段階でkickされるのかちょっと自信ないです)
Java の例としては、パッケージのエントリポイントとなる main 関数にべたっと書かれている例がありました (Java / Ruby の検体をあまり多くは見てないのでこの辺は話半分に聞いてもらえれば...)
以上からわかる通り、パッケージがインストール、または実行されたタイミングで、 起動される箇所に不正コードが仕込まれることが多いです。 ただ「必ずエントリポイントにマルウェアが仕込まれる」というわけではなく、 検知を遅らせるために、巧妙に何かしらの関数の中に不正コードを挿入するケースももちろんあります。
数は少ないですが、一部検体を軽く見てまとめた記事を少し前に書いていますのでよければ。
パッケージファイルに擬態したマルウェアのコードを読む part1 - ぶるーたるごぶりん
対策
ここまでで、この攻撃の特徴や外観がわかってたと思います。 なので、次は以下について書いていきます。
- エンドユーザ(開発者)で行える対策
- パッケージマネージャ側が実施している(しようとしている)対策
エンドユーザ側の対策
エンドユーザ(開発者)の行える対策としては以下があります。
(もちろん、銀の弾丸などありません。それと他にもソリューションがあれば是非教えてください)
- 依存ツリーを洗い出し、依存パッケージの中に悪性パッケージが含まれていないかを検出する
- 依存パッケージを読み込み、コードを静的・動的に検査する
この二つのアプローチには、それなりに課題があります。
(1) の方は、いわゆる Dependabot や Snyk, White Source, yamory 等の SCA(Software Composition Analysis) 製品を利用しつつ、 彼らの持つDBにある脅威情報(悪性パッケージ情報)を突合する形式です。
この手法の課題としては SCA 製品が悪性と判断したもの(正確な言い方をすると検出対象と判断したもの)しか検出できない点にあります。 つまり「(場合によっては)CVEが発行されない脅威情報」について、SCA製品がどこまでそれを検知対象とするか?等の検出品質に依存している点が問題となります。
(これは特定の製品が劣っている・優れているという事を言いたいわけではないですが)、 Snyk のDBを軽く調べてみると、過去の CVEの発行されていないマルウェアの一部はDBに登録されていませんでした。
ただ、この精度問題については(個人的な予想ですが) 今後は SCA 製品がパッケージマネージャと協力し、脅威情報をいち早く取り入れて行くのではないかなと思います。 なので、実際はそれほど大きな問題ではないのかもしれません。 (もしかしたら既に実施しているのかもしれません。)
続いて (2) の方ですが、まだあまり製品などが(体感では)出揃っていない印象を持っています。
一般的なデメリットとして静的・動的解析は、過剰検知・検知不足 (false positive/negative) が激しいという特徴があり、
運用負荷が高い可能性があります。
製品としては(あまり詳しくないので1つしか挙げられませんが) GitLab が Falconベースの検出ツールを出しています。
ただ「パッケージインストール時に疑わしい挙動を監視する」と言う機能らしく、 巧妙にパッケージの一部関数が call された時にだけ動くマルウェア等は検出できないように思えます。
まとめると、当たり前の話ですが 「銀の弾丸などない」と言う話です。
ただ、「じゃあやらなくていいんだね!」と言いたいわけではなく、 「多層防御的にできる限り防御を重ねて行く」ことが重要なんだと思います。
Dependency Hell とパッケージマネージャのバージョン解決
対策の話から少しそれますが、 パッケージ型マルウェアには、修正時に以下のような問題が発生することがあります。
- 利用している(マルウェアの)パッケージが置き換えられない
- テスト不足のため、不安
- コードがパッケージにガッツリ依存していて、容易に置き換えられない
- 既にプロダクションにコードが挙げられており、デプロイ仕切るのが辛い
- パッケージ型マルウェアを利用して構築しているライブラリを自分たちが利用しており、ライブラリに修正バージョンがない
- インストール後のコードを無理矢理手作業で書き換える?
- 元のライブラリに Issue や Pull Request を送って直るのを待つ?
- パッケージマネージャのバージョン固定化問題で、修正するのが辛い
- 複数のライブラリが特定ライブラリのバージョンを引っ張りあって、バージョンが固定化される
見るからに嫌な課題が山盛りです。 この辺りの Dependency Hell (依存地獄) の話も、大きな課題の一つです。
ただ、本記事でこの辺りの話を持ち出すと終わりが見えないので、 このあたりの問題について、「どう対応すれば良いのか」「GitHubがこれらの問題にどうアプローチしようとしているのか」 などについて、過去に記事を書いているので、時間があり余っている方は読んでみていただければ幸いです。
GitHubが狙う「ライブラリのバージョン管理問題」の解決と依存関係地獄の話 - ぶるーたるごぶりん
パッケージマネージャ側の対策
エンドユーザの対策は記載したのでお次はパッケージマネージャ側の対策です。
(このタイトルだと語弊のある書き方なので、より正確に言うと 「パッケージマネージャ上にマルウェアが混入されるのを防ぐための対策」です)
先ほども書いた通り、攻撃には以下のパターンがあります。
ここから対策を考えると、 「クレデンシャルの保護」と「アップロード時のパッケージに対するセキュリティスキャン」あたりが思いつきます。
実際これらの真っ先に思いつく対策というのは現在進んできています。
2FA
2FA については、 GitHub が npm 周りについて、何かしら動き出そうとしているのを以下の記事で書いています。
また、 Gem だと以下のようにパッケージのリリース時だけではなく、削除等にも MFA の有効化が行えるらしいです。
このように、開発者のクレデンシャル保護の流れはおそらく今後活発になっていくように思います。
おそらく 2FA が推進されていくと、特定のケース(クレデンシャルが知らないうちに漏れるケース)は防御できると思います。 ただ、それでも完璧ではなく、先ほど例に挙げた「パッケージのメンテを引き継ぐから権限くれ!」といったケースは防御しきれないでしょう。
静的解析・動的解析
静的・動的解析に関しては、割と前からパッケージマネージャのメンテナによって動きがあったりします。
例えば、 2018年に pypi 上で静的・動的解析を行い、マルウェアを検出する手法を試したという記事があります。
この記事で書かれている検出手法は非常にシンプルで、
パッケージのインストール時に実行される setup.py の中に、以下のような特徴があるかを検査しています。
「アウトバウンド通信」の方は、
「正常なパッケージは、 setup.py でわざわざアウトバウンドの通信を行わない(行うことは稀)」だろう
という推測を元に実施しています。
このスキャンを行って、当時の pypi に存在する全てのパッケージをスキャンしたところ、11個のマルウェアを検出されたそうです。
また、Ruian Duanらの 2020年の研究では、 npm, pypi, gem から合計 278個のマルウェアを検出した手法について論文が出されています(すげ〜)。
(インタプリタ言語のパッケージマネージャに対するサプライチェーン攻撃の測定に向けて)
この研究論文はかなり良く考えられており、記事の最初の方で紹介したそれぞれの登場人物について、 現状どのようなセキュリティの保護が(pypi, npm, gem 上で)適用されているかを洗い出し、 詳細に脅威分析などを行っています。
また、マルウェアの検知では、以下をスキャンしており、 静的・動的解析では、先ほど少し挙げた Dependency Hell などの問題も回避しつつ検知を行っています(すげ〜〜〜〜)。
- メタデータ解析(パッケージ名や作者、ダウンロード数、依存関係等の解析)
- 静的・動的解析
例えば、メタデータ解析では以下のようなチェックを行っています。
- 有名なパッケージに名前を似せたパッケージか?
- パッケージに同梱されているファイルの種類をチェックし、バイナリが埋め込まれていないか?
また、静的・動的解析では以下の特徴を検出しています。
- アウトバウンド通信の有無
- ファイルシステム関連の呼び出し(chmod 等)
- プロセスの生成・終了、権限変更等
- eval 等
研究の結果として、マルウェアの特徴を以下のように分析し、検知ルールを作成したと書かれています。

出典: Ruian Duan & Omar Alrawi & Ranjita Pai Kasturi & Ryan Elder & Brendan Saltaformaggio & Wenke Lee. "Towards Measuring Supply Chain Attacks on Package Managers for Interpreted Languages" (2020) p.8 https://arxiv.org/pdf/2002.01139.pdf%C3%AF%C2%BB%C2%BF
意訳:
Metadata の検知ルール:
- パッケージ名が同じレジストリ(パッケージマネージャ)の有名なものに似せている
- パッケージ名が別のレジストリ(パッケージマネージャ)と同じだが、作者が異なる
- パッケージが、既知のマルウェアに依存しているか、作者を共有している
- パッケージに、既知のマルウェアとして同時期にリリースされた古いバージョンが含まれている
- パッケージに Windows PEファイル、または Linux ELF ファイル(実行ファイル)が含まれている
静的解析のルール:
- パッケージに、カスタムされたインストールロジックがある
- パッケージの、最近リリースされたバージョンでは、ネットワーク・プロセス・コード生成のAPIが追加されている
- パッケージには filesystem から network sink へのフローがある
- パッケージには network source から コード生成や process sink へのフローがある
動的解析のルール:
確かに、マルウェアの特徴としてはこれらのことを行いそうだなと思います。
まとめ
現状、多くの開発者にとっては「対策の方法がない」「コストがかかる」等の問題があります。
また、先ほどあげたパッケージマネージャ側での検知手法なども、(おそらく)アドホック的に行われたものだと思います。
ただ、これらの手法がより一般化していく流れが、今後は来るのではないかと思います。
つまり、エンドユーザ(開発者)側の対策、そしてパッケージマネージャ側の対策として、
GitHub や CICD 等の中で、上記のような検知手法を用いていく流れができていくのではないかと思っています。
そうすると、多くの開発者端末で動くパッケージ管理システムや、CICD, Github等でスキャナーが動き、 マルウェアが検知されると、パッケージマネージャのメンテナに連絡が行き、 CVEが発行され、SCA 製品等によって脆弱性の検知・ブロックが行われる・・・といった流れができるのではないかと期待しています。
終わりに
長い記事 (1万字)なので、どれほど読まれるか不明ですが、 誰かの役に立てば幸いです。
ヘヴィメタルより。
更新履歴
2021/11/28 * タイポ (mave -> Maven, packagist -> Packagist, pypi -> PyPI)の修正 * ビルドイン -> ビルトイン に統一