OpenSSH 10.x SSHセキュリティ完全ハードニングガイド:耐量子暗号・証明書認証・CIS準拠

OpenSSH 10.0の耐量子暗号デフォルト化やDSA廃止を踏まえ、sshd_configのハードニング、SSH証明書ベース認証、CISベンチマーク準拠設定、ssh-auditによる監査まで実践的に解説します。

はじめに:2025年、SSHセキュリティは大きな転換点を迎えた

SSHはLinuxサーバー管理の生命線ですよね。リモートアクセス、ファイル転送、トンネリング――ほぼすべてのインフラ運用がSSH上に成り立っています。だからこそ、SSHが侵害されたときの影響は本当に甚大なんです。

2024年から2025年にかけて、OpenSSHに複数の重大な脆弱性が発見されました。2024年7月のregreSSHion(CVE-2024-6387)はシグナルハンドラのレースコンディションを突いたリモートコード実行の脆弱性で、glibcベースのLinuxシステムが広範に影響を受けました。2025年2月にはQualysのThreat Research Unitが中間者攻撃を可能にするCVE-2025-26465とDoS攻撃のCVE-2025-26466を発見。さらに同年10月には、ProxyCommandのユーザー名処理における制御文字のサニタイズ不備を突いたコマンドインジェクション脆弱性CVE-2025-61984が公開されました。

正直、この1年半だけでもかなりの数ですよね。

一方で、OpenSSH 10.0(2025年4月リリース)は大きなマイルストーンとなりました。DSA署名アルゴリズムの完全廃止、耐量子暗号鍵交換のデフォルト化、認証プロセスの分離による攻撃面の縮小など、10年来の技術的負債を清算しつつ、量子コンピュータ時代への備えを本格的に開始したんです。

この記事では、OpenSSH 10.x時代にふさわしいSSHセキュリティのハードニングを、基礎から応用まで徹底的に解説していきます。最新の脆弱性対策から、耐量子暗号の設定、証明書ベース認証の導入、CISベンチマーク準拠の設定、ssh-auditによる監査まで。現場で即座に活用できる実践ガイドを目指しました。

最新のOpenSSH脆弱性と対策

CVE-2025-26465:VerifyHostKeyDNSを悪用した中間者攻撃

CVE-2025-26465は、OpenSSHクライアントのバージョン6.8p1から9.9p1に影響する中間者攻撃(MITM)の脆弱性です。CVSSスコアは6.8で、VerifyHostKeyDNSオプションが有効な場合に、攻撃者がメモリ不足状態を誘発してホスト鍵の検証をバイパスし、任意のサーバーになりすますことができてしまいます。

この脆弱性が厄介なのは、FreeBSDなど一部のディストリビューションではVerifyHostKeyDNSがデフォルトで有効になっていたこと。自分のssh_configで有効にしていないから安全、とは限らないんですよね。

対策:

  • OpenSSH 9.9p2以降にアップデートする(最優先)
  • VerifyHostKeyDNSを明示的にnoに設定する
  • known_hostsファイルを適切に管理し、ホスト鍵の変更を厳格にチェックする
# ssh_config または ~/.ssh/config で明示的に無効化
Host *
    VerifyHostKeyDNS no
    StrictHostKeyChecking ask

CVE-2025-26466:SSH2_MSG_PINGによるDoS攻撃

CVE-2025-26466は、OpenSSH 9.5p1から9.9p1のsshd(サーバー側)に影響する認証前DoS攻撃の脆弱性です。攻撃者がSSH2_MSG_PINGパケットを大量に送りつけることで、メモリとCPUを枯渇させることができます。認証前に実行できてしまうところがポイントで、有効な認証情報すら必要ありません。

対策:

  • OpenSSH 9.9p2以降にアップデートする
  • PerSourcePenaltiesを設定して、同一IPからの異常な接続を制限する
# /etc/ssh/sshd_config
# OpenSSH 9.8以降で利用可能
PerSourcePenalties crash:90 authfail:5 noauth:3 grace-exceeded:20
PerSourcePenaltyExemptList 10.0.0.0/8 192.168.0.0/16

CVE-2025-61984:ProxyCommandコマンドインジェクション

