サーバーの身元を検証しない鍵交換

サーバーの身元を検証しない鍵交換

説明

SSHクライアントがサーバーのホスト鍵を検証せずに信頼すると、接続先の身元を確認できません。Goの ssh.InsecureIgnoreHostKey() は、接続したサーバーの鍵を無条件で受け入れます。

通信経路上の攻撃者がSSHサーバーになりすまし、接続を傍受するおそれがあります。パスワード認証に使う値や、その接続で送信する重要なデータが漏れる可能性があります。ただし、公開鍵認証によってクライアントの秘密鍵自体がサーバーへ送信されるわけではありません。

想定される影響

  • 偽のサーバーにセッション内容を読み取られたり、改ざんされたりするおそれがあります。
  • SSHパスワード、トークン、コマンドやその結果などが漏れる可能性があります。
  • 悪意のある接続先がクライアントのコマンドを受信し、返す応答を偽造できます。

対処方法

  • 本番コードで ssh.InsecureIgnoreHostKey() を使わないでください。
  • golang.org/x/crypto/ssh/knownhosts の knownhosts.New で信頼済みのOpenSSH known_hosts を読み、HostKeyCallback に設定してください。
  • 限定した環境では、ssh.FixedHostKey() で事前に信頼したサーバー公開鍵を固定してください。
  • 信頼するファイルや鍵を安全に配布・管理し、サーバー鍵の変更時は信頼できる手順で新しい鍵を確認してください。
  • テストでも検証を省略せず、テスト用の known_hosts や固定鍵を使ってください。

例

ホスト鍵の検証だけを比較します。例のパスワードを本番で使わず、保護された設定から取得してください。known_hosts は信頼できる経路で配布し、実際のコードではホームディレクトリの取得エラーも処理してください。

変更前

go
package main

import (
    "log"

    "golang.org/x/crypto/ssh"
)

func newSSHClientInsecure(addr, user, password string) (*ssh.Client, error) {
    config := &ssh.ClientConfig{
        User: user,
        Auth: []ssh.AuthMethod{
            ssh.Password(password),
        },
        // サーバーのホスト鍵を検証しない
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
    }

    client, err := ssh.Dial("tcp", addr, config)
    if err != nil {
        return nil, err
    }
    return client, nil
}

func main() {
    client, err := newSSHClientInsecure("example.com:22", "deploy", "secret-password")
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    // ... 重要な操作を実行 ...
}

変更後

go
package main

import (
    "log"
    "os"

    "golang.org/x/crypto/ssh"
    "golang.org/x/crypto/ssh/knownhosts"
)

func newSSHClientSecure(addr, user, password, knownHostsPath string) (*ssh.Client, error) {
    // known_hostsによるホスト鍵の検証を設定
    hostKeyCallback, err := knownhosts.New(knownHostsPath)
    if err != nil {
        return nil, err
    }

    config := &ssh.ClientConfig{
        User: user,
        Auth: []ssh.AuthMethod{
            ssh.Password(password),
        },
        HostKeyCallback: hostKeyCallback,
    }

    client, err := ssh.Dial("tcp", addr, config)
    if err != nil {
        return nil, err
    }
    return client, nil
}

func main() {
    home, _ := os.UserHomeDir()
    knownHosts := home + "/.ssh/known_hosts"

    client, err := newSSHClientSecure("example.com:22", "deploy", "secret-password", knownHosts)
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    // ... ホスト鍵を検証してからSSH操作を実行 ...
}

解説:

  • 変更前: HostKeyCallback: ssh.InsecureIgnoreHostKey() は鍵を検証しないため、偽のサーバーも受け入れます。その接続のパスワード、コマンド、応答などを悪意のある接続先に読み取られたり操作されたりするおそれがあります。
  • 変更後: knownhosts.New が信頼済みのOpenSSHホスト鍵ファイルを読み、検証コールバックを提供します。鍵が一致しなければ接続は失敗します。サーバーの身元を確認し、なりすましから鍵交換と通信経路を保護します。

参考資料