Alpine LinuxのOpenSSHパスワード認証バイパスとは — musl crypt()の「*」が招いた穴と対処法

はじめに

2026年11月5日、Alpine Linux が OpenSSH サーバーのパスワード認証バイパスに関するセキュリティ勧告を出し、同日に OpenSSH 10.6p2 が「重大な回避策」としてリリースされました。Hacker News では勧告が 360pt 超、発見者の解説サイト sshhole.com が 1,600pt 超を集めています。

条件がそろうと、パスワードを設定していないアカウントに、257〜1024バイトの長い文字列を送るだけでログインできるという内容です。本記事では、何が起きたのか、なぜ musl と「PAMなし」の組み合わせだけで起きたのか、そして手元の環境が影響を受けるかをどう確認するかを整理します。

何が起きたのか:長いパスワードが「*」と一致する

Alpine の勧告によると、仕組みは次のとおりです。

PAM を使わずにビルドされた sshd は、入力されたパスワードを crypt() でハッシュ化し、/etc/shadow に保存された値と文字列比較して認証します。一方、musl libc の crypt() は、256バイトを超える鍵を渡されると、エラーの印として文字列 * を返します。

そして * は、パスワードを持たないアカウント(鍵認証専用のユーザーなど)の shadow フィールドに入っている値そのものです。つまり「ハッシュ計算に失敗した結果」と「保存値」が一致してしまい、ログインが通ります。

攻撃の条件として、勧告は次の点を挙げています。

  • パスワードフィールドが * のアカウント名を攻撃者が知っていること
  • 同じマシンに、$5$(SHA-256)または $6$(SHA-512)のハッシュを持つアカウントが少なくとも1つあること
  • 257〜1024バイトのパスワードを送ること

2つ目の条件が効いてくる理由は、sshd の設計にあります。勧告によると、保存値が短くてソルトを取り出せないアカウントでは、sshd は別のアカウントのソルトを借りてハッシュ計算を行います。発見者の解説(後述)は、この挙動の由来を 2016 年のユーザー列挙対策(CVE-2016-6210)としています。存在しないユーザーでも本物と同じようにハッシュ計算を行い、応答時間の差から実在ユーザーを推測させないための処理です。同解説によれば、256 バイトの上限は musl の SHA-256/512 実装に入っているため、借りたソルトがこの方式のときに * が返ります。勧告は「パスワードを持つユーザーが1人いれば、パスワードの無いアカウントがすべて到達可能になる」「毎回成功し、リトライ不要」と述べています。

なお、! でロックされたアカウント(user:!:...)は * と一致しないため対象外です。

技術的な背景:3つの「正しかった判断」の組み合わせ

発見者 Özgün Kültekin 氏の解説サイト sshhole.com は、この穴を十数年にわたる複数の変更の積み重ねとして描いています。同サイトの記述に沿うと、流れは次のとおりです。

  1. 2012年、musl の crypt() が失敗時に NULL ではなく * を返すように変更。コミットには、エラーで失敗させるより照合不能な値を返して「成功」させる方が良いとされた、という趣旨の説明があります。同じ月に追加された SHA-256/512 実装には、DoS 対策として鍵長 256 バイトの上限が入りました
  2. 2016年、OpenSSH が存在しないユーザーにも他人のソルトでハッシュ計算するよう変更(CVE-2016-6210 のユーザー列挙対策)。sshhole.com はこれが「* を持つ実在ユーザー」にも適用されていたと指摘しています
  3. 2022年、Alpine 3.16 で root の初期値が ! から * に変更。PAM なしでも SSH 鍵ログインを可能にするための変更です

POSIX は crypt() の失敗時に NULL を返すと定めています。HN では「musl の挙動は仕様違反だ」という指摘と、「Rust などの言語や型で防げる類ではない論理バグだ」という意見の両方が出ています。