CVE-2025-61984は、2025年10月に公開されたProxyCommandのユーザー名処理におけるコマンドインジェクション脆弱性です。OpenSSHがユーザー名に含まれる制御文字を適切にサニタイズしていなかったため、悪意のあるユーザー名を介してリモートコード実行が可能でした。

これ、実は以前の修正のバイパスなんです。根本的な問題が完全に解決されていなかったということですね。こういったパターンは他のソフトウェアでもよく見かけるので、「一回パッチを当てたから安心」という思考は禁物です。

対策:

  • OpenSSH 10.1以降にアップデートする(ユーザー名での制御文字を完全に禁止)
  • ProxyCommandを使用する場合は、ユーザー入力を直接渡さない設計にする

regreSSHion(CVE-2024-6387)から学ぶこと

2024年7月に公開されたregreSSHionは、OpenSSH 8.5p1から9.7p1に影響するリモートコード実行脆弱性でした。

興味深いのは、この脆弱性が2006年に修正された問題(CVE-2006-5051)の回帰(regression)だったこと。2020年10月のリファクタリングで誤って再導入されていたんです。18年前のバグが復活するとは、なかなか衝撃的ですよね。

ここから得るべき教訓は明確です。セキュリティ修正のテストが回帰テストとして維持されていなければ、同じ問題が再び発生し得る。そして私たちインフラ管理者にとっては、「パッチ管理の自動化」と「脆弱性スキャンの定期実行」がいかに大切かを痛感させられる事例です。

OpenSSH 10.0の主要な変更点

DSA署名アルゴリズムの完全廃止

OpenSSH 10.0で、ついにDSA(Digital Signature Algorithm)のサポートが完全に削除されました。DSAは2015年にデフォルト無効化されて以来、10年間にわたる段階的な廃止プロセスの末に、コードベースから完全に取り除かれたわけです。長かった。

もしまだDSA鍵を使っているサーバーやスクリプトがあれば、OpenSSH 10.0へのアップグレード前にEd25519またはRSA(4096ビット以上)への移行が必須です。

# DSA鍵を使用しているホストの検索
grep -r "ssh-dss" ~/.ssh/known_hosts
grep -r "ssh-dss" ~/.ssh/authorized_keys

# Ed25519鍵の新規生成
ssh-keygen -t ed25519 -C "user@host-$(date +%Y%m%d)"

# RSA 4096ビット鍵の生成(Ed25519非対応の古い環境用)
ssh-keygen -t rsa -b 4096 -C "user@host-$(date +%Y%m%d)"

耐量子暗号鍵交換のデフォルト化

OpenSSH 10.0では、ハイブリッド耐量子鍵交換アルゴリズムmlkem768x25519-sha256がデフォルトの鍵交換方式となりました。これはNIST標準化されたML-KEM(Module-Lattice Key Encapsulation Mechanism)と古典的なX25519楕円曲線鍵共有を組み合わせたハイブリッドアルゴリズムです。

ここで「ハイブリッド」の意味をちょっと掘り下げておきましょう。量子コンピュータがML-KEMを破る方法が見つかったとしても、X25519部分が防御線として残ります。逆に古典的な攻撃でX25519が破られても、ML-KEM部分がカバーしてくれる。どちらか一方が安全であれば全体の安全性が保たれるという、非常に堅牢な設計なんです。

ちなみに、OpenSSHは2022年のバージョン9.0からsntrup761x25519-sha512という耐量子鍵交換をデフォルトで提供していました。10.0でML-KEMベースに変更されたのは、NISTの正式標準化を受けての移行です。さらにOpenSSH 10.1では、耐量子鍵交換が選択されていない場合に警告メッセージが表示されるようになっています。時代は確実に動いていますね。

認証プロセスの分離(sshd-auth)

OpenSSH 10.0のもう一つの重要な変更は、認証処理のコードを既存のsshd-sessionバイナリから新しいsshd-authバイナリに分離したことです。

これにより、認証前の攻撃面(pre-authentication attack surface)が完全に独立したアドレス空間で動作するようになりました。regreSSHionのような認証前の脆弱性が発見されたとしても、攻撃者が利用できるコードの範囲が大幅に制限されます。地味ですが、セキュリティ設計としてはかなり効果的な変更です。

