4 ポイント 投稿者 GN⁺ 2024-06-24 | 1件のコメント | WhatsAppで共有
  • sudodoassetuid バイナリ と権限昇格に依存するため、ローカル sshd を Unix ドメインソケットに束縛し、root コマンド実行を任せる実験である
  • 目標は、許可されたユーザーだけが root コマンドを実行できるようにしつつ、ユーザーセッション全体に権限昇格能力を残さないことにある
  • 実装は、root 専用 SSH キーファイル、アクセス制限された /run/sshd/sshd.sockAuthorizedKeysFilePermitRootLogin=yes オプションを持つ別個の sshd インスタンスで構成される
  • ssh には既存ソケットを直接渡すオプションがないため、当初は ProxyCommandsocat を使い、その後 ProxyUseFdpass と短い Python スクリプトでソケットのファイルディスクリプタを渡すようにした
  • この方式は動作し、セキュリティ処理の大半は OpenSSH に依存するが、日常利用では passfd.py を小さな実行ファイルにし、ssh コマンド全体をラッパーで包むのが適している

sudodoas が持つ構造的制約

  • sudodoas は、コマンドを root として実行するために setuid バイナリと 権限昇格 に依存している
  • この設計にはいくつかの限界がある
    • ユーザーセッション全体が権限昇格を実行できる能力を保持しなければならない
    • 制限されたユーザーネームスペース内でユーザーセッション全体を実行する場合に動作しない
    • setuid バイナリはシステム全体のセキュリティ構成に制約を与える
  • s6-sudod は、権限のあるサーバーと権限のないクライアントにプログラムを分ける代替案である

実験の目標

  • ローカルで ssh を使って sudo のような役割を果たさせるが、その sshd インスタンスはネットワークに公開しない
  • 中核となる条件は 2 つある
    • 許可されたユーザーだけ が root としてコマンドを実行できる
    • 権限昇格を使わない

ローカル root ログイン用 sshd 構成

  • root 認証にのみ使う専用 SSH キーを作成し、通常の authorized_keys ではなく /root/.ssh/local_keys に保存する
  • 別個の sshd インスタンスを Unix ドメインソケットにバインドし、許可されていないユーザーがソケットにアクセスできないよう /run/sshd/ の権限を制限する
  • 実行例は s6-ipcserver/run/sshd/sshd.sock を作り、sshd に次のオプションを渡す形である
    • AuthorizedKeysFile=/root/.ssh/local_keys
    • PermitRootLogin=yes
  • /etc/ssh/sshd_config は変更しない
    • ネットワークにバインドされた既存の sshd で root ログインを許可したくないためである
    • 既存設定では PermitRootLogin no を維持する

root アカウントのロックとパスワードログイン処理

  • root アカウントは、どの方法でもログインできないようロックされており、これはパスワードハッシュの先頭に ! を付ける方式だった
  • sshd はこの ! 接頭辞をアカウントロックと解釈し、root ログインを許可しない
  • /etc/passwd の root パスワード値を変更し、!* に置き換える
    • sshd* を特別なロック値として解釈しない
    • * はどのパスワードハッシュとも一致しないため、パスワードログインは事実上無効のままになる
  • 別途 sshd_config では PasswordAuthentication no を設定しており、sshd 側でパスワードベース認証を無効にする構成が安全である

ssh で Unix ソケットに接続する

  • sshd には既存ソケットを渡せる -i フラグがあるが、ssh には同じ役割のフラグがない
  • 当初は ProxyCommandsocat を使って /run/sshd/sshd.sock に接続した
  • 接続コマンドは次の要素で構成される
    • ProxyCommand='socat STDIO UNIX-CONNECT:/run/sshd/sshd.sock'
    • 専用 root キーを -i .ssh/root-key.pub で指定
    • root@root に接続
    • 現在のディレクトリへ移動した後、ログインシェルを実行
  • 使用した SSH キーは ハードウェアバインドキー であり、接続承認のために物理デバイスをタップする必要がある
  • 明示的な承認後にのみキーを公開する ssh-agent、たとえば hissh-agent も代替になりうる

socat のオーバーヘッドと ProxyUseFdpass

  • socatssh のすべての入出力を中継してからソケットに書き込むため、接続オーバーヘッドが実質的に重複する
  • ProxyUseFdpass は、コマンドがソケットのファイルディスクリプタを stdout 経由で ssh に渡し、ssh がそのソケットに接続する仕組みである
  • 2016 年の OpenSSH ProxyUseFdpass 使用例 をもとに、Python スクリプト passfd.py を作成した
    • Unix ドメインソケット /run/sshd/sshd.sock に接続する
    • sendmsgSCM_RIGHTS でファイルディスクリプタを渡す
  • 以後の接続コマンドは、ProxyCommandpassfd.py を指定し、ProxyUseFdpass=yes を追加する形になった
  • nc -FU /run/sshd/sshd.sock も使えそうだが、マニュアルでは -F-U と一緒に使えないと明記されている

