コンテナエンジンCRI-OとPodmanに影響を与える新しい脆弱性(CVE-2021-20291)

A conceptual image representing container security, such as that affected by CVE-2021-20291, discussed in this post

This post is also available in: English (英語)

概要

クラウドネイティブ環境のセキュリティ向上イニシアチブの一環として、筆者はKubernetesがベースとしている複数のGoライブラリのセキュリティ監査を実施しました。この研究においてcontainers/storageCVE-2021-20291が見つかりました。本脆弱性により、レジストリから悪意のあるイメージをプルした場合にCRI-OPodmanにコンテナエンジンのサービス拒否(DoS)が発生します。この脆弱性により、KubernetesやOpenShiftなどのこれら脆弱なコンテナエンジンに依存するコンテナ化されたインフラストラクチャを、悪意のあるアクターが危険にさらす可能性があります。

パロアルトネットワークスのPrisma Cloudを実行しておられるお客様は、Prisma Cloud Computeのホスト脆弱性スキャナとTrusted Imagesの機能によりこの脆弱性から保護されています。

公開プロセス

私たちは2021年3月10日に責任を持って脆弱性を公開し、バージョン1.28.1で修正がリリースされました。対応する修正は、CRI-Oではバージョンv1.20.2でリリースされました。また、Podmanでは、バージョン3.1.0でリリースされました。

プラットフォームのユーザー設定によっては、更新が自動的にダウンロードされることもありますが手動でのダウンロードが必要な場合もあります。クラウドネイティブ環境をお使いの皆さんに、ソフトウェアのバージョンを確認し、最新でない場合は更新することをお勧めします。

この問題へ迅速にご対応いただき、脆弱性にCVE-2021-20291を割り当ててくださったRed Hatのセキュリティチームに感謝いたします。

コンテナのイメージはどのようにプルされるか

この脆弱性がどのように機能するかを理解するには、コンテナエンジンがレジストリからイメージをプルしたときに何が起こるかを理解する必要があります。

コンテナイメージをプルする最初のステップは、対応するマニフェストをダウンロードすることです。各イメージにはマニフェストというファイルがあり、このなかにイメージの作成方法についての指示が含まれています。これには、イメージのオペレーティングシステムやCPUアーキテクチャなどの情報が含まれます。本稿ではlayers配列に焦点を当てます。layers配列というのはマニフェストに含まれるリストのことで、このリストはコンテナファイルシステムを構成するレイヤで構成されています。イメージをプルするさいのオペレーションの1つが、コンテナエンジンでこのリストを読み取って、各レイヤのダウンロード、圧縮の展開、untarを行うことです。

コンテナイメージをプルするとき、結果として生じる操作の1つは、このフローチャートに示すように、コンテナエンジンがレイヤ配列を読み取り、各レイヤをダウンロード、展開、untarすることです。
図1 レイヤ取得フロー

攻撃者は、脆弱性の悪用を目的とした悪意のあるレイヤをレジストリにアップロードした後、レイヤを多数使用するイメージにこの悪意のあるレイヤを含めてアップロードすることにより、悪意のあるイメージを作成する可能性があります。その後被害者がレジストリからイメージをプルすると、そのプロセス内に悪意のあるレイヤがダウンロードされ、脆弱性が悪用されます。

脆弱性: CVE-2021-20291

実行フロー

CVE-2021-20291の脆弱性実行フロー: 1) Goルーチン1のダウンロードレイヤ、2) ロックを取得、3) Goルーチン2およびExec(xz -d)への移動、4) Goルーチン3のWait(xz) 、ブロック、chdoneをクローズ、5) Untar、untar失敗、6) ストリームをクローズ(chdoneを待機)、ブロック
図2 脆弱性の実行フロー