有限体DH鍵交換のデフォルト無効化

sshdのデフォルトKexAlgorithmsリストからdiffie-hellman-group*およびdiffie-hellman-group-exchange-*メソッドが除外されました。古いクライアントとの互換性が必要な場合は明示的に有効化する必要があります。

sshd_configの包括的ハードニング

さて、ここからが本題です。OpenSSH 10.x環境を前提とした実践的なsshd_config設定を解説していきます。各セクションごとに設定の意図と根拠を説明しますので、コピペで終わりにせず、理解した上で適用してください。

基本的なアクセス制御

# /etc/ssh/sshd_config - 基本アクセス制御

# SSHプロトコルバージョン2のみ使用(v1は完全に廃止済み)
# OpenSSH 7.6以降ではv1のサポートが削除されているため記述不要だが明示的に
Protocol 2

# rootでの直接ログインを禁止
# forced-commands-onlyは特定の自動化スクリプトに限定して許可する場合に使用
PermitRootLogin no

# パスワード認証を無効化し、公開鍵認証のみ許可
PasswordAuthentication no
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no

# 公開鍵認証を有効化
PubkeyAuthentication yes

# 空パスワードを禁止
PermitEmptyPasswords no

# ログインを許可するユーザー・グループを明示的に指定
AllowGroups ssh-users admin-ops
# AllowUsers deploy monitoring

# 認証の猶予時間とリトライ回数を制限
LoginGraceTime 30
MaxAuthTries 4
MaxSessions 3

# 接続元IPの逆引きDNSを無効化(パフォーマンスとセキュリティ)
UseDNS no

暗号アルゴリズムの設定

暗号アルゴリズムの設定は、SSHハードニングの中核です。弱いアルゴリズムを一つでも残しておくと、攻撃者にダウングレード攻撃の糸口を与えてしまいます。ここは妥協しないでください。

# /etc/ssh/sshd_config - 暗号アルゴリズム

# 鍵交換アルゴリズム(耐量子暗号を最優先)
KexAlgorithms mlkem768x25519-sha256,[email protected],curve25519-sha256,[email protected]

# ホスト鍵アルゴリズム(Ed25519を優先)
HostKeyAlgorithms [email protected],[email protected],ssh-ed25519,[email protected],[email protected],[email protected],rsa-sha2-512,rsa-sha2-256

# 暗号化方式(AES-GCMを優先、CTRモードはフォールバック)
Ciphers [email protected],[email protected],[email protected],aes256-ctr,aes192-ctr

# メッセージ認証コード(Encrypt-then-MACを使用)
MACs [email protected],[email protected]

# ホスト鍵ファイルの指定(Ed25519を優先的にロード)
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key

ここで注目したいのは、OpenSSH 10.0ではAES-GCMがAES-CTRモードより優先されるようになった点です。GCMは認証付き暗号化(AEAD)なので、別途MACを計算する必要がなく、セキュリティとパフォーマンスの両面で優れています。

セッション管理とログ設定

# /etc/ssh/sshd_config - セッション管理

# アイドルセッションのタイムアウト設定
ClientAliveInterval 300
ClientAliveCountMax 2

# X11フォワーディングの無効化(GUIが不要なサーバーでは必ず無効に)
X11Forwarding no

# TCPフォワーディングの制限
AllowTcpForwarding no
# 特定のユーザーにのみ許可する場合:
# Match User tunnel-user
#     AllowTcpForwarding yes

# エージェントフォワーディングの無効化
AllowAgentForwarding no

# ログレベルの設定(CISベンチマーク推奨)
LogLevel VERBOSE

# SFTPサブシステム(内部SFTPを使用)
Subsystem sftp internal-sftp

# バナーの設定(法的警告文)
Banner /etc/ssh/banner

PerSourcePenaltiesによるブルートフォース対策

OpenSSH 9.8で導入されたPerSourcePenaltiesは、個人的にかなり画期的だと思っている機能です。fail2banのような外部ツールなしで、sshdが自律的にブルートフォース攻撃をブロックできるんです。

