1 ポイント 投稿者 GN⁺ 5 시간 전 | 1件のコメント | WhatsAppで共有
  • ssh-init-vm は、新しいVMへの初回SSH接続で 中間者攻撃 を防ぐため、cloud-init で一時的なSSHホスト秘密鍵を注入し、長期ホスト鍵を生成・取得している間だけそれを信頼する
  • Hetzner Cloud のように専用の接続保護機能がないVPSやクラウドでも動作し、必要なのは広くサポートされている cloud-init だけ
  • 一般的な Trust On First Use では、ssh の “The authenticity of host [...] can't be established” という質問に yes を入力すると、攻撃者がトラフィックをプロキシしたり、ユーザーのVMに見せかけたマシンを提供したりできる
  • 長期SSHホスト秘密鍵を cloud-init userdata に直接入れると、初回接続の認証には役立つが、メタデータサービス・SSRF・クラウドプロバイダーのシステム・管理者ワークステーションを通じて 機密性の高い鍵情報 が露出する可能性がある
  • ssh-init-vm は一時鍵を一時ディレクトリに置き、~/.ssh/known_hosts には入れず、VMの出力をそのまま保存せずに OpenSSH の ホスト鍵ローテーション に依存して長期鍵を記録する

cloud-init userdata の露出問題

  • cloud-init で長期SSHホスト秘密鍵を注入すれば、公開鍵を ~/.ssh/known_hosts に入れて初回接続を認証できるが、秘密鍵が複数の経路で漏えいする可能性がある
  • VM内部の任意のプロセスが、通常読み取れる メタデータサービス から userdata を取得できることがあり、Hetzner VM では http://169.254.169.254/hetzner/v1/userdata で cloud-init の内容が見える場合がある
  • 攻撃者は SSRF によってプロセスにメタデータを漏えいさせることができ、この種の遮断は専用保護機能がある環境でも 常に適用されるとは限らない
  • クラウドプロバイダーの他のシステムでも userdata が露出する可能性があり、Hetzner はサーバー作成APIの文書で “passwords or other sensitive information” を保存しないよう明記 している
  • 管理者ワークステーションも cloud-init userdata が残ったり通過したりする場所になりうるため、長期秘密鍵を入れる方式は、その鍵が有効な間ずっと露出リスクを生む

セキュリティ分析と脅威モデル

  • 前提は OpenSSH のプロトコルと実装 を信頼し、管理者が攻撃を検知できる能力には依存しないこと
  • ネットワーク攻撃者に対する保護

    • 保護対象は管理者ワークステーションの完全性とVM
    • 攻撃者はネットワークを完全に制御する中間者であり、スクリプトが成功または失敗して終了した後のどこかの時点で cloud-init userdata を知りうる
    • 攻撃者は価値のある時点で鍵情報を知らないため、保護が成立する
    • スクリプト は、一時SSHホスト鍵の偶発的な使用を防ぐためにそれを一時ディレクトリに保管し、一時SSHホスト鍵を ~/.ssh/known_hosts に入れない
  • 管理者ワークステーションが侵害された場合

    • 保護対象はVMとVMの長期SSHホスト秘密鍵に限定される
    • 攻撃者はネットワークと管理者ワークステーションを完全に制御するが、実際のVMには接続しないと仮定する
    • 長期SSHホスト秘密鍵は管理者ワークステーションに存在したことがなく、攻撃者も実際のVMに接続しないため、VMの長期ホスト鍵を取得できない
    • 攻撃者が実際のVMに接続できれば、ssh root@<VM> cat /etc/ssh/ssh_host_* のような方法でSSHホスト鍵を知れる可能性が高い
  • VMまたはプロバイダーが侵害された場合

    • 保護対象は管理者ワークステーションの完全性に限られる
    • 攻撃者はネットワークを完全に制御し、VMまたはプロバイダーも完全に制御できる
    • この場合でも、OpenSSH が安全であるという前提により、管理者ワークステーションの完全性は保護される
    • 追加防御として、スクリプト は VM の出力をそのまま ~/.ssh/known_hosts に書き込まず、OpenSSH ローテーション に依存して長期SSHホスト鍵を追加する
    • この方式により、侵害されたホストが known_hosts パーサーに悪意あるデータを食わせることを防ぎ、VMが実際に 制御している鍵 だけが ~/.ssh/known_hosts に記録される
    • HashKnownHosts のような OpenSSH オプションや、将来の関連オプションも正しく処理できる

実際に中間者攻撃が成立する条件

  • 中間者攻撃が成功するかどうかは、ユーザーが最初からすべての接続先が誤ったマシンだったと実際に気づくか、パスワード入力を拒否するか、ssh の agent または X11 転送を設定するかに左右される
  • ssh-mitm に基づく単純化した条件では、攻撃者が本物の対象ホストではなく攻撃者制御のマシンへのアクセス権を提供してユーザーを欺けるなら、成功する可能性が高い
  • 攻撃者がユーザーをだまして本物のホストにログインできる情報を得られれば成功する
    • ユーザーが攻撃者のマシンに パスワード でログインすると、攻撃者は成功できる
    • ユーザーが何らかの認証方式でログインした後、プロンプトでパスワードを入力すると、攻撃者は成功できる
    • ユーザーが何らかの認証方式でログインし、ssh-agent 接続 を転送すると、攻撃者は成功できる
  • これらの条件がなければ、攻撃者はユーザーをだますために本物のホストへのアクセスが必要になるが、ユーザーの入力だけでは本物のホストにログインできず、失敗する可能性が高い
  • ユーザーが X11 接続を転送すると、攻撃者は認証方式に関係なく管理者ワークステーションへの攻撃にも成功する可能性がある