図2は、コンテナエンジンが悪意のあるイメージのダウンロードをリクエストされた場合に、悪意のあるレイヤのダウンロード開始後におきる脆弱性の仕組みを示しています。最終結果はデッドロックの発生、つまりロックが取得されたまま永久に解放されない状況です。これにより、他のスレッドとプロセスが実行を停止し、ロックの解放を永久に待ちつづけることからサービス拒否(DoS)が発生します。

  • ルーチン1 - レジストリから悪意のあるレイヤをダウンロードします。
  • ルーチン1 - ロックを取得します。
  • ルーチン2 - ダウンロードしたレイヤをxzバイナリを使って展開し、その結果をstdoutに出力します。
  • ルーチン3 - xzの終了とstdout内のすべてのデータの読み取りを待機します。条件が満たされると続行してchdoneというチャネルをクローズします。
  • ルーチン1 - xzの出力を入力として使い、データのuntarを試みます。ファイルはtarアーカイブではないことからuntarは「invalid tar header(無効なtarヘッダ)」で失敗し、xzのstdoutからの残りのデータ読み取りが終了しなくなります。データが読み取られことがなくなったためにルーチン3がデッドロックし、chdoneがクローズされなくなります。
  • ルーチン1 - ルーチン3によるchdoneのクローズを待機します。したがってここでもデッドロックが発生します。

ルーチン1がデッドロックしているのでコンテナエンジンは新しいリクエストを実行できなくなります。というのも、リクエストを実行するにはステップ2でロックを取得してやる必要がありますが、このロックが解放されることはないためです。

影響

上記で詳細を説明した脆弱性はcontainers/storageに存在します。CRI-OとPodmanはこのライブラリを使ってストレージを管理しコンテナイメージをダウンロードしています。本脆弱性が悪用された場合、そうした重要な機能が正常に行えなくなります。このサービス拒否脆弱性が悪用された場合に観測された影響には次のものがあります。

CRI-O

  1. 新しいイメージのプルに失敗する
  2. 新しいコンテナの起動に失敗する(すでにプルされている場合も含む)
  3. ローカルイメージのリスト取得に失敗する
  4. コンテナのkillに失敗する

Podman

  1. 新しいイメージのプルに失敗する
  2. 実行中のポッドの取得に失敗する
  3. 新しいコンテナの起動に失敗する(すでにプルされている場合も含む)
  4. コンテナ内に入ってのexecに失敗する
  5. 既存のイメージ取得に失敗する
  6. 既存コンテナのkillに失敗する

Kubernetes

Kubernetes v1.20以降ではDockerが非推奨になり、サポートされるコンテナエンジンがCRI-OとContainerdのみになりました。このため、多数のクラスタがCRI-Oを使用していてそれらが脆弱となる状況が発生しかねません。攻撃のシナリオとしては、攻撃者が悪意のあるイメージを複数の異なるノードに対してプルし、すべてのノードをクラッシュさせてクラスタを機能させなくすることが考えられます。こうなるとノードを再起動する以外に問題を修正する方法はありません。

この画像は起こりうるKubernetesへの攻撃フローを示しています。この図では通常のCRI-OコンテナエンジンのKubelet、APIサーバーに対する位置関係を示しています。
図3 Kubernetesの攻撃ベクトル

結論

ここ数年、Unit 42を含め、さまざまな人々がクラウドアプリケーションとコンテナインフラストラクチャのセキュリティ監査を実施しています。こうした監査の結果、多くのセキュリティ問題が特定・修正されています。クラウドへの移行が急ピッチで進んでいる現在、攻撃者たちも引き続きクラウドを注視しています。筆者のチームはセキュリティ監査の実施、重大な脆弱性の発見、クラウドインフラストラクチャのサポートに取り込み、組織の皆さんの修正プログラム公開や修正プログラム適用による問題への対応を支援しています。

このスクリーンショットは、CVE-2021-20291からの保護に役立つPrisma CloudのTrusted Imagesという機能を示しています。この図は、Trusted Images機能のルールをオンにする方法を示しています。これによりユーザーは、信頼できるソースや実行を許可するイメージをコントロールするためのルールを作成できます。ポリシーの変更はただちに反映されます。Consoleビューで可視性を得るには再スキャンが必要です。
図4 PrismaのTrusted Images機能

パロアルトネットワークスのPrisma Cloudを実行しておられるお客様は、Prisma Cloud Computeのホスト脆弱性スキャナとTrusted Imagesの機能によりこの脆弱性から保護されています。