2 ポイント 投稿者 GN⁺ 2024-05-26 | 1件のコメント | WhatsAppで共有
  • Goのchecksum databaseとpublic module proxyは、Goコードがないリポジトリまで受け入れられるため、任意のGitリポジトリデータをGoインフラに載せて再度ダウンロードする経路が生まれる
  • sum.golang.org/lookup/$module@$version リクエストは、未記録のモジュールバージョンに遭遇するとオリジンサーバーから取得し、その過程で proxy.golang.org にも該当リポジトリのzipが提供される
  • HomebrewのRubyリポジトリとRustリポジトリのforkがchecksum databaseに存在し、新しいGoモジュールだけでなくGoファイルがないリポジトリもpseudo-versionとして登録される実験に成功した
  • モジュールzipには圧縮・非圧縮のどちらも最大500 MiBの制限があるが、開発者マシンやCI/CDのダウンロード制限回避、ペイロード保存、C2実装には十分に大きい規模である
  • sum.golang.org の一意なパス約159万件のうちGitHubパスが約151万件で95%程度を占め、GoエコシステムのGitHub依存度とpublic proxy悪用の可能性がともに明らかになった

Go checksum databaseで見つかった非Goリポジトリ

  • Goのchecksum databaseを調べていたところ、modules テーブルで github.com/homebrew/homebrew-core が非常に多く出現した
    • github.com/homebrew/homebrew-core: 39,438件
    • github.com/Homebrew/homebrew-core: 30,896件
    • github.com/concourse/concourse: 25,372件
    • github.com/openshift/release: 24,065件
    • github.com/cilium/cilium: 22,138件
  • HomebrewリポジトリはRubyを使っていることで知られており、リポジトリとクローンしたファイルからも go.mod やGoソースファイルは見つからなかった
  • 大文字・小文字の違いは、Goのドキュメントにあるcase encodingルールで説明できる
    • 大文字は ! と対応する小文字にエンコードされ、大文字・小文字を区別しないファイルシステム上でも example.com/Mexample.com/m を併存して保存できる
  • github.com/Edu4rdSHL/rust-headless-chrome もGoとは関係のないRustリポジトリのforkだが、checksum databaseに現れている

/lookup がリポジトリを取得する仕組み

  • Goモジュール文書のGo Modules Referenceによると、Goコマンドはchecksum databaseを照会する際、まず /lookup エンドポイントからレコードデータを取得する
  • モジュールバージョンがまだログに記録されていない場合、checksum databaseは応答前にオリジンサーバーから該当モジュールを取得しようとする
  • エンドポイント形式は $base/lookup/$module@$version である
    • $module$version に対するログレコード番号、go.sum 行、署名付きツリー説明を返す
  • github.com/homebrew/homebrew-core のpseudo-versionを照会すると、checksum recordと go.mod ハッシュが返される
  • リポジトリにバージョンタグがない場合は、Goのpseudo-versionルールが使われる

新しいGoモジュール登録の実験

  • 新しいGoモジュール github.com/gdbinit/fluxmatter を作成した後、lookup リクエストで登録されるかを確認した
  • @latest の照会はcanonical versionではないというエラーを返した
    • bad request: version "latest" is not canonical
  • @v0.0.0 の照会は該当revisionを知らないというエラーを返した
    • not found: ... invalid version: unknown revision v0.0.0
  • しかしchecksum databaseを再同期して照会すると、モジュールは登録されていた
    • github.com/gdbinit/fluxmatter|v0.0.0-20240524163826-a7e64ffd69f2|2024-05-24T16:40:51.203837Z
  • proxy.golang.org/github.com/gdbinit/fluxmatter/@latest はpseudo-versionとGitHubオリジン情報を返し、そのバージョンのzipファイルもダウンロードおよび圧縮検証が可能だった
  • 初期シーディングでは正確なバージョンを指定しなくても、モジュールパスとバージョンらしき値を含むlookupクエリだけで動作する

Goコードがないリポジトリもpublic proxyに載る

  • Goコードがまったくない github.com/gdbinit/readmem リポジトリでも同じ実験に成功した
  • lookup リクエストは v0.0.0 revisionを知らないというエラーを返したが、checksum databaseにはpseudo-versionとして登録された
    • github.com/gdbinit/readmem|v0.0.0-20131006075740-407cb0a56933|2024-05-24T16:45:35.88456Z
  • proxy.golang.org@latest は該当リポジトリのpseudo-versionとGitオリジン情報を返した
  • ダウンロードしたzipにはGoファイルではなく、Entitlements.plistREADME、Xcode projectファイル、main.c などが含まれていた
  • この実験ではGitHubリポジトリを使ったが、動作するVCSであれば他のホスティングサイトでも可能な場合がある