# /etc/ssh/sshd_config - PerSourcePenalties

# 各種イベントに対するペナルティ時間(秒)
PerSourcePenalties crash:90 authfail:5 noauth:3 grace-exceeded:20

# ペナルティの最大値と最小閾値
PerSourceMaxStartups 5
PerSourceNetBlockSize 32:128

# 信頼されたネットワークをペナルティから除外
PerSourcePenaltyExemptList 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16

この設定では、認証失敗時に5秒、認証なしの切断に3秒、猶予時間超過に20秒、sshdのクラッシュ(攻撃の兆候です)に90秒のペナルティが課されます。ペナルティが蓄積すると、そのIPアドレスからの新規接続が一時的にブロックされる仕組みです。fail2banの設定に悩まされてきた方には朗報ではないでしょうか。

SSH証明書ベース認証の導入

従来のSSH公開鍵認証には、スケーラビリティの問題があります。サーバーが100台、ユーザーが50人いたら、authorized_keysの管理だけで5000エントリ。鍵のローテーションとなると……もはや手動管理は現実的じゃないですよね。

SSH証明書ベース認証は、この問題を根本的に解決するアプローチです。認証局(CA)が発行する短期間有効な証明書を使用することで、authorized_keysの分散管理が不要になり、ゼロトラストの原則に沿ったアクセス制御が実現できます。

CA鍵の生成とサーバー証明書の署名

# ============================================
# ステップ1:認証局(CA)鍵の生成
# ============================================

# ユーザー証明書用のCA鍵
ssh-keygen -t ed25519 -f /etc/ssh/ca/user_ca -C "User CA Key"

# ホスト証明書用のCA鍵
ssh-keygen -t ed25519 -f /etc/ssh/ca/host_ca -C "Host CA Key"

# ============================================
# ステップ2:ホスト証明書の生成
# ============================================

# サーバーのホスト鍵に対して証明書を発行
ssh-keygen -s /etc/ssh/ca/host_ca \
  -I "web-server-01" \
  -h \
  -n "web01.example.com,10.0.1.10" \
  -V +52w \
  /etc/ssh/ssh_host_ed25519_key.pub

# 生成された証明書の確認
ssh-keygen -L -f /etc/ssh/ssh_host_ed25519_key-cert.pub

ユーザー証明書の発行(短期間有効)

# ============================================
# ステップ3:ユーザー証明書の発行
# ============================================

# 8時間のみ有効なユーザー証明書を発行
ssh-keygen -s /etc/ssh/ca/user_ca \
  -I "tanaka@engineering" \
  -n "deploy,tanaka" \
  -V +8h \
  -O source-address=10.0.0.0/8 \
  -O no-port-forwarding \
  -O no-x11-forwarding \
  /home/tanaka/.ssh/id_ed25519.pub

# 発行された証明書の詳細を確認
ssh-keygen -L -f /home/tanaka/.ssh/id_ed25519-cert.pub

-V +8hで有効期間を8時間に制限しています。これがゼロトラストにおける「短期間有効な証明書」の考え方ですね。朝の業務開始時に証明書を発行し、終業時には自動的に失効する。万が一鍵が漏洩しても、被害の時間窓が限定されるわけです。

-O source-address=10.0.0.0/8は、この証明書による接続を社内ネットワークからに限定するオプション。証明書が社外に流出しても、社内ネットワーク以外からは使用できないという二重の防御になります。

サーバー側の証明書認証設定

# /etc/ssh/sshd_config - 証明書認証

# ユーザー証明書のCA公開鍵を指定
TrustedUserCAKeys /etc/ssh/ca/user_ca.pub

# ホスト証明書の設定
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub

# 証明書の失効リスト(オプション)
RevokedKeys /etc/ssh/ca/revoked_keys

# AuthorizedPrincipalsFileで証明書のプリンシパルとローカルユーザーの対応を定義
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
# /etc/ssh/auth_principals/deploy の内容例
deploy
engineering-team
sre-oncall

クライアント側のCA信頼設定

# ~/.ssh/known_hosts または /etc/ssh/ssh_known_hosts
@cert-authority *.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... Host CA Key