結論と実運用の形

  • この手法は動作し、センシティブなセキュリティ処理の大半は OpenSSH に任せられる
  • OpenSSH は良好な実績を持ち、ハードウェアベースの SSH キーを含む複数の認証方式を利用できる
  • 新しいホストで設定する過程にも複雑な手順はなく、ipcserver コマンドはシステムのサービスマネージャー経由で実行できる
  • passfd.py は実験を進めるための手早いハックに近い
  • 日常利用では、同じ役割を担う小さな実行ファイルを作って /usr/local/bin に置き、ssh コマンド全体も小さなラッパーで包む方式のほうが適している

1件のコメント

 
GN⁺ 2024-06-24
Hacker Newsのコメント
  • この方式への最大の反対理由は、複雑さの増加であること。設定ファイルを読み込んで exec() を呼ぶ単一の suid バイナリの代わりに、root として動作して UNIX ソケットを待ち受けるバイナリ1つと、そのソケットに接続するバイナリ1つが必要になり、しかも両方が非対称暗号まで処理しなければならない
    sudo/doas に対する中核的な反論が「すべてのユーザーがアクセス可能な suid バイナリがあり、バグがあれば権限昇格に悪用されうる」というものなら、次のようにすればよい
    chgrp wheel /usr/bin/sudo
    chmod o-rwx /usr/bin/sudo
    sudo 利用者を wheel グループに入れておけば、sudo 可能なユーザーだけがディスク上のファイルのバイト列を読み取れ、実行もできる。UNIX ソケットを wheel ユーザーだけが利用可能にする sshd 方式と、アクセス制御の観点ではほぼ同等の安全性だが、複雑さははるかに低い
    そのうえ sshd 方式では sudo のように root アクセスを特定のコマンドに制限できないため、たとえ sudo のコマンド制限を回避するバグがあったとしても、sshd 方式以上の権限を与えることにはならない
    パッケージマネージャーが /usr/bin/sudo の権限を壊してしまうのが心配なら、cron で定期的に修復させるか、sudo を完全に削除してソースから別の場所へ手動インストールすることもできる。もちろん、その場合は保守とアップグレードも自分で行う必要がある

    • 個人的には etckeeper/etc のすべての変更を追跡している。ソフトウェアのインストールやアップグレードでも、人間が行った変更でも記録され、新しいリリースへアップグレードするときもローカル変更分のパッチを作ってクリーンインストールに適用し、その後 3-way マージで競合を確認できるので便利
      https://etckeeper.branchable.com/
    • sudo 利用者を wheel グループに入れて、sudo 可能なユーザーだけがファイルを読んで実行できるようにするのはかなり合理的。なぜこれがどこでもデフォルト設定ではないのか気になる
    • 無知を許してほしい。wheel グループ とは何で、何をするものなのか説明を聞きたい。これが複雑な議論を呼びうることは分かっている
    • /usr/bin/sudoimmutable にすることもできるのではないか? そうすればパッケージマネージャーが触るのを防ぐ助けになりそう
    • 特定のコマンドへ root アクセスを制限できないという点については、ForcedCommand の仕組みがある
  • これは今の systemd run0 がやっていることではないのか? systemd には run0 という新しいツールがあるが、実際には完全に新しいツールというより、以前からある systemd-runrun0 という名前のシンボリックリンク経由で呼び出すと sudo の代替のように振る舞う仕組み
    中核的な違いは、これが実際には SUID ではない点。サービスマネージャーに対して対象ユーザーの UID でコマンドやシェルを実行するよう要求し、新しい PTY を割り当てたうえで、元の TTY とその PTY の間でデータを中継する
    つまり対象コマンドはクライアントのコンテキストを継承せず、PID 1 から新たに fork された隔離済みの実行コンテキストで動作する。$TERM は渡すが、これは明示的な例外であり、ブロックリストというより許可リストに近い
    多くの点で run0sudo より ssh の挙動に近いと言える
    https://mastodon.social/@pid_eins/112353324518585654

    • なぜ systemd は何でも作り直さなければならないのだろう?
  • 何か見落としているのだろうか? root での ssh ログイン が sudo を使うよりどう安全なのか分からない。常に許可すべきではないと教わってきたので、実際どれほど危険なのかもよく分からない
    ここではリモートユーザーを防ぐための話が入っているようなので、そのセキュリティ面を言っているのではない
    sudo の限界の3番目に関係するのかもしれないが、少なくとも1番目と比べて利点が見えない
    実験だというのは理解できるが、sudo より脆弱性が少ないというより、むしろ脆弱に思える。開いたソケットプロキシは中間者攻撃に弱そうに見える
    それでも古いツールでできるいくつかの技術は学べたし、新しいものを見せてくれた点はよかった

    • sudo バイナリは suid root であり特権バイナリなので、信頼できないユーザーに直接さらされる。sudo の内部で何か問題が起きれば、ユーザーの環境全体が攻撃面となって悪用されうる
      ssh 方式は suid バイナリを露出させず、ssh のネットワーク層を使う。したがって、ネットワーク経由で ssh にアクセスされる場合より安全性が低いわけではなく、これはかなり安全と見なされている
    • sudo の大きな利点の1つは、単に sudo bash のようなものを起動するのではなく、個別のコマンドを sudo で実行して 監査可能性 を高められること
    • root で ssh ログインするというのは、記事では Unix ソケットを待ち受ける追加の SSH サーバーを指している。インターネットに root ログインを公開するときの一般的な脅威モデルは、ここには当てはまらないかもしれない
    • この方式は、理論上の設定ミスや存在するかもしれないし存在しないかもしれない脆弱性と、新しいデーモンを動かすことによる新しい攻撃面を比較している
      そのデーモンにも設定ミスや脆弱性がありうるし、ユーザーベースの認可レイヤーをいくつか単一の root レベルへと減らしてしまう
      それにもかかわらず、より安全だと見なされているようだ。合理的なセキュリティの観点では、より安全とは言えず、単に別のやり方にすぎない
    • リンク先の記事のアプローチには懐疑的だが、「root でのリモート SSH 直接ログインは危険だ」という言説には、恐怖・不確実性・疑念に流されている面がある
      実際には、リモート SSH でユーザーとしてログインしてから sudo を使うより、最初から root で SSH ログインする方が厳密には安全である
      user@homeroot@server に ssh する場合、root@server が侵害されるのは user@home が侵害されたときだけ
      一方で user@homeuser@server に ssh してから sudo で root@server になる場合、user@home または user@server のどちらか一方が侵害されるだけで root が侵害される。特に user@server ではデーモンや cron ジョブのような別のソフトウェアがよく動いている
      そうした経路で侵入に成功した相手に、ただで root 権限昇格を与えるべきではないし、パスワード再利用によってしばしば起こるラテラルムーブメントまで許すべきではない
      もちろん、sudo が許可リストにあるコマンド専用モードで使われており、パスワードやリモートホストから完全にアクセス可能な認証情報を受け取らない場合はこの限りではない
  • 起動時に ssh が開始されなかったらどうなるのか? 一般的な構成ではネットワークに依存して起動すると記憶している。そうなると failsafe コンソールでもログインできない
    sudo や su と比べて実際に何が得られるのか分からない。setuid バイナリを避ける代わりに、root 権限でネットワークサービスを、たとえソケットにしか接続しないとしても、実行することになる

    • 似たような構成を使っている立場から言うと、自分のメインデスクトップではこうしている。最悪中の最悪になっても、すべてバックアップがあるのでシステムを再インストールする
      SSD が壊れたらどうする? そのときも failsafe コンソールにはログインできない
      30年間 Linux を使ってきて、ハードディスクが壊れたことは sshd デーモンが起動しなかったことよりはるかに多く、比率で言えば 0 で割るようなものだ
      OS の sshd デーモンがランダムに起動しないのなら、もっと安定した OS に移るべきだというサインだと考える
      sudo や su より得られるのは、ローカル権限昇格エクスプロイトがはるかに難しくなる点だ
    • Linux コンソール、つまりローカルディスプレイやリモート KVM に表示される tty、シリアルポートや IPMI SoL の ttyS* デバイスでは sudo や su は使わない
      こうしたコンソールでは getty のようなプログラムやウィンドウマネージャを使い、これらは root で起動される非 suid プログラムだ
      コンソールログイン用の root パスワードは設定しておくべきだ
    • 自分の場合、setuid/sudo を使うのは 監査ログのためだ。今ではマルチユーザー・マルチサービスのボックスはほとんど運用しておらず、マルチテナントなものの大半は k8s なので ssh の代わりに kubectl エンドポイントを使えばよい
      ログインできるなら root に setuid できるようにしている。k8s ボックスならそれはプラットフォームインフラチームで、その上のサービスへのアクセスは k8s 権限プロバイダ経由だ
      プラットフォームインフラチームの観点で指標やログだけが必要なら、すでにボックスの外にあるし、何らかの作業やワークフローをトリガーする必要があるならパイプラインを使えばよい
      それでも誰かがログインして root 作業をするなら、監査ログは残したい
      自分が持っているボックスで、ログイン権限がある人が完全な root 権限を持たないケースは思い浮かばない
      もちろんサービスが setuid するケースは理解できるが、サービスの場合は通常 systemd が権限を上げるためではなく下げるために setuid を行う
    • ブートローダにアクセスできるなら、依然として systems.unit=emergency.targetinit=/bin/bashrd.break=pre-pivot を設定したり、live CD 環境で起動したりできる。一般的な 緊急復旧オプションはすべて使える
      それほど致命的でない緊急時なら、この sshd インスタンスがネットワークにバインドされている必要もないように思う
  • このアプローチの欠点がすべて含まれていないのは少し残念だ。sudo なら、どのグループがどのコマンドを実行できるか、そのコマンドがどんな引数を受け取れるか、サブシェルの生成を許可するかどうかなどを制御できる
    この方式ではそうした きめ細かな制御の多くが失われ、sudoers ファイルの編集より管理しづらい信頼鍵に依存することになる
    sudo ができる驚くべき機能を知るには、Sudo Mastery の本を本当に勧めたい

    • SSH でも ForceCommand でその一部はできるが、それほど柔軟でも精密でもないという点には同意する
  • これは Systemd の run0 に近い発想だ: https://news.itsfoss.com/systemd-run0/

    • そして run0 は自作の場当たり的な解決策ではない。監査も受けており、自作のものよりおそらく良いだろう
  • ssh の問題の1つは、プロセス生成がプロトコルの一部ではないことだ。そしてリモートプロトコルなので、ローカル資源を子プロセスに渡せない
    そのため、null 区切りの引数配列を渡したり、追加のファイルディスクリプタを渡したり、実行ファイルを指定したりできない
    代わりにサーバーに設定されたシェルへ文字列1つを渡す。するとシェルエスケープが必要になり、サーバー側でどのシェルが動いているかも知っていなければならない
    SSH をまともな sudo 代替として使うには、拡張として posix_spawn に近い機能が必要だ

  • 完全に同意する。自分も似たようなことをしていて、以前 HN のコメントでも説明したことがある
    自分のやり方は少し違う。物理的な SSH コンソール用に専用マシンを使っていて、このマシンは家のほかのマシンとは分離されたプライベート LAN 上にある。管理スイッチではなく普通のスイッチを使い、Ethernet ケーブルを使い、トランクはない
    ログインは SSH でしかできず、ここに Yubikey を付けている
    デスクトップ PC は独自のファイアウォールを持っていて、この SSH コンソールの IP/MAC アドレスから来る SSH トラフィックだけを許可している。これは両者が共有するプライベート LAN 上でのみで、別の物理 LAN ではデスクトップはインターネットにアクセスできる
    sshd デーモンは公開鍵/秘密鍵ログインのみを許可し、パスワードログインは無効にしてある
    root が必要なら「SSH コンソール」を起動する。ほとんど何も入っていないマシンなので非常にすばやく起動する。ログインして上矢印で ssh root@... の行を呼び出し、Enter を押して Yubikey をタッチする
    その SSH コンソールとキーボードは机の上にあり、常に手の届くところにある
    プライベート LAN、それもほかのプライベート LAN と物理的に分離された場所にある iptables/nftables + sshd が sudo バイナリや su より安全かどうかは、各自で判断すればよい
    「なぜ」と聞かれたら「できるから」と答えるだろう。かなり前に設定したので、いつやったのかも覚えていない。たぶん2年ほど前にこのアイデアをいじり始め、それ以来ずっと使っている。問題はまったくなかった

    • 持っている構成はまったく同じではないにせよ、バスチオンホストの概念に近いように見える
  • この問題に対するエレガントな解決策だ。ユーザーを子どものように扱う必要はないが、同時に合理的なデフォルトで潜在的なミスを避けるべきだ
    root が必要ならコンソールで root ログインすればよいので、su すら不要だと思う。この方式はコンソール tty で root としてログインすることにできるだけ近い

    • root が必要ならコンソールで root ログインしろ、という方式には2つの問題がある
      1つ目は、sudo と違ってすべてのユーザーが root パスワードを知っていなければならないこと
      2つ目は、全員がそのまま root でログインしてしまうと、実際に誰がログインして何をしたのかを監査する方法がないこと
  • 10年前に似たようなことをやったことがある。UNIXソケットの部分はなく、localhostでのみ待ち受ける別個のsshdを立ち上げていたので、SCM_RIGHTSを扱う必要もなかった
    特に良くも悪くもならず、単に興味が薄れて次のマシンへ設定を移さなかった