GitHub依存度とサイズ制限

  • checksum databaseの一意なパス数は1,591,375件で、そのうち github.com% パスは1,515,957件だった
  • 一意なパスの約**95%**がGitHubにホストされている {p:95}
  • この数値はforkや実際のGoコードではない対象を除外していない生の統計である
  • GoモジュールzipにはFile path and size constraintsが適用される
    • module zipファイルは最大500 MiB
    • ファイルの非圧縮サイズ合計も最大500 MiB
    • go.mod ファイルは最大16 MiB
    • LICENSE ファイルも最大16 MiB
  • この制限は、ユーザー、proxy、モジュールエコシステムの他の部分に対するサービス拒否攻撃を緩和するための仕組みである
  • 500 MiBは悪用可能性を考えると十分に大きいサイズである

想定される悪用シナリオ

  • public Go proxyは、開発者マシンやCI/CDサーバーで宛先ダウンロード制限を回避するために使われる可能性がある
    • private GOPROXY がない状況を前提とする
    • マルウェアはペイロードをリポジトリにアップロードし、必要なときにproxyからダウンロードできる
    • オリジンソースが消えても、checksum database entryには小さな痕跡だけが残る可能性がある
  • proxy.golang.org に対するDoSは実行が難しい可能性がある
    • 任意のGitリポジトリをproxyにダウンロードさせるようリクエストできる
    • あり得る攻撃は、GitHub URLを大量に収集した後、lookup APIへ多数のリクエストを送る方式である
    • サーバー実装は不明だが、work queueのような並列処理制限があるかもしれない
    • GitHub側の帯域保護が機能する可能性もある
    • ストレージ容量を対象にしたDoSの可能性もあるが、推測にとどまる
  • C2(command and control)proxy.golang.org 上に容易に構築できる
    • @latest クエリで特定モジュールの最新バージョンを見つけられる
    • ペイロードは単純なファイルでもよく、go.mod やGoソースファイルの中に隠すこともできる
    • 単一リポジトリの使用を避けるにはmodule DGAを使える

C2ダウンロードフロー

結論と残る疑問

  • Go checksum databaseとproxyは、文書化された手順に従い、未記録のモジュールをオリジンサーバーから取得して保存できる
  • 現在の状態がGoインフラの深刻な問題に見えるわけではないが、容易に悪用でき、改善の余地もある
  • Goコードではないリポジトリがproxyとchecksum databaseに載ることを許容している、文書化された理由または非公開の理由がある可能性がある
  • すでに誰かがこれを悪用しているか確認するには、約160万件の一意なリポジトリと、最新のローカルdatabase基準で約2,200万件のentryを調査する必要がある
  • 一部の有効なnon-Goプロジェクトがなぜdatabaseにあるのかは、まだ未解決の疑問として残っている