たったこの一行を追加するだけで、CAによって署名されたすべてのホスト証明書が自動的に信頼されます。新しいサーバーをデプロイしてもknown_hostsの更新は不要。初回接続時の「本当にこのホストに接続しますか?」という、あの面倒な警告も表示されなくなります。これだけでも運用の手間がだいぶ減りますよ。

証明書の自動発行・ローテーション

証明書ベース認証の真価は、自動化と組み合わせたときに発揮されます。手動で毎回証明書を発行していたのでは、正直なところ公開鍵と大差ありません。

systemdタイマーによる自動ローテーション

# /etc/systemd/system/ssh-cert-renew.service
[Unit]
Description=SSH Certificate Renewal
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/renew-ssh-cert.sh
User=root

# /etc/systemd/system/ssh-cert-renew.timer
[Unit]
Description=Renew SSH host certificate weekly

[Timer]
OnCalendar=weekly
RandomizedDelaySec=3600
Persistent=true

[Install]
WantedBy=timers.target
#!/bin/bash
# /usr/local/bin/renew-ssh-cert.sh
set -euo pipefail

CA_HOST="ca.example.com"
CERT_PATH="/etc/ssh/ssh_host_ed25519_key-cert.pub"
KEY_PATH="/etc/ssh/ssh_host_ed25519_key.pub"
HOSTNAME=$(hostname -f)

# CA サーバーに署名をリクエスト(実運用ではAPIベースの仕組みを推奨)
scp "${KEY_PATH}" "certadmin@${CA_HOST}:/tmp/sign_request/"

ssh certadmin@${CA_HOST} \
  "ssh-keygen -s /etc/ssh/ca/host_ca -I ${HOSTNAME} -h -n ${HOSTNAME} -V +2w /tmp/sign_request/$(basename ${KEY_PATH})"

scp "certadmin@${CA_HOST}:/tmp/sign_request/$(basename ${KEY_PATH} .pub)-cert.pub" "${CERT_PATH}"

# sshdを再読み込みして新しい証明書を適用
systemctl reload sshd

logger -t ssh-cert-renew "Host certificate renewed successfully for ${HOSTNAME}"

CISベンチマーク準拠の確認

CIS(Center for Internet Security)ベンチマークは、業界標準のセキュリティ設定ガイドラインです。コンプライアンス監査でSSH設定を指摘されること、めちゃくちゃ多いですよね。主要なチェック項目を確認しておきましょう。

CISベンチマーク主要チェック項目

#!/bin/bash
# ssh_cis_check.sh - CISベンチマークSSH設定チェックスクリプト
set -euo pipefail

SSHD_CONFIG="/etc/ssh/sshd_config"
PASS=0
FAIL=0

check_setting() {
    local setting="$1"
    local expected="$2"
    local description="$3"

    actual=$(sshd -T 2>/dev/null | grep -i "^${setting} " | awk '{print $2}')

    if [ "${actual,,}" = "${expected,,}" ]; then
        echo "[PASS] ${description}: ${setting} = ${actual}"
        ((PASS++))
    else
        echo "[FAIL] ${description}: ${setting} = ${actual} (期待値: ${expected})"
        ((FAIL++))
    fi
}

echo "======================================"
echo "CIS Benchmark SSH Configuration Check"
echo "======================================"
echo ""

# 5.2.1 - sshd_configのパーミッション
PERM=$(stat -c %a "${SSHD_CONFIG}" 2>/dev/null || stat -f %A "${SSHD_CONFIG}" 2>/dev/null)
if [ "${PERM}" = "600" ]; then
    echo "[PASS] sshd_config パーミッション: ${PERM}"
    ((PASS++))
else
    echo "[FAIL] sshd_config パーミッション: ${PERM} (期待値: 600)"
    ((FAIL++))
fi

# 5.2.4 - PermitRootLogin
check_setting "permitrootlogin" "no" "rootログイン禁止"

# 5.2.6 - MaxAuthTries
actual_mat=$(sshd -T 2>/dev/null | grep -i "^maxauthtries " | awk '{print $2}')
if [ "${actual_mat}" -le 4 ]; then
    echo "[PASS] MaxAuthTries: ${actual_mat} (<= 4)"
    ((PASS++))
