PaPoo
cover
technews
Author
technews
世界の技術ニュースをリアルタイムでキャッチし、日本語でわかりやすく発信。AI・半導体・スタートアップから規制動向まで、グローバルテックシーンの「今」をお届けします。

257文字で開く扉、その手前で起きていたこと

この記事は後半です。前半は Qiita にあります: 257文字の沈黙――OpenSSH と musl が見落とした * の行方

失敗の印が、そのまま一致してしまう

画像: github.com より
画像: github.com より

この件の妙は、認証の入口が意外なほど単純なことにあります。OpenSSH は、入力されたパスワードをそのまま保存値と比べているのではなく、同じ salt を使って crypt() でハッシュ化し、その結果が /etc/shadow の値と一致するかを見ています。保存されている値が $6$... なら普通のハッシュ比較、* なら本来は誰とも一致しない、という前提です。

ところが musl では、crypt() が失敗したときに * を返します。OpenSSH は PAM なしの経路で、比較のために別アカウントの salt を借りることがあります。ここで長すぎる入力を与えると musl 側の crypt() が失敗し、返り値が * になる。比較対象も * なら、文字列比較は通ってしまいます。拒否の印と失敗の印が同じ形をしていたわけです。

256 までは拒まれて、257 から抜ける理由

境目が 257 文字なのは、OpenSSH 側が入力長を 1024 文字まで受ける一方で、musl の SHA-512 系 crypt() が 256 バイトを上限にしているからです。256 までは通常のハッシュ計算として扱われ、* には落ちません。257 文字を超えると crypt() が失敗し、* を返す。その結果、保存値が * のアカウントでは一致判定になってしまう、という構図です。

だからといって「257 文字なら何でも root になる」という話ではありません。条件はかなり絞られます。musl を使っていて、PAM が無効で、PasswordAuthentication yes の構成で、なおかつ対象アカウントのパスワード欄が * であること。root、鍵専用ユーザー、LDAP ユーザー、あるいは Alpine の一部クラウドイメージのように、ふだんはパスワードを使わないアカウントが狙われます。

なぜ十年近く沈黙していたのか

見逃された理由は、派手なバグだからではなく、条件が薄く重なっていたからです。まず OpenSSH の検証は OpenBSD と glibc 寄りで、musl の sshd 経路は主戦場ではありませんでした。さらに PAM を使う構成では、この * のすれ違い自体を踏みにくい。glibc の一般的なディストリビューションではそもそも再現しません。

加えて、影響を受けるのは「* が入ったアカウント」に限られます。しかも長い入力を送らないと発火しないので、普通のログイン失敗では気づきにくい。公開された説明でも、誰かが長い文字列を誤って貼り付けた瞬間にだけ、たまたま通ることがある、とされています。静かに起きるタイプの事故です。

実際に困るのは、root だけではない

この問題は root の専売特許ではありません。鍵専用の運用にしていたユーザー、LDAP 連携のアカウント、初期状態のクラウドイメージのユーザーなど、パスワードを持たせていない相手全般が対象です。GitHub の公開リポジトリでは、再現用の手順として Alpine の通常構成で alice が、さらに条件次第で root も通ることが示されています。

注意したいのは、PasswordAuthentication yes がデフォルトである点です。つまり「何もしていないから安全」とは言えません。逆に、PasswordAuthentication no にしてしまえば、この経路は閉じます。すぐに更新できない環境では、そこが最短の回避策になります。OpenSSH 10.6p2 はこの件を閉じる修正を入れており、公開リリースノートでも musl と PAM 無効の組み合わせが対象だと明示されました。

自分なら、まず何を見るか

自分なら、まず sshd がどの libc で動いているかを確認します。次に PAM の有無、そして PasswordAuthentication の設定です。そのうえで、* のまま残っているアカウントがないかを見る。鍵だけで運用しているつもりでも、裏で PasswordAuthentication yes のままになっていると、境界が開いたままになるからです。

この手の問題は、派手な暗号突破ではなく、文字列の扱いが一段ずつ噛み合った結果として起きます。だからこそ、設定を一つ変えるだけで止まるし、逆に条件がそろうと長いあいだ沈黙します。* は記号としては小さい。でも、そこに別々の実装が同じ意味を載せてしまった瞬間、扉になってしまった。

同じ著者の記事