1件のコメント

 
GN⁺ 2024-05-26
Hacker News の意見
  • ユーザーが資料をアップロードし、その資料が公開されて見えるオンラインサービスは、結局コマンド&コントロール、著作権侵害、CSAM のホスティングに使われることになる
    ファイルホスティング以外にも重要な用途があってブロックしにくいサービスほど特にそうで、Twitter[1]、Telegram[2]、PGP 鍵インフラ[3]でもすでに起きているし、GitHub のような明らかな対象は言うまでもない
    [1] https://pentestlab.blog/2017/09/26/command-and-control-twitt...
    [2] https://www.blazeinfosec.com/post/leveraging-telegram-as-a-c...
    [3] https://torrentfreak.com/openpgp-keyservers-now-store-irremo...

    • Gmail、Google Groups、Google Drive、Gchat も同じで、保存したデータが公開されている必要すらなかった
      Gmail の場合、認証情報を配布してログインさせたうえで、IMAP でアップロード済みの添付ファイルを読ませていた
      元 Google SAD-SRE(Spam, Abuse, Delivery)だった
    • PyPI もパッケージに任意の非 Python ファイルを入れられるので、こうした用途に使いやすそうに見える
      Python コード文字列の中にファイルを Base64 エンコードして入れることも可能
    • すでにあったかは分からないが、あまり obvious でない対象は HuggingFace だと思う
  • Googler であり個人の意見だが、この分野には詳しくない
    Go チームが GCP や Drive 側と協業していたことを願う。悪意あるファイルホスティングは Google が常に扱っている問題だからだ
    Google がすでに人々に任意データの投入を許可している他のエンドポイントと大きくは変わらない

    • 元 Googler で、Go Dev Tools チームのことはよく知らないが、Google は私が働いた、あるいは親しい友人から聞いた巨大企業の中で、この種の社内協業をほぼ最も上手にやる会社の一つだ
      Google は中央チームがインフラを管理し、全社に共有することに非常に長けている。メッセンジャーアプリでなければそうだし、完全な推測だが、Go チームは社内の blob ストレージを使っていて、不正利用対応やファイルスキャンを自動処理する社内インフラチームもいるのではないかと思う
  • PyPI にも非 Python プロジェクトはかなりある
    Python ではユーザーがライブラリコードをコンパイルできない場合があるので、コンパイル済みバイナリである wheel を配布する能力が必要になる
    そうしたコードは C で書かれることが多いが Golang[1] でも可能で、例は見つけられないものの、ライブラリではなくアプリケーション配布にも使われていたのを見た気がする
    C でアプリを書いて PyPI に上げ、ユーザーに pip install でインストールするよう案内するのはかなりクールだ
    [1] https://github.com/popatam/gopy_build_wheel_example

    • たとえば Python 使用要件を入れたとしても、悪意をもって合わせにくる人は最小限のPython スタブコードだけ提供すればよいのではないかと思う
      Linux なのに ls が Python で書かれているようなものなので、こういうゲームはしないほうがよさそうだ
    • pip がパッケージをダウンロードするには venv/virtualenv/pipenv/pyenv のような環境内にいる必要がある、と要求するようになる前なら、この用途ははるかに有用だったと思う
    • pip install cmake もあるし、プロプライエタリなバイナリも pip install nvidia-cudnn-cu12 のように可能だ
    • 最近 PyPI を FFmpeg や Eigen のような非 Python ツールによく使っている
      おかげで Homebrew を完全に捨てられた理由の一つになった
  • 素朴な考えかもしれないが、これが GitHub リポジトリにファイルをアップロードすることとどう違うのか分からない
    違いは GitHub ではアカウントを作る必要があるという点だけなのか? GitHub にも任意のデータを保存できるし、500MB 制限もない

    • GitHub は匿名リクエストにかなり厳しいリクエスト制限がある
  • CUE のモジュールシステムがついにリリースされつつあり、MVS は Go と似ているが OCI インフラの上に作られている
    依存関係管理システムに関心があるなら参考になるリンクだ
    proposal: https://github.com/cue-lang/proposal/tree/main/designs/modul...
    custom registry: https://cuelang.org/docs/tutorial/working-with-a-custom-modu...
    road map: https://github.com/orgs/cue-lang/projects/10/views/8
    0.9.0-alpha-5 からモジュールがデフォルトで有効化された: https://github.com/cue-lang/cue/releases/tag/v0.9.0-alpha.5
    Go Sum では Trillian プロジェクトが透明性ログを支えている: https://github.com/google/trillian
    CUE は証明(attestation)のような OCI オプションに乗っていく計画だ

    • それがリンク先の記事とどう関係しているのか分からない
  • golang proxy と sumdb に便乗、つまり悪用して、任意 URL のチェックサムについて無料の透明性ログを作るアイデアで遊んでみた
    https://getsum.pub/

  • 自分が間抜けなだけかもしれないが、ここで正確に何が問題なのか分からない
    プロキシが Go ではないリポジトリをキャッシュするのは少し無駄かもしれないが、そうしなくても Go リポジトリをキャッシュさせれば、結局任意のデータを保存させられるのでは?
    何か見落としていない限り、完全に大したことではないように聞こえる

    • 見落としているものはなさそう
      ここでの新しい話は、セキュリティのない公開プロキシがプロキシ対象を受け取り、それをセキュリティのない形で公開提供する、という程度に見える
      記事では、一部の監視されたネットワークでは任意の Web URL より golang proxy URL のほうが信頼されやすく、レピュテーションフィルターの回避などに使える可能性があるとしているが、そうした方法はすでにいくつもあり、この方式が特別だとは思えない
  • 話題からは外れるが、ドメインを見てダジャレが不穏だったので put.as を確認してみたら、だいたい予想どおりの内容だった

    • https://put.as/ やや職場閲覧注意
    • 職場でそれを開くというミスをしてしまった
    • スペイン語の複数形の単語ではある。だが……
  • すでに知られている issue: https://github.com/golang/go/issues/31866

    • その修正はミス防止には役立つだろうが、意図的にやろうとする人ならルートに .mod.go ファイルを追加すればよいだけではないか?
    • Marwan がこの issue にいるのはまったく驚きではない
      彼と Aaron は Athens を作ったし、私の知る限り Marwan は Athens の基盤となった最初の Go ダウンロードプロトコル実装を書いた
      この issue が興味深いのは、Athens がすでにモジュール検証の事前確認として言及されている go mod download -json コマンドを使っている点だ
      概ね、リポジトリが Go モジュールコマンドの理解するモジュールとして通れば、Athens が提供する
      もっと禁じ手っぽい言い方をすれば、モジュールバージョン・疑似バージョン・+incompatible を作れる必要があり、そのモジュールと依存関係が有効なチェックサムを生成しなければならない
      モジュールのチェックサムは現在、.mod とすべてのファイル、さらに各依存関係を再帰的に含めることに関係している
      そのため筆者が言うように、基本的な Go プログラムさえあれば、設計上、任意ファイル用の領域を大量に持てる
  • W3C は Web 上のあらゆるものを強力にキャッシュできるよう基盤を整えたのに、汎用プロキシキャッシュがこれほど少ないのは奇妙だ
    パブリッシャーが必要もないのに短い Cache-Control: max-ageVary: Cookie レスポンスを送っているのだろうか?
    ISP はピアリングよりもトランジット費用を払いすぎているのだろうか?

    • 一般に、キャッシュが内容を改ざんしていないことを保証する方法がない
      たとえば HTTPS ではないサイトでは、ISP のプロキシが広告を挿入できる
      ソフトウェアのダウンロードには通常、署名やチェックサムがあるが、任意のコンテンツにはそうしたものはあまりない