else
    echo "[FAIL] MaxAuthTries: ${actual_mat} (期待値: <= 4)"
    ((FAIL++))
fi

# 5.2.8 - PasswordAuthentication
check_setting "passwordauthentication" "no" "パスワード認証無効化"

# 5.2.10 - PermitEmptyPasswords
check_setting "permitemptypasswords" "no" "空パスワード禁止"

# 5.2.12 - X11Forwarding
check_setting "x11forwarding" "no" "X11フォワーディング無効化"

# 5.2.14 - LogLevel
check_setting "loglevel" "verbose" "ログレベル"

# 5.2.16 - AllowTcpForwarding
check_setting "allowtcpforwarding" "no" "TCPフォワーディング無効化"

echo ""
echo "======================================"
echo "結果: PASS=${PASS} FAIL=${FAIL}"
echo "======================================"

sshd -Tによる設定検証

設定ファイルの構文チェックだけでなく、実効設定の確認も大事です。sshd -Tは、includeファイルやMatch文を展開した最終的な設定値を表示してくれます。設定ファイルをいくら眺めても、Matchブロックやincludeの組み合わせで意図しない値になっていることがあるので、必ずこちらで確認しましょう。

# 設定の構文チェック
sudo sshd -t

# 実効設定の全表示
sudo sshd -T

# 特定のユーザー・ホストからの接続をシミュレートして設定を確認
sudo sshd -T -C user=deploy,host=10.0.1.50,addr=10.0.1.50

# 特定の設定項目だけを抽出
sudo sshd -T | grep -E "^(permitrootlogin|passwordauthentication|kexalgorithms|ciphers|macs) "

ssh-auditによるセキュリティ監査

ssh-auditは、SSHサーバーとクライアントのセキュリティ設定を包括的に監査してくれるツールです。対応しているアルゴリズムの安全性を評価し、既知の脆弱性を検出して、具体的な改善提案まで出してくれます。ハードニングの仕上げとして、ぜひ活用してください。

ssh-auditのインストールと基本使用

# pipでインストール
pip3 install ssh-audit

# または最新版をGitHubから取得
git clone https://github.com/jtesta/ssh-audit.git
cd ssh-audit

# 基本的なサーバースキャン
ssh-audit localhost
ssh-audit -p 22 target-server.example.com

# JSON形式での出力(自動化・CI統合向け)
ssh-audit --json localhost

# 特定のポリシーに対する準拠チェック
ssh-audit --policy-file=custom-policy.txt localhost

# 組み込みポリシーの一覧表示
ssh-audit -L

# 特定のOSの推奨設定をハードニングガイドとして表示
ssh-audit --hardening-guide ubuntu-24.04-lts-server

ssh-auditの出力の読み方

ssh-auditは各アルゴリズムを色分けして評価します。

  • 緑(good):安全なアルゴリズム。現時点で既知の脆弱性なし
  • 黄(warn):許容可能だが、より強い代替手段がある
  • 赤(fail):脆弱なアルゴリズム。即座に無効化すべき

ハードニング後の理想的な出力はこんな感じになります。

# ssh-auditの出力例(ハードニング後)
# general
(gen) banner: SSH-2.0-OpenSSH_10.0
(gen) software: OpenSSH 10.0
(gen) compression: enabled ([email protected])

# key exchange algorithms
(kex) mlkem768x25519-sha256             -- [info] available since OpenSSH 9.9
(kex) [email protected] -- [info] available since OpenSSH 8.5
(kex) curve25519-sha256                  -- [info] available since OpenSSH 7.4
(kex) [email protected]       -- [info] available since OpenSSH 6.5

# host-key algorithms
(key) ssh-ed25519                        -- [info] available since OpenSSH 6.5

# encryption algorithms (ciphers)
(enc) [email protected]            -- [info] available since OpenSSH 6.2
(enc) [email protected]     -- [info] available since OpenSSH 6.5

# message authentication code algorithms
(mac) [email protected]     -- [info] available since OpenSSH 6.2
(mac) [email protected]     -- [info] available since OpenSSH 6.2

