SSHサーバのハードニングと鍵の運用
対象の目安: Linuxサーバを運用する情報システム担当 / 実務

インターネットに置いたLinuxサーバの22番ポートには、公開した直後から見知らぬ接続が届きます。JPCERT/CCが複数の観測用センサーを分散配置して観測しているインターネット定点観測レポートでも、2026年1月から3月の期間で、頻繁に探索された国内のサービスのトップ5に22/TCPが3番目として入っています。ただし同レポートは、観測されたパケットが必ずしも各サービスプロトコルにのっとった形式とは限らないと注記しているため、この順位から読めるのは探索の量までです。届いた接続が何を試しているかは、自分のサーバの認証ログを見れば判別できます。後述するJPCERT/CCの注意喚起は、2005年の時点でユーザー名とパスワードの組み合わせを辞書で総当たりする試行の報告が多数寄せられていたと伝えており、いま自組織のログに何が並んでいるかは、その目でログを読んで確かめる話になります。想定読者は、Linuxサーバを運用する情報システム担当です。
この記事では、パスワード認証から公開鍵認証へ移す理由を署名のやり取りから説明し、sshd_configで何をどう絞るのか、鍵をどう作ってどこに置くのか、到達できる経路をどう設計するのか、そして運用に入ったあと何を見て何を棚卸しするのかまでを順に扱います。事実として書く既定値や仕様は、OpenSSH公式のmanページとリリースノート、IETFのRFC、NISTとJPCERT/CCの公開資料で確認できた範囲に限ります。
注意しておきたいのは、既定値がOpenSSHの版と配布物によって違うことです。man.openbsd.orgが示すのはOpenBSD側の説明であり、各Linuxディストリビューションはパッケージ独自の設定ファイルで上書きしていることがあります。設定を判断するときは、この記事の記述をそのまま信じるのではなく、稼働しているサーバでsshd -Tを実行して実効値を読むところから始めてください。設定変更の試行は、自組織が管理する、または管理者から明示の許可を得たサーバだけで行ってください。
パスワード認証が総当たりに晒される仕組み
SSHのパスワード認証は、RFC 4252の第8節が定める「password」メソッドです。クライアントはユーザー名とサービス名に続けて、平文のパスワードをUTF-8で載せたリクエストを送ります。RFC自身が、平文のパスワードがパケットに入っていてもパケット全体はトランスポート層で暗号化されると注記しており、経路上の盗聴には守られています。守られていないのは、その先です。サーバは受け取った文字列を照合するだけなので、正しい組み合わせを当てられれば、それが本人の入力か機械の試行かを区別できません。
試行の元手は攻撃側に潤沢にあります。他のサービスから漏れた資格情報の一覧は流通しており、同じパスワードを別のサーバでも使っていれば、1回の試行で通ります。ユーザー名の側も、rootやubuntu、adminのように配布物ごとに決まった名前が候補になります。JPCERT/CCは2005年の時点で、辞書を使ってユーザー名とパスワードを総当たり的に試すブルートフォース攻撃の報告が多数寄せられていると書き、任意のクライアントからアクセスできてしまうパスワード認証を無効にして公開鍵認証だけを有効にする設定を推奨していました。20年以上前に示されたこの対策は、現在のOpenSSHでも同じ設定項目で実現できます。
パスワードを長くする、失敗回数を制限する、といった対策は総当たりの費用を上げますが、認証の材料が「知っている文字列」である限り、使い回しとフィッシングによる漏えいの経路は残ります。総当たりそのものの成立条件と、パスワード側でできる対策の全体像は別記事で整理しています。
あわせて読みたい
総当たり攻撃と使い回しの悪用から見るログインの守り方
公開鍵認証で交わされる署名の中身
公開鍵認証では、経路上を流れるものが変わります。RFC 4252の第7節は、公開鍵認証を必須のメソッドと定め、秘密鍵を持っていること自体が認証になると説明しています。クライアントは公開鍵をサーバへ提示し、サーバがその鍵をそのユーザーの認証子として受け入れられるかを確かめます。受け入れられる場合、クライアントは秘密鍵で署名を作って送り、サーバは署名の正しさを検証します。
署名の対象が要点です。RFC 4252は、署名がセッション識別子、SSH_MSG_USERAUTH_REQUESTのメッセージ番号、ユーザー名、サービス名、"publickey"という文字列、真偽値のTRUE、公開鍵アルゴリズム名、認証に使う公開鍵という順のデータに対して作られると定めています。このセッション識別子は、RFC 4253の第7.2節が定めるとおり最初の鍵交換の交換ハッシュから作られる、その接続に固有の値です。同節は、いったん計算されたセッション識別子は後で鍵を交換し直しても変わらないと明記しています。値が変わるのは接続が変わったときなので、ある接続で観測した署名を別の接続へそのまま流し込んでも通りません。パスワードのように、盗めば何度でも使える固定の値が経路上に現れない構造になっています。
もう1つの違いは、秘密鍵がクライアント側から出ていかないことです。SSHの認証材料としてサーバに置かれるのは公開鍵だけなので、authorized_keysが読み取られても、そこから他のサーバへログインできる秘密は取り出せません。ここで塞がるのは、認証情報の保管場所を読まれて再利用される経路です。OSのアカウントに設定したパスワードのハッシュは/etc/shadowに残るため、そちらは別に扱う必要があります。SSHで使わないアカウントのパスワードをロックする、PAM経由の対話認証を閉じる、他サービスとパスワードを使い回さないといった手当てが引き続き要ります。ただしこの利点は、秘密鍵の側を守れている場合にだけ成立します。秘密鍵の入ったファイルがそのままコピーされれば、パスワードを盗まれたのと同じことになります。
鍵の種類と長さの選び方
OpenSSHのssh-keygen(1)は、生成できる鍵の種類としてecdsa、ecdsa-sk、ed25519、ed25519-sk、mldsa44-ed25519、rsaを挙げ、既定はed25519だと明記しています。特別な理由がなければ、Ed25519を選ぶのが素直です。鍵長は固定で、-bは無視されます。
RSAを選ぶ場合、ssh-keygen(1)は最小1024ビット、既定3072ビットと書き、一般に3072ビットあれば十分だとしています。sshd(8)側も、RSAの最小モジュラスサイズとして1024ビットを強制すると記載しています。ここで注意したいのは、1024という数字が下限であって推奨ではないことです。OpenSSH 9.1(2022年10月4日)でRequiredRSASizeが追加され、これより短い鍵をユーザー認証とホスト認証で無視できるようになりました。sshd_config(5)とssh_config(5)はいずれも既定を1024ビットと記載し、この値は既定から引き上げる方向にしか変更できないと注記しています。3072を明示しておけば、古い1024ビットの鍵が残っていても弾けます。
署名アルゴリズムの世代交代にも触れておきます。OpenSSH 8.8(2021年9月26日)は、SHA-1ハッシュを使うRSA署名を既定で無効にしました。リリースノートは、SHA-1が暗号学的に破られており、5万米ドル未満で選択プレフィックス衝突を作れる状況になったことを理由として挙げています。既存のRSA鍵そのものが使えなくなるわけではなく、RFC 8332が定めるRSA/SHA-256とRSA/SHA-512の署名へ切り替わります。さらにOpenSSH 10.0(2025年4月9日)ではDSAのサポートが完全に削除され、同じ版から鍵交換の既定がポスト量子計算機を想定したハイブリッド方式のmlkem768x25519-sha256になりました。執筆時点の最新版はOpenSSH 10.4(2026年7月6日リリース)です。
秘密鍵の保管とssh-agentの扱い
秘密鍵にパスフレーズを設定すると、ファイルはbcrypt_pbkdfで導出した鍵で暗号化されます。ssh-keygen(1)の-a roundsはその反復回数で、既定は16回です。回数を増やすとパスフレーズの検証は遅くなりますが、鍵ファイルを盗まれた場合の総当たりへの耐性が上がります。NIST IR 7966も、対話利用者に割り当てる識別鍵はパスフレーズで保護すべきであり、パスフレーズの強度と、鍵を入れ替えるたびにパスフレーズも変える基準を定めるよう求めています。
パスフレーズを付けると毎回の入力が煩わしくなるため、ssh-agentへ鍵を預けます。エージェントはメモリ上で署名を代行し、秘密鍵そのものを渡しません。ssh_config(5)のAddKeysToAgentは、鍵を自動的にエージェントへ追加するかどうかを決める設定で、既定はnoです。confirmを指定すると鍵を使うたびに確認を求め、時間を指定すればその時間で自動的に取り除かれます。
危ないのはエージェント転送です。ssh_config(5)のForwardAgentは既定でnoですが、有効にすると、接続先ホストでファイルのパーミッションを迂回できる利用者が、転送されたソケット経由で手元のエージェントへ到達できます。manページは、攻撃者が鍵の材料そのものを取り出すことはできないものの、エージェントに読み込まれている識別情報で認証する操作は実行できると明記しています。踏み台を経由したいだけなら、エージェント転送ではなくProxyJumpを使うのが安全です。ProxyJumpは踏み台へのSSH接続を先に張り、そこから最終目的地へのTCP転送を確立する仕組みで、認証は手元で完結します。
エージェントに鍵を多数読み込ませていると、サーバへ順に提示するうちにMaxAuthTriesの上限へ達して接続できなくなることがあります。ssh_config(5)のIdentitiesOnlyをyesにすると、設定または引数で明示した鍵だけを使うようになります。既定はnoです。ホストごとの設定を~/.ssh/configへ書き、IdentityFileとIdentitiesOnlyを組みで指定しておくと、意図しない鍵の提示を避けられます。
sshd_configで絞る認証まわりの設定
サーバ側の設定はsshd_configに書きます。近年の配布物では/etc/ssh/sshd_config.d/*.confを読み込むIncludeが先頭に置かれていることが多く、この場合は後から読まれるファイルの値が効くとは限りません。sshd_config(5)は、Includeが指定したファイルを読み込み、絶対パスでない場合は/etc/sshにあるものとみなすと記載しています。加えて、同じキーワードが複数のMatchブロックで一致した場合は最初の1つだけが適用されます。どこで何が決まっているかを追うより、sshd -Tで実効値を確認するほうが確実です。
最小限として押さえたい設定を並べます。値はいずれも自組織の運用に合わせて調整してください。
# /etc/ssh/sshd_config.d/10-hardening.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
PermitEmptyPasswords no
PermitUserEnvironment no
AllowGroups ssh-users
MaxAuthTries 3
LoginGraceTime 30
RequiredRSASize 3072
LogLevel VERBOSE
ClientAliveInterval 300
ClientAliveCountMax 2
それぞれの既定値と挙動を確認しておきます。PasswordAuthenticationの既定はyesです。閉じるだけでは足りず、KbdInteractiveAuthentication(既定はyes、ChallengeResponseAuthenticationは非推奨の別名)も閉じる必要があります。PAM経由の対話認証がここに入り込むため、片方だけ閉じてパスワードで入れてしまう状態は珍しくありません。PermitEmptyPasswordsの既定はnoですが、明示しておくと意図が読み取れます。
MaxAuthTriesの既定は6で、sshd_config(5)は失敗回数がこの値の半分に達した時点から追加の失敗をログに記録すると説明しています。3にすれば、2回目の失敗からログへ出ます。LoginGraceTimeの既定は120秒で、この時間内にログインが成功しなければサーバから切断します。値を短くすると、認証を完了しないまま接続を占有する行為の効果を下げられます。
AllowUsersとAllowGroupsは既定では設定されておらず、すべての利用者とグループにログインが許されています。AllowGroupsで管理用のグループを1つ作り、そこに入れた利用者だけを通す構成にすると、OSにアカウントが増えてもSSHの入口は増えません。AllowUsersはUSER@HOSTの形を取れて、HOST側はCIDR記法のアドレスも書けます。
同じ版で確認しておきたいのが、認証に失敗した接続元へのペナルティです。OpenSSH 9.8(2024年7月1日)はPerSourcePenaltiesを導入し、既定で有効にしました。認証に繰り返し失敗した接続元、認証を完了せずに切断を繰り返す接続元、sshdをクラッシュさせた接続元に対して、PerSourceNetBlockSizeが定めるCIDR範囲ごとに一定時間の接続拒否を課します。執筆時点のsshd_config(5)が示す既定値は、認証失敗が5秒、クラッシュが90秒、LoginGraceTime超過が10秒、認証を試みずに切断した場合が1秒です。存在しないユーザー名での試行に対するinvaliduser(既定5秒)は、9.8ではなくOpenSSH 10.3(2026年4月2日)で追加された項目なので、それより前の版には現れません。稼働中のサーバでどの項目がどの値になっているかは、sshd -T | grep -i persourcepenaltiesで確認してください。ここで押さえておきたいのが発動の条件です。sshd_config(5)は、ペナルティが最小のしきい値まで累積するまでは適用されないと述べ、そのminの既定を15秒と記載しています。既定のままなら、1回の認証失敗で積まれるのは5秒だけなので拒否は始まらず、同じ接続元からの違反が重なって15秒を超えたところで効き始めます。累積の上限はmaxの既定である10分です。管理用のアドレス帯を確実に通したい場合は、PerSourcePenaltyExemptListで除外します。
拒否の単位が接続元のアドレス範囲である以上、正規の利用者を巻き込む場面があります。9.8のリリースノートも、多数の利用者から接続を受けるサーバや、NATやプロキシの背後のアドレスから接続を受けるサーバの運用者は、この設定の検討が要るかもしれないと述べています。同じ出口アドレスを共有している環境では、1人の失敗が同じ範囲の全員の接続拒否につながります。PerSourceNetBlockSizeの値と除外リストを、自組織の接続元の実態に合わせて評価してください。
fail2banのような外部の遮断ツールを併用している環境では、PerSourcePenaltiesと二重に効くことになります。どちらが先に遮断しているのかがログから読めなくなるため、片方に寄せるか、しきい値の役割分担を決めてから運用に入るのが扱いやすくなります。
サーバ全体の設定堅牢化としては、SSH以外にも同じ考え方を適用する項目が並びます。
あわせて読みたい
サーバーのハードニング基礎。最小化・最小権限・更新・設定堅牢化をCISベンチマークから学ぶ
authorized_keysのオプションで鍵ごとに制限する
公開鍵認証へ移すと、次に管理する対象はauthorized_keysになります。sshd_config(5)のAuthorizedKeysFileの既定は.ssh/authorized_keys .ssh/authorized_keys2で、利用者のホームディレクトリ配下です。つまり、そのアカウントに書き込める者は誰でも自分の鍵を追加できます。
NIST IR 7966は、これをバックドアの作り方として明示しています。新しい鍵ペアを生成してauthorized_keysに1行足すだけで、特権アクセス管理システムを迂回する入口ができ、authorized_keysは監査されないことが多いため、その行が何年も気づかれずに残ると述べています。対処として同文書が推奨するのが、authorized_keysを一般利用者が書き換えられない、スーパーユーザー所有の場所へ移し、SSHサーバがその場所だけを見るように設定することです。OpenSSHでは、AuthorizedKeysFileにトークンを使った絶対パスを指定し、ディレクトリをroot所有にすることで実現できます。
# 鍵の置き場をroot所有の集中管理ディレクトリへ移す例
AuthorizedKeysFile /etc/ssh/authorized_keys.d/%u
鍵1本ごとの制限は、authorized_keysの行頭に書くオプションで指定します。sshd(8)のAUTHORIZED_KEYS FILE FORMATが定義しているもののうち、実務で効くのは次の組み合わせです。
# バックアップ処理専用の鍵。接続元と実行内容を固定し、他の機能をすべて落とす
restrict,from="203.0.113.10,198.51.100.0/24",command="/usr/local/sbin/backup-run" ssh-ed25519 AAAAC3Nza... backup@jobserver
# 期限付きの一時鍵。指定した日時を過ぎると受け付けられなくなる
expiry-time="20260930",restrict,pty ssh-ed25519 AAAAC3Nza... vendor-temp
restrictは、ポート転送、エージェント転送、X11転送、pty割り当て、~/.ssh/rcの実行をまとめて無効にします。あとから必要なものだけptyやport-forwardingで戻す書き方ができるため、許可を1つずつ足す方向で設計できます。from=は、公開鍵認証に加えて接続元のホスト名またはIPアドレスがパターン一覧に含まれることを要求します。manページは、公開鍵認証は鍵以外を信頼しないため、鍵が盗まれれば世界中どこからでもログインできてしまうと述べ、from=はその難易度を上げるものだと説明しています。command=は、利用者が何を送っても指定したコマンドだけを実行します。ファイル転送専用の鍵で対話シェルへ落ちる事故を防ぐ用途に向きます。expiry-time=は、YYYYMMDDまたはYYYYMMDDHHMM[SS]の形式で受け付ける期限を指定します。
鍵ごとに権限を切り分ける発想は、アカウントの権限設計と同じ方向を向いています。
あわせて読みたい
最小権限の原則(Least Privilege)。なぜ権限を絞ることが最強の防御の一つなのか
rootログインとポート変更をめぐる誤解
PermitRootLoginの既定はprohibit-passwordです。OpenSSH 7.0(2015年8月11日)で既定がyesからprohibit-passwordへ変わり、同時にprohibit-passwordの意味も、対話的な認証方式をすべて禁じて公開鍵、hostbased、GSSAPIだけを許すものに変わりました。つまり執筆時点の既定でも、rootの鍵がauthorized_keysに入っていればrootで直接ログインできます。
rootで直接ログインさせない設定にする理由は、権限の強さではなく、誰が操作したかを記録に残せるかどうかにあります。個人のアカウントでログインしてから昇格すれば、認証ログとsudoのログの両方に個人が残ります。rootで直接入ると、共有アカウントの操作として記録され、複数人が同じ鍵を持っている場合に追跡できません。NIST IR 7966も、SSHによるrootアカウントへのアクセスは一般には許可されておらず、管理者はログイン後にsudo等を使って多くの操作のために権限を昇格させると注記しています。バックアップのように自動処理でroot権限が要る場合は、PermitRootLoginをforced-commands-onlyにして、authorized_keys側のcommand=が指定されている鍵だけを通す使い方があります。
もう1つよく話題になるのが、待ち受けポートを22番から変えることです。ログに残る自動化された試行の量は確かに減ります。ただし、減るのは22番だけを狙う雑な走査であって、全ポートを走査して応答したサービスを特定する手法には効きません。認証の強度は1ビットも変わらないため、パスワード認証を閉じないままポートだけ変える運用は、ログが静かになったぶん危険な状態に見えにくくなります。加えて、1024番以上のポートを選ぶと、sshdが停止している間に一般利用者がそのポートを掴める余地が生まれます。ポート変更は、パスワード認証の停止と到達性の制限を済ませたうえで、ログのノイズを減らす目的で選ぶ手段として位置づけるのが妥当です。
到達性の制限と踏み台サーバの設計
sshdの設定より前に効くのが、そもそも22番へ到達できる範囲です。ListenAddressで待ち受けるアドレスを内部側のインターフェースに限る、クラウドのセキュリティグループやOSのファイアウォールで送信元を管理用のアドレス帯に絞る、といった制御は、認証の試行そのものを届かせません。sshd_config(5)のMatchブロックを使えば、接続元アドレスに応じて設定を変えることもできます。
# 管理ネットワーク以外からは公開鍵に加えてもう1段階の完了を求める
Match Address 10.0.0.0/8
AuthenticationMethods publickey
Match all
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive:pam
sshd_config(5)は、AuthenticationMethodsに並べた方式はそれぞれ設定側でも明示的に有効にしておく必要があると注記しています。前掲の最小構成のようにKbdInteractiveAuthenticationをnoにしたままこのMatchを足すと、管理ネットワーク以外からの接続は認証を完了できません。あわせて、keyboard-interactiveがワンタイムパスワードを求めるかどうかはPAMの構成で決まり、通常のパスワード認証にもなりえます。デバイス識別子を付けたkeyboard-interactive:pamのような書き方で対象を限定したうえで、PAMスタックからパスワードのモジュールが外れているかを確認してください。
Matchの判定条件として使えるのはUser、Group、Host、LocalAddress、LocalPort、Version、RDomain、Addressと、すべてに一致するAll、既知のアカウントに一致しない場合のInvalid-Userです。Addressの条件にはCIDR記法が書けます。同じキーワードが複数のMatchブロックで一致した場合は、最初に現れたものだけが適用される点に注意が要ります。
サーバの台数が増えると、すべてのサーバへ個別に管理経路を開けるより、踏み台を1台に集約するほうが管理しやすくなります。踏み台の側だけを監視と多要素認証の対象にでき、業務サーバは踏み台からの接続だけを受け付ける構成にできます。クライアント側の設定はProxyJumpで完結します。
# ~/.ssh/config
Host bastion
HostName bastion.example.jp
User admin
IdentityFile ~/.ssh/id_ed25519_sk
IdentitiesOnly yes
Host app-*
ProxyJump bastion
User deploy
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
踏み台を置くときに気をつけたいのが、NIST IR 7966が「pivoting」として説明する経路です。同文書は、あるアカウントに受信側の鍵と送信側の鍵の両方を設定すると、そのアカウントの鍵を得た攻撃者が次のサーバへ移動できると述べ、承認された踏み台のように明示的に必要な場合を除き、受信と送信の信頼関係は別のアカウントに分けるよう求めています。加えて、開発環境や検証環境から本番環境へ渡る信頼関係を作らないことも挙げています。踏み台は、この経路を1か所へ集めて監視する代わりに、そこが破られたときの影響も集めるという性質を持ちます。
到達できるネットワークの範囲そのものを設計する話は、セグメント分割の記事で扱っています。
あわせて読みたい
ネットワークセグメンテーションで侵入後の被害を広げない設計
多要素認証とハードウェアキーの組み込み
公開鍵認証は「持っているもの」による認証です。秘密鍵のパスフレーズを組み合わせれば、実質的に2つの要素になりますが、パスフレーズの検証はクライアント側で完結するため、サーバは鍵ファイルが保護されていたかどうかを知りません。サーバ側で複数の方式の完了を要求したい場合は、AuthenticationMethodsを使います。
sshd_config(5)は、AuthenticationMethodsの既定を「any」、つまり単一の方式が成功すれば認証を認める挙動だと説明しています。既定を上書きすると、列挙したリストのうち少なくとも1つについて、そこに並ぶすべての方式を完了する必要があります。例として挙げられているpublickey,password publickey,keyboard-interactiveは、公開鍵認証のあとにパスワードまたはキーボードインタラクティブ認証を求める指定です。各段階で提示されるのは、いずれかのリストで次に来る方式だけなので、この例では公開鍵より先にパスワードを試すことはできません。
キーボードインタラクティブ側にPAMのワンタイムパスワードモジュールを組み合わせると、公開鍵とTOTPの2段階になります。この構成を取る場合、KbdInteractiveAuthenticationは有効にしておく必要があります。前述の「KbdInteractiveAuthenticationも閉じる」という指針とぶつかるため、どちらの構成を採るのかを先に決めてください。パスワードだけを閉じてキーボードインタラクティブを残すなら、PAMの設定でパスワードモジュールが呼ばれない状態になっているかを確認する作業が要ります。
もう1つの道が、FIDO認証器に紐づく鍵です。OpenSSH 8.2(2020年2月14日)がecdsa-skとed25519-skという鍵種別を追加しました。この鍵は2つの部分から成り、ディスク上の秘密鍵ファイルに入るのはキーハンドルの部分だけで、認証器から取り出せない機器固有の秘密鍵と組み合わせて、署名時に実際の鍵が導出されます。鍵ファイルをコピーされても、認証器が手元になければ署名できません。
# FIDO2認証器に紐づく鍵を作る。署名のたびにPIN等の利用者検証を要求する
ssh-keygen -t ed25519-sk -O verify-required -C "admin@example.jp"
ssh-keygen(1)のFIDO AUTHENTICATOR節は、verify-requiredが署名のたびに利用者検証を要求する指定であり、現時点で対応している検証方法はPIN認証だと説明しています。ただし、この-O verify-requiredが効くのは鍵を作るクライアント側です。サーバ側で「利用者検証を伴う署名でなければ受け付けない」を強制するには、sshd(8)のAUTHORIZED_KEYS FILE FORMATが定義する同名のオプションを、authorized_keysの行頭にも書きます。
# 利用者検証(PIN等)を伴うことを署名が証明していなければ受け付けない
verify-required sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5... admin@example.jp
residentを付けると、キーハンドルを認証器本体へ保存して複数のPCで使い回せるようになりますが、同じmanページは、鍵の両方の部分を認証器へ保存することで、認証器を盗まれた場合に攻撃者が使える可能性が上がると注意しています。no-touch-requiredはタッチ操作を不要にする指定ですが、sshd(8)は既定でそうした署名を拒否し、authorized_keys側の同名オプションで明示的に許した場合にだけ受け付けます。
ハードウェアキーの製品選びと運用の勘所は、別記事にまとめています。
あわせて読みたい
ハードウェアセキュリティキー(FIDO2)の選び方とおすすめ。フィッシングに原理的に強い認証
ホスト鍵の検証と初回接続の扱い
ここまではサーバが利用者を確かめる側の話でした。逆方向、つまり利用者が接続先のサーバを確かめる仕組みがホスト鍵です。RFC 4251の第4.1節は、サーバのホスト鍵が鍵交換の際に「本当に正しいサーバと話しているか」を検証するために使われ、そのためにはクライアントが事前にサーバの公開ホスト鍵を知っている必要があると述べています。
同節は、信頼のモデルを2つ挙げます。1つは、クライアントがホスト名と公開ホスト鍵の対応をローカルのデータベースで持つ方法で、中央の基盤も第三者の調整も要らない代わりに、対応表の維持が負担になります。もう1つは、信頼された認証局がその対応を証明する方法で、クライアントはCAのルート鍵だけを知っていればよくなります。
初回接続の扱いについてもRFC 4251は率直です。プロトコルは初回接続時に名前と鍵の対応を検証しない選択肢を認めており、この場合も受動的な盗聴には守られるものの、能動的な中間者攻撃には脆弱になると書いています。そのうえで、実装はそうした接続を既定で許すべきではないと述べ、それでもこの選択肢が現実的なのは広く展開された鍵基盤が存在しないためであり、初回だけ検証せずに受け入れて保存し、以降はその鍵と照合する戦略を例として挙げています。これが一般にTOFU(Trust On First Use)と呼ばれる運用です。
OpenSSHのクライアント側では、ssh_config(5)のStrictHostKeyCheckingが既定でaskです。新しいホスト鍵は利用者が確認した場合にだけknown_hostsへ追加され、鍵が変わったホストへの接続は拒否されます。accept-newにすると、新しい鍵は自動で追加しつつ、変わった鍵は拒否します。自動化されたジョブではnoが使われがちですが、これは変わった鍵での接続まで許してしまうため、accept-newか、事前に配布したknown_hostsとの組み合わせを選んでください。
台数が多い環境では、ホスト証明書のほうが管理しやすくなります。sshd(8)のSSH_KNOWN_HOSTS FILE FORMATは、known_hostsの各行がマーカー、ホスト名、鍵種別、base64の鍵、コメントの順で並ぶと定めています。マーカーの@cert-authorityはその行がCAの鍵であることを示すもので、CAの公開鍵だけでは足りず、そのCAを信頼する対象のホスト名パターンを併せて書きます。
# known_hosts: 指定したパターンに一致するホストの証明書だけをこのCAで検証する
@cert-authority *.example.jp ssh-ed25519 AAAAC3Nza...
パターンを*のように広く取ると、CAの信頼範囲が意図しないホスト名まで及びます。管理下のドメインに限る書き方にしてください。侵害が判明した鍵は@revokedマーカーで拒否できます。ホスト鍵の入れ替えについては、ssh_config(5)のUpdateHostKeysが、認証済みのサーバから追加のホスト鍵の通知を受け取ってknown_hostsへ追加する仕組みを提供しており、利用者がUserKnownHostsFileの既定を変更しておらずVerifyHostKeyDNSも有効にしていない場合は既定で有効になります。
known_hostsそのものの扱いにも触れておきます。ssh_config(5)のHashKnownHostsは既定でnoで、ホスト名とアドレスが平文で残ります。ファイルが流出したときに接続先の一覧を読まれたくない場合は、yesにするか、既存のファイルをssh-keygen -Hでハッシュ化します。
鍵の棚卸しと失効、証明書による短命化
前掲のexpiry-time=のようなオプションを付けずにauthorized_keysへ追加した鍵には、有効期限がありません。ここが、SSHの鍵運用でいちばん崩れやすい部分です。NIST IR 7966は、多くの組織が自組織のシステムへのアクセスを許すSSH鍵を何本設定しているか、誰がその複製を持っているかすら把握していないと述べ、大企業では自動処理用の鍵が数十万本から数百万本に達し、対話利用のアカウントよりはるかに多くの入口を提供していると指摘しています。同文書は、識別鍵の暗号期間(cryptoperiod)を定め、担当者の異動や退職の際には期限前でも入れ替えることを推奨しています。
棚卸しの実務は、まず現状を数えるところからです。
# 探索先を先に確認する。AuthorizedKeysFileを変えている場合や、
# AuthorizedKeysCommandとTrustedUserCAKeysで別の認可経路がある場合はここに出る
sudo sshd -T | grep -iE 'authorizedkeysfile|authorizedkeyscommand|trustedusercakeys|authorizedprincipalsfile'
# authorized_keysを列挙し、鍵のフィンガープリントとコメントを出す
# root所有のファイルも読むため、パイプの先まで含めてroot権限で実行する
sudo sh -c 'find /home /root /etc/ssh/authorized_keys.d \
\( -name "authorized_keys" -o -name "authorized_keys2" \
-o -path "/etc/ssh/authorized_keys.d/*" \) -type f 2>/dev/null |
while read -r f; do
echo "--- $f"
ssh-keygen -l -f "$f"
done'
ファイルの列挙だけでは足りない場合があります。AuthorizedKeysCommandで外部のディレクトリサービスから鍵を引いている構成や、TrustedUserCAKeysで証明書を受け入れている構成では、ファイルに現れない認可経路が別に存在します。sshd -Tの出力でどの経路が有効かを確かめてから、経路ごとに棚卸しの手順を決めてください。
鍵の数が増えてきたら、証明書方式へ移す判断が出てきます。ssh-keygen(1)のCERTIFICATES節は、証明書が公開鍵と識別情報、0個以上のプリンシパル名、そして一連のオプションをCA鍵で署名したものであり、クライアントやサーバは多数の利用者鍵やホスト鍵ではなくCA鍵だけを信頼して署名を検証すればよくなると説明しています。X.509とは別の、より単純な形式です。
# CA鍵でユーザー証明書を発行する。有効期間を8時間に限る
# この操作はSSHの接続先とは分けた発行専用のホストで行い、CAの秘密鍵は接続先へ置かない
ssh-keygen -s /srv/ssh-ca/ca_user_key -I "yamada-20260804" \
-n deploy -V +8h ./id_ed25519.pub
サーバ側へ配るのはCAの公開鍵だけです。TrustedUserCAKeysにその公開鍵を指定します。sshd_config(5)は、この設定に値としてnoneを指定するとCAを使わない状態になること、プリンシパル一覧を持たない証明書はTrustedUserCAKeys経由の認証には使えないことを記載しています。CAを設定していないサーバではsshd -Tの出力がtrustedusercakeys noneになるため、実効値はそこで確認できます。受け入れるプリンシパル名を制御したい場合はAuthorizedPrincipalsFileを使いますが、manはこちらの既定をnoneと明記しており、その場合は利用者名が証明書のプリンシパル一覧に含まれている必要があります。
TrustedUserCAKeys /etc/ssh/ca_user_key.pub
AuthorizedPrincipalsFile /etc/ssh/principals.d/%u
RevokedKeys /etc/ssh/revoked_keys
RevokedKeysを書くときは、指定したファイルを先に用意してください。sshd_config(5)は、このファイルが読めない場合は全利用者の公開鍵認証が拒否されると明記しています。存在しないパスを書いたまま再読み込みすると、その場で締め出しになります。root所有で空の失効ファイルまたは有効なKRLを置き、sshdから読めることを確認してから反映してください。同じ節は、このファイルが公開鍵認証のたびに参照されうるため内容は常に一貫している必要があり、稼働中に書き換えるのではなく原子的に置き換えるべきだとも述べています。まだ失効の運用を始めていない段階なら、この行は入れずに進める判断もあります。
証明書に有効期限を持たせると、失効の設計が変わります。-V +8hで発行していれば、退職者の証明書は発行を止めた時点から最長8時間で自然に切れます。全サーバのauthorized_keysを回って行を消す作業は不要になりますが、切れるまでの残り時間はログインできる状態が続く点は残ります。その残り時間も待たずに止めたい場合は、RevokedKeysで指定するファイル、またはssh-keygen(1)の-kが生成する鍵失効リスト(KRL)を配布して、対象の全サーバへ行き渡ったことを確認してください。退職や事故のように即時停止が要る場面を想定するなら、失効の配布経路のほうを先に用意しておく判断になります。
証明書方式は、CA鍵という単一の重要な秘密を新たに抱えることでもあります。CA鍵をオフラインの環境やハードウェアトークンへ置き、発行を許す条件と発行の記録の残し方を先に決めてから移行してください。CA鍵が漏れれば、そのCAを信頼するすべてのサーバへ任意の証明書を発行できてしまいます。
ログの見どころと検知の組み立て
sshd_config(5)のLogLevelの既定はINFOで、VERBOSEを指定するとより詳しい記録が残ります。鍵フィンガープリントをログに記録するときのハッシュアルゴリズムはFingerprintHashで決まり、既定はsha256です。どの水準でどこまで出るかは版と配布物で差があるため、設定を変えたあとに実際のログを読んで確かめてください。NIST IR 7966も、認可鍵による認証について鍵フィンガープリントをログに記録するようすべてのSSHサーバを設定すべきであり、これが継続的な監視とフォレンジック調査に必要だと述べています。
認証まわりで見る行は、おおむね次のような形になります。
Accepted publickey for deploy from 203.0.113.10 port 51234 ssh2: ED25519 SHA256:xxxxxxxx...
Failed password for invalid user admin from 198.51.100.7 port 40122 ssh2
Connection closed by authenticating user root 198.51.100.7 port 40130 [preauth]
フィンガープリントが記録されていれば、どの鍵が使われたかを事後に特定できます。棚卸しで作った鍵の一覧と突き合わせると、台帳に無い鍵での成功を見つけられます。これはNIST IR 7966が挙げるバックドア鍵の検知に直接効きます。逆にフィンガープリントが記録されていない設定では、成功したログインが「誰の鍵か」まで遡れません。
なお、OpenSSH 9.8以降はサーバがリスナーのsshdとセッションごとのsshd-sessionに分かれ、一部のログメッセージがsshd-sessionという名前で出るようになりました。さらにOpenSSH 10.0では認証の処理がsshd-authという別のバイナリへ移り、リリースノートは一部のログメッセージがsshd-sessionではなくsshd-auth名で出るようになると案内しています。既存の検知ルールがプロセス名で絞り込んでいる場合は、sshd、sshd-session、sshd-authの3つを対象に入れておかないと、更新の際に取りこぼしが起きます。
見るべき兆候をいくつか整理します。1つは、失敗の連続です。MaxAuthTriesの半分を超えた時点から追加の失敗が記録されるため、しきい値を3にしておけば早い段階で行が出ます。2つ目は、存在しないユーザー名での試行で、invalid userの行がまとまって出ます。3つ目は、成功したログインの接続元と時刻の分布です。普段は社内のアドレス帯からしか来ないアカウントが、深夜に別の国のアドレスから成功している、といった変化は、鍵そのものが漏れた可能性を示します。4つ目は、authorized_keysファイルの変更です。ファイル監視で更新を検知し、変更内容を承認記録と突き合わせる運用にしておくと、バックドア鍵の追加を気づける形にできます。
集約と保全の実務は、ログ管理の記事で扱っています。
あわせて読みたい
ログ管理の基本。何を・どこまで・どれだけ残すか
設定変更を安全に反映する手順
SSHの設定変更には、失敗すると自分が締め出されるという固有の危険があります。作業中のセッションを1つ残したまま、別のセッションで接続を確認する手順を必ず踏んでください。
sshd_configの変更を締め出しなしで反映する手順
- 1
作業前に `sudo sshd -T` を実行し、変更前の実効値をファイルへ保存する
- 2
現在のSSHセッションはそのまま維持し、このセッションから設定ファイルを編集する
- 3
Includeの読み込み順を確認し、上書きされない位置(例えばsshd_config.d配下)へ変更を書く
- 4
`sudo sshd -t` で構文とホスト鍵の健全性を確認し、エラーが出たら反映を中止する
- 5
`sudo sshd -T -C user=deploy,addr=203.0.113.10,host=admin01.example.jp,laddr=10.0.0.5,lport=22` のように接続元の条件を与えて、Matchブロック適用後の実効値を確認する
- 6
設定を再読み込みする(systemd環境ではsystemctl reload sshまたはsshd)
- 7
元のセッションを閉じずに、別の端末から新しいセッションで接続できることを確認する
- 8
公開鍵での接続成功と、パスワードでの接続失敗の両方をログで確認する
- 9
問題がなければ元のセッションを閉じ、変更内容と実効値の差分を作業記録へ残す
sshd -Tは拡張テストモードで、設定ファイルの妥当性を確認したうえで実効設定を標準出力へ書き出して終了します。-Cで接続パラメータ(addr、user、host、laddr、lport、rdomain)を与えると、該当するMatchディレクティブを適用した結果を出力します。ここで指定するaddrとhostは接続元のアドレスと解決済みのホスト名で、laddrとlportはサーバ側が受けるアドレスとポート番号です。hostに接続先のサーバ名を書くとMatch Hostの検証結果を読み違えます。値を取らないinvalid-userフラグも指定でき、既知のアカウントに一致しない利用者からの接続を模して、Match Invalid-Userを書いた場合の実効値を確かめられます。sshd -tはテストモードで、設定ファイルの妥当性と鍵の健全性だけを確認します。manページも、設定オプションが変わりうるためsshdを確実に更新するのに有用だと説明しています。
反映後に確認する項目をまとめます。
- sshd -Tの出力でpasswordauthenticationとkbdinteractiveauthenticationがいずれもnoになっている
- sshd -Tの出力でpermitrootloginが意図した値(noまたはforced-commands-only)になっている
- AllowUsersまたはAllowGroupsで、SSHログインを許す対象が明示的に限定されている
- requiredrsasizeが組織の基準値まで引き上げられ、基準に満たない鍵が残っていない
- authorized_keysの置き場と所有者が決まっており、一般利用者が任意に鍵を追加できない
- 自動処理に使う鍵へfrom=とcommand=とrestrictが適用されている
- PerSourcePenaltiesの既定動作と、外部の遮断ツールの役割分担が整理されている
- 22番ポートへ到達できる送信元が、ファイアウォールまたはセキュリティグループで限定されている
- 踏み台を使う構成で、受信側と送信側の信頼関係が同一アカウントに同居していない
- LogLevelがVERBOSEで、成功したログインの鍵フィンガープリントがログに残っている
- ログが外部のログ基盤へ転送され、サーバ側が失われても追跡できる期間を確保している
- 鍵の台帳があり、発行者と用途と有効期限、棚卸しの担当者が決まっている
- OpenSSHのバージョンを把握し、CVE-2024-6387の修正が入ったパッケージかどうかを配布元のアドバイザリで確認している
最後の項目について補足します。OpenSSH 9.8のリリースノートは、Portable OpenSSHの8.5p1から9.7p1(いずれも含む)にsshdの重大な脆弱性があり、root権限での任意コード実行につながりうると記載しています。この脆弱性はNVDにCVE-2024-6387として登録されており、NVDが挙げる影響構成には、これに加えて4.4より前のOpenSSHも含まれます。判定でつまずきやすいのは、ssh -Vが返す番号だけでは結論が出ないことです。Linuxの配布物は上流の修正を古い版数のパッケージへ取り込むことがあり、番号が9.7p1のままでも修正済みという状態が起こります。使っている配布物のセキュリティアドバイザリで、そのパッケージ版数が修正済みと判定されているかを確認してください。設定の堅牢化と並行して、実装そのものを新しく保つ作業が要ります。設定値のベースラインを外部の基準に合わせたい場合は、CIS Benchmarksが配布物ごとの設定推奨を公開しています。ただし具体的な推奨値は配布物と版によって異なるため、採用する際は対象の文書を直接確認してください。
まとめ
SSHのハードニングは、パスワード認証を閉じるという1行の設定で終わる作業ではありません。RFC 4252が定める公開鍵認証は、セッション識別子を含むデータへの署名を交わすことで、盗めば再利用できる固定の秘密を経路から消します。その利点は、秘密鍵をパスフレーズとハードウェア認証器で守り、authorized_keysを一般利用者が書き換えられない場所へ置き、鍵1本ごとに接続元と実行できる操作を絞って初めて、想定どおりに働きます。
サーバ側の設定は、sshd -Tで実効値を読むところから始めてください。man.openbsd.orgが示す既定値と、稼働中の配布物の既定値は一致しないことがあります。PasswordAuthenticationの既定はyesで、PermitRootLoginの既定はprohibit-passwordです。どちらも、明示的に書かない限り望む状態にはなりません。OpenSSH 9.8で入ったPerSourcePenaltiesのように、新しい版が既定で加える保護もあるため、実装を新しく保つことが設定の堅牢化と同じくらい効いてきます。
そして、運用に入ってからの仕事が残ります。NIST IR 7966が指摘したとおり、何本の鍵が自組織のサーバへのアクセスを許しているかを把握していない組織は珍しくありません。鍵の一覧を作り、ログにフィンガープリントを残し、authorized_keysの変更を検知できるようにしておくこと、そして数が増えてきたら証明書方式で有効期限を持たせることが、入口を数えられる状態を保つ道になります。ここまでの設定と運用は、許可されたサーバでの検証を挟みながら、締め出しを避ける手順で少しずつ進めてください。
出典・参考
- OpenSSH: sshd_config(5) manual page (man.openbsd.org)
- OpenSSH: sshd(8) manual page (man.openbsd.org)
- OpenSSH: ssh-keygen(1) manual page (man.openbsd.org)
- OpenSSH: ssh_config(5) manual page (man.openbsd.org)
- OpenSSH: ssh(1) manual page (man.openbsd.org)
- OpenSSH 7.0 release notes
- OpenSSH 8.2 release notes (FIDO/U2F support)
- OpenSSH 8.8 release notes (RSA/SHA-1 signatures disabled by default)
- OpenSSH 9.1 release notes (RequiredRSASize)
- OpenSSH 9.8 release notes (PerSourcePenalties, sshd race condition fix)
- OpenSSH 10.0 release notes (DSA removal, mlkem768x25519-sha256)
- OpenSSH Release Notes
- RFC 4252: The Secure Shell (SSH) Authentication Protocol (IETF)
- RFC 4251: The Secure Shell (SSH) Protocol Architecture (IETF)
- RFC 4253: The Secure Shell (SSH) Transport Layer Protocol (IETF)
- NVD: CVE-2024-6387 (NIST National Vulnerability Database)
- NIST IR 7966 Security of Interactive and Automated Access Management Using Secure Shell (SSH)
- NIST SP 800-53 Rev.5 Security and Privacy Controls for Information Systems and Organizations
- SSH運用上の注意|JPCERT/CC Weekly Report(2005年11月9日)
- インターネット定点観測レポート(2026年1〜3月)|JPCERT/CC
- CIS Benchmarks (Center for Internet Security)
関連する記事
サーバーのハードニング基礎。最小化・最小権限・更新・設定堅牢化をCISベンチマークから学ぶ
サーバーを攻撃から守る土台となるハードニング(要塞化)の考え方を、最小化・最小権限・継続的な更新・設定の堅牢化という4本柱で整理し、CISベンチマークを使った具体的な進め方と運用までを実務目線で解説します。
総当たり攻撃と使い回しの悪用から見るログインの守り方
総当たり攻撃が成立する仕組みと、辞書攻撃やパスワードスプレーという派生、他サービスから漏れた認証情報を試すクレデンシャルスタッフィングとの違いを整理し、長いパスフレーズや多要素認証、レート制限まで、個人と運用側それぞれの守り方をNISTやOWASP、IPAの一次情報に沿って入門者向けに説明します。
最小権限の原則(Least Privilege)。なぜ権限を絞ることが最強の防御の一つなのか
必要最小限の権限だけを与える「最小権限の原則」を、なぜ効くのか(侵害時の被害局所化・横展開の抑止)という原理から噛み砕き、過剰権限の典型例とRBAC・ジャストインタイム権限・定期棚卸しといった実践、ゼロトラストとの関係までを実務目線で整理します。