1件のコメント

 
GN⁺ 5 시간 전
Lobste.rsのコメント
  • 自動化できるのがすばらしい。手動では、クラウドプロバイダーのコンソールでサーバーのSSHフィンガープリントを別チャネルで確認していた
    管理しているクラウドインスタンスはそれほど多くないので、プロビジョニングに手動の手順がいくつかあっても問題ない

    • その方法でも機能する。ただ、使っていたプロバイダーではその部分が連携されておらず、同時に設定自動化スクリプトを作っているところだった
  • DNSゾーンを自動化しているなら、別のアプローチも可能: 非常に限定された使い捨てトークンを作り、たとえば my-server-hostname.example.net にレコードを1つ作成することだけを許可する
    そのトークンを cloud-init でサーバーに渡し、サーバーが公開SSH鍵をDNSのSSHFPレコードとして登録する。その後、SSHクライアントにSSHFPレコードを自動検証させることができ、DNSゾーンはDNSSEC署名されている必要がある
    この流れなら、サーバーは秘密SSHホスト鍵を保持したまま、鍵ローテーションを避けられる。ほとんどのDNSプロバイダーはこのような細かい使い捨てアクセストークンをサポートしていないが、トークンを検証したあと、永続的でスコープ制限のないトークンを使って代わりにAPI呼び出しを行う簡単な社内Webサービスを置くことはできる。SSHサーバーはその永続トークンにはアクセスできない

    • 創造的な方法で、カスタムサービス用の使い捨てトークンならかなり柔軟でうまく機能しそうだ
      ただ、DNSSECドメインに書き込むよりはSSH証明書を生成するほうを好みそうで、そこから先はどの解決策が特定の環境により適しているかの問題になる
      こうした柔軟なトークンを生成できるソフトウェアやプロバイダーを知っているのか、あるいはある程度自前で開発する必要があるのか気になる
  • かなりきれいだ。ただ、記事の日付で月と日が入れ替わっているように見える

    • OpenSSHで作業するのはいつも楽しくて、月/日の表記は修正しておいた
  • 以前、同じSSHの鶏と卵問題を探るために実験的なサービスを作ったことがある
    リクエスト時にSSHFP DNSレコードを作成してくれるもので、興味があれば https://github.com/tedb/sshfp をどうぞ

  • VMプロバイダー間である程度一貫した169.254.169.254 メタデータサービスを扱ってくれているのはうれしい。cloud-init ソースの各種 cloudinit/source/DataSource*.py を見ると確認できる
    個人的には、cloud-init の設計と限界のせいでだんだん疲れを感じている。ローカルのQEMU仮想マシン、リモートマシン、コンテナ、物理ハードウェア全体でシステム設定を統一することに関心がある
    arch-boxes project は ArchLinux の cloud-init image がどう作られているかを示しており、非常に単純なシェルスクリプトの集まりだ。この方法を guestfishµvm で応用すれば、OCI互換イメージ、QEMUやクラウドプロバイダー向けのディスクイメージ、新しい物理マシンのプロビジョニングにまったく同じスクリプトを使える
    いくつかのQEMUフラグを組み合わせれば、cloud-init への依存なしに同じアプローチを再現できる。知る限り、systemd.system-credentials では一時的なホスト鍵を渡すことはできず、ssh.authorized_keys.root のような ~/.ssh/authorized_keys 向けの認証情報しかない
    代わりに、initrd段階で実行される、あるいは systemd-firstboot.service と一緒に実行されるunitファイルを作成できる。このunitファイルはイメージにあらかじめ入れておくか、systemd.extra-unit.* 認証情報で一時注入し、systemd.wants=… カーネルコマンドラインオプションで有効化できる。QEMUでは -netdev user,id=metadata,net=169.254.0.0/16,dhcpstart=169.254.0.15,guestfwd=tcp:169.254.169.254:80-cmd:… でメタデータサービスの存在をエミュレートできる。ただし生成されたインターフェースを有効化する必要がある可能性が高く、これも一時的なunitファイルで処理したほうがよいかもしれない
    こうすることで、複数種類の「マシン」で一貫したシステム設定を行う際に、比較的低い複雑さでかなりの柔軟性が得られる。実際、この作業だけを見るなら、イメージ生成ツールが固定ホスト鍵入りのマシンイメージを作り、初回再起動またはシャットダウン時に実行されるカスタムのホスト鍵ローテーションスクリプトをSystemDサービスとしてインストールする方式が最良に思える

    • ArchLinuxでは /etc/mkinitcpio.confsystemd HOOK を有効にすると、initrd で実行するためのSystemD unitファイルを書けるし、実質そうすべきだ
      実際に使ってみると、{/etc,/usr/lib}/initcpio/hooks を書くよりほんの少し面倒なくらいだ
      しかし initrd で systemd-networkingsystemd-resolved を有効にするのはかなり簡単なので、initrd がシステム起動の責任を持ち、ルートファイルシステムへ切り替わる前に作業を予約できる
      もちろん、ノートPCのような物理ハードウェアでは Wi‑Fi 接続に NetworkManager のようなものが必要になり、あまり適さないかもしれないが、QEMU VM やホスティングVMにはよく合い、多くのシステム起動処理がこの領域に自然に収まる
      目標は、cloud-init に依存せず、特定のクラウドプロバイダー1社に縛られず、物理マシン・コンテナ・ローカルVM・ホスティングVM全体で一貫性を得て、依存関係を事実上SystemD程度まで減らすことだ
    • 必要な機能だけを拡張していけるごく小さなツールとしては、https://github.com/the-maldridge/shinit/ のようなものも使えるかもしれない