CI/CDパイプラインへの統合

ssh-auditをCI/CDパイプラインに組み込んでおけば、サーバーのデプロイ時にSSH設定が基準を満たしているか自動的に検証できます。設定の劣化を防ぐために、これはかなりおすすめです。

# .gitlab-ci.yml の例
ssh_security_audit:
  stage: security
  image: python:3.12-slim
  before_script:
    - pip install ssh-audit
  script:
    - |
      SERVERS="web01.example.com web02.example.com db01.example.com"
      FAILED=0
      for server in ${SERVERS}; do
        echo "=== Auditing ${server} ==="
        if ! ssh-audit --policy-file=ssh-policy.txt "${server}"; then
          echo "FAIL: ${server} does not meet SSH security policy"
          FAILED=1
        fi
      done
      exit ${FAILED}
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"

SSH鍵管理のベストプラクティス

authorized_keysの適切な管理

# authorized_keysのパーミッション確認と修正
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

# 不要な鍵の定期的な棚卸しスクリプト
#!/bin/bash
# audit_ssh_keys.sh

echo "=== SSH鍵の棚卸し ==="
echo "Date: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo ""

for home_dir in /home/*; do
    user=$(basename "${home_dir}")
    auth_keys="${home_dir}/.ssh/authorized_keys"

    if [ -f "${auth_keys}" ]; then
        key_count=$(grep -c "^ssh-" "${auth_keys}" 2>/dev/null || echo 0)
        echo "User: ${user} - Keys: ${key_count}"

        # DSA鍵の検出
        if grep -q "ssh-dss" "${auth_keys}" 2>/dev/null; then
            echo "  [WARNING] DSA鍵が残存しています!即座に置き換えてください"
        fi

        # RSA 2048ビット未満の鍵を検出
        while IFS= read -r key; do
            key_type=$(echo "${key}" | awk '{print $1}')
            if [ "${key_type}" = "ssh-rsa" ]; then
                bits=$(echo "${key}" | ssh-keygen -l -f - 2>/dev/null | awk '{print $1}')
                if [ "${bits}" -lt 4096 ] 2>/dev/null; then
                    echo "  [WARNING] RSA鍵のビット長が不十分: ${bits}ビット"
                fi
            fi
        done < "${auth_keys}"
    fi
done

FIDO2/WebAuthnハードウェアキーの活用

OpenSSH 8.2以降では、FIDO2セキュリティキー(YubiKeyなど)をSSH認証に使用できます。秘密鍵がハードウェアデバイスに格納されるため、鍵の窃取が物理的に不可能になるんです。これ、特に高権限アカウントには本当におすすめです。

# FIDO2 Ed25519鍵の生成(居住鍵:デバイスに秘密鍵を保存)
ssh-keygen -t ed25519-sk -O resident -O application=ssh:production -C "yubikey-prod"

# FIDO2 ECDSA鍵の生成(互換性が高い)
ssh-keygen -t ecdsa-sk -C "yubikey-general"

# タッチ要求の設定(接続時にデバイスのタッチが必要)
ssh-keygen -t ed25519-sk -O resident -O verify-required -C "yubikey-secure"

-O residentオプションにより、秘密鍵がセキュリティキーのデバイス上に保存されます。任意のコンピュータからセキュリティキーを差し込むだけでSSH認証が可能になるのは便利ですが、紛失のリスクもあるので、バックアップ用のセキュリティキーは必ず用意しておいてください。(実際に紛失して冷や汗をかいた経験がある人も少なくないはず……)

Matchブロックによるきめ細かなアクセス制御

sshd_configのMatchブロックを使うと、ユーザー、グループ、接続元アドレスに応じて異なるセキュリティポリシーを適用できます。全員に同じポリシーを適用するのではなく、用途に応じて柔軟に制御しましょう。

# /etc/ssh/sshd_config - Matchブロック

# SFTPのみ許可するグループ
Match Group sftp-only
    ForceCommand internal-sftp
    ChrootDirectory /data/sftp/%u
    AllowTcpForwarding no
    AllowAgentForwarding no
    X11Forwarding no
    PermitTunnel no

# CI/CDパイプラインからのデプロイユーザー
Match User deploy Address 10.0.100.0/24
    PasswordAuthentication no
    AllowTcpForwarding no
    AllowAgentForwarding no
    X11Forwarding no
    MaxSessions 5

# 緊急メンテナンス用(通常は無効化、必要時のみ有効化)
Match User emergency Address 10.0.0.0/8
    AuthenticationMethods publickey,keyboard-interactive
    MaxAuthTries 3
    MaxSessions 1
    ForceCommand /usr/local/bin/emergency-shell.sh

監視・ログ分析とインシデント対応

SSHログの構造化分析

設定をハードニングしても、それで安心というわけにはいきません。ログを継続的に監視して、異常な兆候を早期に発見することが大切です。

# 認証失敗のリアルタイム監視
journalctl -u sshd -f --grep="Failed\|Invalid\|Disconnected"

# 過去24時間の認証失敗をIPアドレス別に集計
journalctl -u sshd --since="24 hours ago" \
  | grep "Failed password\|Failed publickey" \
  | grep -oP '\d+\.\d+\.\d+\.\d+' \
  | sort | uniq -c | sort -rn | head -20

# 成功したログインの監査
journalctl -u sshd --since="7 days ago" \
  | grep "Accepted" \
  | awk '{print $1, $2, $3, $9, $11}' \
  | sort -k4

sshdのVERBOSEログの活用

LogLevel VERBOSEを設定すると、認証に使われた鍵のフィンガープリントがログに記録されます。これにより、「どの鍵で」「誰が」「いつ」ログインしたかを追跡できるようになります。インシデント発生時にはこの情報が非常に重要です。

# VERBOSEログの出力例
# Feb 12 10:30:15 web01 sshd[1234]: Found matching ED25519 key: SHA256:xxxxxxxxxxx
# Feb 12 10:30:15 web01 sshd[1234]: Accepted publickey for deploy from 10.0.1.50 port 52341

# 特定の鍵フィンガープリントによるログイン履歴を検索
journalctl -u sshd | grep "SHA256:xxxxxxxxxxx"

まとめ:SSHセキュリティハードニングのチェックリスト

最後に、本記事の内容を実践的なチェックリストとしてまとめておきます。一つずつ確認・適用していくことで、OpenSSH 10.x時代にふさわしいセキュリティレベルを達成できるはずです。

  • バージョン管理:OpenSSH 10.0以降にアップデートし、CVE-2025-26465/26466/61984の修正が適用済みであることを確認
  • 鍵アルゴリズム:DSA鍵を全廃し、Ed25519を標準鍵として採用。RSAを使う場合は4096ビット以上
  • 耐量子暗号mlkem768x25519-sha256を鍵交換アルゴリズムの最優先に設定
  • 認証:パスワード認証を無効化し、公開鍵認証または証明書認証を使用。可能であればFIDO2ハードウェアキーの導入を検討
  • アクセス制御:AllowGroups/AllowUsersで接続可能なユーザーを明示的に制限。PermitRootLogin noは必須
  • ブルートフォース対策:PerSourcePenaltiesを設定し、認証失敗時のペナルティを適用
  • 暗号化:AES-GCMおよびChaCha20-Poly1305を優先。弱い暗号化方式はすべて無効化
  • MAC:Encrypt-then-MAC(etm)方式のみを許可
  • 証明書認証:スケーラブルな環境では証明書ベース認証を導入し、短期間有効な証明書で運用
  • 監査:ssh-auditを定期的に実行し、LogLevel VERBOSEでログを詳細に記録
  • 不要機能の無効化:X11Forwarding、AllowTcpForwarding、AllowAgentForwardingは必要な場合のみ有効化

SSHのセキュリティは一度設定して終わりではありません。新たな脆弱性は常に発見され、攻撃手法も進化し続けています。量子コンピュータの実用化が近づく中、耐量子暗号への移行は「もし」ではなく「いつ」の問題です。OpenSSH 10.xはその移行への第一歩を踏み出してくれました。私たちも、この流れに乗り遅れないようにしていきましょう。

著者について Editorial Team

Our team of expert writers and editors.