OpenSSH のリリースノートも「根本原因は sshd の外にあると考えているが、sshd 経由で悪用でき、sshd 側で回避できる」と書いており、ライブラリの挙動とアプリケーションの前提がずれた箇所で起きた事故だと分かります。PAM を有効にした構成では、PAM が * のアカウントに対して crypt() を呼ばないため影響しません。発見者の解説と OpenSSH 側の告知によれば、glibc 環境の sshd も対象外です。

発見の経緯も注目点です。Kültekin 氏は glibc/musl・PAM の有無・shadow の有無を組み合わせて sshd を何通りもビルドし、約17,600通りの変則的なログインを投げ、「musl かつ PAM なし」の構成だけが他と違う結果を返すことを見つけたと書いています。同氏は大量のエージェントを使ったことにも触れており、OpenSSH の Damien Miller 氏も「AI エージェントによる数時間単位でのバグ再発見」を理由に、短い公開猶予期間を選んでいると説明しています。

エンジニアへの影響:確認すべき環境と手順

影響を受ける環境

勧告によると、Alpine の openssh-server パッケージ(PAM なしビルド)は 3.16〜3.24 と edge の全リリースが対象です。openssh-server-pam でも UsePAM no にしていれば対象になります。

特に注意したいのは **Alpine 3.18.11〜3.24.3 のクラウドイメージ(qcow2/VHD/AMI)**です。これらは * パスワードの alpine ユーザーと、パスワードなしの doas を同梱しており、誰かが $6$ のパスワードを設定した時点で root 相当の権限まで到達できる状態になります。

一方、sshd を動かしていない Docker コンテナは影響を受けません。公式の alpine イメージは sshd を含まないため、普通にアプリを動かしているだけのコンテナは対象外です。開発用に Alpine ベースで sshd を立てたコンテナや、Alpine の VPS・組み込み機器が確認の対象になります。

確認コマンド

# sshd が入っているか・バージョン
apk info -v openssh-server openssh-server-pam

# パスワード認証が有効か
sshd -T | grep -i passwordauthentication

# 条件となるアカウントがあるか(root で実行)
awk -F: '$2=="*"{print $1}' /etc/shadow          # パスワード未設定
awk -F: '$2 ~ /^\$[56]\$/{print $1}' /etc/shadow  # SHA-256/512 ハッシュ持ち

パスワード認証が有効で、両方の awk が結果を返す場合は該当します。

対処

  • apk upgrade -U -a で修正版へ更新し、rc-service sshd restart で再起動する(パッケージ更新だけでは動作中の sshd は置き換わりません)。修正版は 3.24 が 10.3_p1-r2、3.23 が 10.2_p1-r1、3.22 が 10.0_p1-r11 など、ブランチごとに勧告の表で確認できます
  • すぐ更新できない場合は PasswordAuthentication no を設定して sshd を再起動する。勧告は /etc/ssh/sshd_config.d/00-no-passwords.conf への書き込みを例示しています(3.19 以降の既定設定で読み込まれます)
  • /var/log/messages に、パスワードを持たないアカウントの Accepted password for が無いか確認する。勧告は「ログインを否定できなければ侵害されたものとして扱う」としています

執筆時点で CVE 番号は割り当てられていません。脆弱性スキャナが CVE ベースで判定している場合、この件を拾えない点にも注意が必要です。

まとめ

  • musl の crypt() は 256 バイト超の入力で * を返し、それがパスワード未設定アカウントの保存値と一致してしまった
  • PAM なしの sshd が「他人のソルトを借りる」タイミング攻撃対策と組み合わさり、SHA-256/512 のユーザーが1人いれば成立する条件になっていた
  • 影響は Alpine など musl+PAMなしの sshd。sshd を動かさないコンテナは対象外
  • 対処は更新+sshd 再起動、または PasswordAuthentication no

個々の判断はそれぞれ理にかなっていても、組み合わさると穴になる典型例です。網羅的なビルド構成の差分テストを AI エージェントで回す手法が広がれば、同種の「組み合わせのバグ」は今後も見つかっていくと考えられます。

ソース