CVE-2023-40547 – shim が HTTP ヘッダーを誤って信頼したことで発生した脆弱性
(github.com/rhboot)- rhboot/shim の commit
0226b56は、ファイル受信処理で HTTP ヘッダーのサイズ値 をそのまま信頼したことにより発生した CVE-2023-40547 を修正 - 細工されたヘッダーが実際の受信データより小さいサイズを指定すると、shim は必要なバッファより小さな領域を確保してしまう可能性がある
- 既存コードは確保時にはヘッダー値を使い、コピー時には プロトコルメタデータ を使用していたため、out-of-bounds write につながる可能性があった
- パッチは
httpboot.cのreceive_http_response()で*buf_size < rx_message.BodyLengthを検査し、失敗時にはEFI_BAD_BUFFER_SIZEとInvalid Content-Lengthとして処理する - 変更範囲は
httpboot.c1ファイルの 7行追加・1行削除 で、Content-Lenghtの誤記もContent-Lengthに修正された
脆弱性の発生フロー
- CVE-2023-40547 は、shim が HTTP または関連プロトコルでファイルを取得する際に発生する問題
- 受信データを保存するバッファを確保する過程で HTTP ヘッダーのサイズ値 が使われていた
- HTTP ヘッダーは改ざん可能であり、実際の受信データより小さいサイズを指定できる
- 既存フローでは、バッファ確保にはヘッダー値を使い、rx バッファからデータをコピーする際には プロトコルメタデータ を基準にしていた
- この差異により、確保されたバッファより大きなデータをコピーできてしまい、その結果 out-of-bounds write が発生する可能性があった
パッチ内容
- パッチは
httpboot.cのreceive_http_response(EFI_HTTP_PROTOCOL *http, VOID **buffer, UINT64 *buf_size)に防御チェックを追加 *buf_size == 0の場合は既存のエラーメッセージの誤記を修正し、goto errorに移動Failed to get Content-Lenght→Failed to get Content-Length
- 新しいチェックでは
*buf_size < rx_message.BodyLength条件を確認- 条件が真なら
efi_status = EFI_BAD_BUFFER_SIZEを設定 Invalid Content-Lengthエラーを出力- その後
goto errorに移動
- 条件が真なら
変更範囲
- 変更されたファイルは
httpboot.c1つ - 変更量は 7行追加、1行削除
- 核心は、受信本文長である
rx_message.BodyLengthが確保サイズである*buf_sizeより大きいかを確認するロジック
関連記録
- この commit は CVE-2023-40547 を解決する変更として示されている
- commit メッセージには、問題が HTTP ヘッダーを誤って信頼したことに起因すると明記されている
- 脆弱性の報告者は Microsoft Security Response Center の Bill Demirkapi と記録されている
1件のコメント
Hacker News の意見
shim は、Secure Boot を有効にしたい Linux ディストリビューションでよく使われる EFI ブートローダーです
ディストリビューション側としては、ユーザーに自分で鍵を登録させるより、標準搭載されている Microsoft 署名鍵で Secure Boot を簡単に有効にしたいと考えます
しかし Microsoft は一般に GRUB のような GPL ブートローダーには署名しないため、Microsoft 鍵で署名できる shim が作られました。shim は Machine Owner Key、つまり MOK という別の鍵で、自分が起動する対象の署名を検証します
shim に起動する EFI バイナリを指定する際、HTTP URL を渡すことができますが、このとき HTTP サーバーが悪意あるものだと、範囲外書き込みを引き起こせます
ただし通常は GRUB のようなローカルの第2段階ブートローダーを起動するために使うので、多くのインストール環境では問題になる可能性は低そうです
Secure Boot は当初から、すでに署名済みのバイナリも DBX リストで失効できるよう設計されており、このリストを UEFI に入れると、有効な署名があっても該当バイナリを拒否します
このバグを含む古い shim バイナリの署名がリストに追加されれば、各自の機器でリストを更新できます。LVFS のようなカプセル更新で配布されることもありますし、Secure Boot の鍵と変数を直接管理しているなら、https://uefi.org/revocationlistfile からリストをダウンロードして登録することもできます
そうであれば Critical 評価にはならなかったでしょう
このバグは、ローカルで権限を持つマルウェアが EFI パーティションを上書きする場合、PXE ブートが有効な隣接ネットワークで中間者攻撃を行う場合、HTTP ブートを使う場合のリモート中間者攻撃で悪用できます
権限のないリモート攻撃者が中間者の位置にいて、被害端末が HTTP ブートを使っていれば、直接アクセスなしでも悪用可能です
被害端末で権限とコード実行を得たリモート攻撃者は、被害者が HTTP ブートを使っていなくても、ファームウェアが HTTP をサポートしていれば Secure Boot を回避できます
例えば、ブート順序の変数を変更して攻撃者が制御するサーバーを指定したり、EFI パーティションのブートローダーを正規の shim と GRUB2 イメージで上書きしたうえで、
grub.cfgから HTTP 経由で新しい shim をチェーンロードさせたりできますGRUB2 のデバイス構文では、HTTP を含むサポート済みデバイスを指定できるためです
また、権限のない隣接攻撃者が中間者の位置にいて、被害端末が PXE ブートを使っている場合、PXE の shim → PXE の GRUB2 → HTTP の shim という形でつなげて悪用できます
出典: https://techcommunity.microsoft.com/t5/hardware-dev-center/u...
GPLv3 の反 Tivoization 条項が Secure Boot 署名鍵の提供を要求し得るなら、MOK 署名鍵も要求があれば提供しなければならないのでは、と思います
そうだとすると、誰でも Secure Boot を通じて間接的に起動される任意のコードに署名できる鍵を得ることになるわけで、Microsoft が GRUB のような GPLv3 プロジェクトに単に署名鍵を発行する結果と、意味のある違いがあるのか分かりません
古い BIOS 機では WBM に GRUB をチェーンロードさせたことがありますが、UEFI 機ではまだ試していないので、どこかで詰まる点があるのかは分かりません
「なぜ信頼できない、または侵害されたサーバーから起動するのか?」「サーバーが侵害されたなら、単に悪意あるバイナリを送ればいいので意味がないのでは?」と思うかもしれませんが、短く言えば、shim が最終的に起動するバイナリは MOK で署名されていなければなりません
したがって、侵害されたネットワーク内で起動する場合でも、HTTP で起動する場合でも、侵害されたサーバーから起動する場合でも、HTTPS かどうかに関係なく同じセキュリティ保証を維持する必要があります
Secure Boot はダウングレードを防ぐものではないため、侵害されたサーバーがダウングレード攻撃に使われ得るという事実は、この脆弱性とは別の問題です
ダウングレード攻撃の防止は、いずれにせよ、より堅牢な方式で別途実装する必要があります
ただし、shim がなぜ HTTP ブートを直接サポートする必要があるのかは分かりません
MOK で署名された2つ目のローカル EFI バイナリで処理してもよさそうですが、おそらく実装が比較的簡単な機能だと考えられていたのでしょう
このコードがなぜ本文の長さを二つの基準で扱っているのか分からない
RFC上、HTTP/1.1における Content-Length はHTTPリクエスト/レスポンス本文の長さに関する権威ある情報である
その長さを超えて回線上にあるデータは、定義上、別のメッセージの一部である
逆にContent-Lengthが
rx_message.BodyLengthより大きいなら、まだメッセージ全体を受け取れていないという意味なので、さらに待つかタイムアウトエラーを出すべきであるいずれにしても、
rx_message.BodyLengthがContent-Lengthと等しい保証がないなら、それは不正な値であるもっと寛容に処理したいなら、Content-Lengthヘッダーを見る理由はなく、単に
rx_message.BodyLengthをバッファサイズとして、回線上のすべてのデータを受信メッセージとして解釈すればよい現在のコードは不必要に複雑で、こういう形でバグが入り込む
周辺のコード https://github.com/rhboot/shim/blob/0226b56513b2b8bd5fd281bc... を見ると、ループ内でデータ片を受け取り、そのたびに新しいデータがContent-Lengthで決まったバッファ容量を超えないか確認している
しかし以前は、ループ外の 最初の読み取り についてその検査をしておらず、それがバグだった
ただし、ダウンロードされたサイズが
*buf_size、つまりContent-Lengthと同じかを最後に確認するコードは見当たらないこの条件が崩れるなら、接続が早すぎるタイミングで閉じられたことを示すシグナルかもしれない
これは明らかにバグで、修正されてよかったが、そもそも信頼できないホストから自分の機器を起動する人がいるのかと思う
攻撃者が悪意あるヘッダーを送れるほどHTTPサービスを掌握しているなら、このオーバーフローを回避することなど最も小さな問題で、証明書も侵害されているし、仕様に合ったペイロードにマルウェアを入れて送ることもできる
バグなのは確かだが、Critical かどうかは確信が持てない
彼らは、潜在的に侵害されうるすべてのものはSecure Bootで署名されていてはならない、というセキュリティ戦略を本気で信じている
脆弱性のある署名済みの何かが一つでもあれば、それを利用して全員のSecure Boot+TPM暗号化ディスクを復号するのに使える
このアプローチがなぜ有効なセキュリティモデルと見なされたのか理解しがたく、こうした脆弱性はすでに山ほどある
さらに、部屋の中の象である Windows は完全に無視している
こうした考え方の例: https://lkml.org/lkml/2018/4/3/767
Linusの懸念にもかかわらず、多くのディストリビューションではSecure Bootで起動すると実際にインテグリティモードが有効になる
おそらくMicrosoftのポリシーと、ディストリビューションがMicrosoft UEFI署名を得るためにそのスレッドで説明された手順に従わざるを得ない状況のためだろう
その結果、Secure Bootを有効にすると通常ディストリビューションの機能が制限され、例えばハイバネーションが使えなくなる
重要なことが起きるときはHTTPのSを使うべきだ
デバイスの起動もこれに含まれ、HTTPSヘッダー は常に暗号化されていた
それでも、よく見つけたバグだ
不正なヘッダーはどちらでも送れる
暗号化には正確な時刻と日付が必要だからだ
RTCが有効な場合もあるが、タイムゾーンをうまく扱うのかも分からないし、いずれにせよ時刻がずれている可能性もある
問題を正しく理解していないように思う
Content-lengthは実際の本文長ではなく、Content-encoding後の長さ であるこれらのshimビルドには httpboot が含まれているのか?
私の理解では、shimはディスク上の別のEFIバイナリを実行するためのものにすぎず、shimのネットワークブート機能が実際に使われているのを見たことはない気がする
よく分かっていないのかもしれないが、ほとんどのHTTPクライアントは指定された Content-Length までしか読み取らず、読み取ったバイト数がContent-Lengthより少なければエラーと見なすものだと思っていた
私の見る限り、UEFI仕様はContent-Lengthヘッダーとレスポンス本文長が一致しない場合の動作を具体的に定めていないようだ
そのため、一部の実装は単に
connection:closeリクエストを作り、Content-Lengthを確認しない可能性も十分あるこの脆弱性はMSRCが報告したもので、CVEの説明には実際の悪用に関する記述はない
後で公開されるかもしれないし、理論上の問題かもしれない
実際の本文長より少なく読むことがなぜ危険なのか説明してもらえる?
むしろ逆のほうが危険だろうと予想していた
ところがサイズを操作可能なHTTPヘッダーから取得しており、攻撃者は受信データより小さいサイズを指定できる
この場合、コードは割り当てにはヘッダー値を使い、受信バッファからコピーするときにはプロトコルメタデータのサイズを使うため、範囲外書き込み が発生する