前言

我负责维护的一套系统,由于面向银行收款,搭建了2个实例的sftp服务器,前端使用F5做负载均衡。
但客户端登录时,时不时会有因指纹(fingerprint)变化出现的中间人攻击(man-in-the-middle attack)提醒。
起初认为是两个实例没有同步.ssh目录内的秘钥导致,把秘钥从一台实例拷贝到另外一台,问题仍没有解决。

1
2
3
4
5
6
7
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that the RSA host key has just been changed. The fingerprint for the RSA key sent by the remote host is 50:e6:cb:58:bc:b7:a3:f6:e8:8f:46:a7:c1:5f:c2:df.
Please contact your system administrator.
Add correct host key in /home/karbin/.ssh/known_hosts to get rid of this message.
Offending key in /home/karbin/.ssh/known_hosts:7 RSA host key for 192.168.0.128 has changed and you have requested strict checking.
Host key verification failed.

为什么要使用fingerprint?

SSH 连接在第一次连接到服务器时最容易受到攻击。
在第一次连接到服务器后,SSH 客户端会记录其指纹。
如果该指纹随后发生变化,即有人试图诱骗连接到恶意服务器,这时SSH 客户端将发出警告,指纹已发生变化。
当我们首次连接到VPS 服务器时,客户端无法记录其指纹并验证其是否正确。
因此,攻击者可以成功执行中间人攻击,确保从一开始就连接到正确服务器的唯一方法是手动检查SSH密钥指纹。

如何查看服务端的fingerprint?

在检查指纹(fingerprint)之前,需要知道生成指纹使用的算法。
比如客户端A连接客户端B,首次连接时出现提示:

1
2
3
4
5
wise@wise-desktop:~$ sftp -oPort=9222 karbin@127.0.0.1
The authenticity of host '[127.0.0.1]:9222 ([127.0.0.1]:9222)' can't be established.
ED25519 key fingerprint is SHA256:qD4inP4dspacQdGKPUu6q8Pa151jboZzcUYF4VInPaY.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

上面的信息提示,密钥使用ED25519算法,并使用SHA256哈希处理。
密钥算法也可能是 ECDSA、RSA 和 DSA,并且哈希算法可能是 MD5 而不是 SHA256。

知道了生成算法,就要找密钥证书文件,该文件位于/etc/ssh/ssh_host_ed25519_key.pub

1
2
3
4
# 执行命令
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
# 输出结果和登录时弹出的一致。
256 SHA256:qD4inP4dspacQdGKPUu6q8Pa151jboZzcUYF4VInPaY no comment (ED25519)

下面提供常见的秘钥生成fingerprint的算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ED25519:
SHA256: ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
MD5: ssh-keygen -E md5 -lf /etc/ssh/ssh_host_ed25519_key.pub

ECDSA:
SHA256: ssh-keygen -lf /etc/ssh/ssh_host_ecdsa_key.pub
MD5: ssh-keygen -E md5 -lf /etc/ssh/ssh_host_ecdsa_key.pub

RSA:
SHA256: ssh-keygen -lf /etc/ssh/ssh_host_rsa_key.pub
MD5: ssh-keygen -E md5 /etc/ssh/ssh_host_rsa_key.pub

DSA:
SHA256: ssh-keygen -lf /etc/ssh/ssh_host_dsa_key.pub
MD5: ssh-keygen -E md5 /etc/ssh/ssh_host_dsa_key.pub

https://bitlaunch.io/blog/how-to-check-your-ssh-key-fingerprint/

如何解决sftp实例之间的fingerprint指纹差异?

知道了fingerprint生成原理,终于有了解决方案,两台sftp server实例,需要同步/etc/ssh/ssh_host*开头的秘钥文件。
从一台拷贝到另外一台,覆盖前先备份,覆盖后重启sshd进程即可。

试验验证

本地使用vmware启动两台装有Centos7系统实例,并分别获取fingerprint:

  • 192.168.0.128

    1
    2
    3
    # cd /etc/ssh
    # ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
    256 SHA256:qD4inP4dspacQdGKPUu6q8Pa151jboZzcUYF4VInPaY no comment (ED25519)
  • 192.168.0.162

    1
    2
    3
    # cd /etc/ssh
    # ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
    256 SHA256:Q+GouijSIb+PjW9pOzUOAr2Rb/DD6HzO3dmOxF2gFfY no comment (ED25519)

本地树莓派(Raspberry)设备,使用nginx提供的stream模块, 通过9222端口,代理后端两个实例的sftp服务。
(nginx使用docker部署)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# nginx.conf, 加在http模块的上方。

stream {
#sftp_stream
upstream sftp_srv { #sftp_srv为组名,可自定义命名
hash $remote_addr consistent;
server 192.168.0.128:22 max_fails=3 fail_timeout=60s;
server 192.168.0.162:22 max_fails=3 fail_timeout=60s;
}
#sftp代理
server {
listen 9222;
#9222端口为sftp服务的代理端口,客户端通过nginx代理登陆sftp服务器将通过此端口。
proxy_connect_timeout 300s;
proxy_timeout 300s;
proxy_pass sftp_srv;
#sftp_srv就是上面配置的upstream sftp_srv
}
}

以上,nginx配置的stream模块,根据客户端地址的Hash值,转发到某一个后端实例。
理论上,只要客户端网络条件不发生变化,初次信任sftp服务的fingerprint后,是不会有问题发生的。

在树莓派执行登录命令:

1
2
3
4
5
wise@wise-desktop:~$ sftp -oPort=9222 karbin@127.0.0.1
The authenticity of host '[127.0.0.1]:9222 ([127.0.0.1]:9222)' can't be established.
ED25519 key fingerprint is SHA256:qD4inP4dspacQdGKPUu6q8Pa151jboZzcUYF4VInPaY.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

以上,说明nginx将请求转发给 192.168.0.128

为了确认转发实例,我们将128从nginx代理池移除,并重启nginx,如下:

1
2
3
4
5
6
7
8
9
10
11
12
stream {  
#sftp_stream
upstream sftp_srv { #sftp_srv为组名,可自定义命名
hash $remote_addr consistent;
# server 192.168.0.128:22 max_fails=3 fail_timeout=60s;
server 192.168.0.162:22 max_fails=3 fail_timeout=60s;
}
...
}

# 重启nginx
sudo docker restart nginx

重新执行sftp登录命令:

1
2
3
4
5
wise@wise-desktop:~$ sftp -oPort=9222 karbin@127.0.0.1
The authenticity of host '[127.0.0.1]:9222 ([127.0.0.1]:9222)' can't be established.
ED25519 key fingerprint is SHA256:Q+GouijSIb+PjW9pOzUOAr2Rb/DD6HzO3dmOxF2gFfY.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

以上,根据打印的fingerprint,确认此次请求已转发到162

下面执行密钥同步,将128的密钥,拷贝到162

1
2
3
4
5
6
7
8
9
# 登录162,执行拷贝:
cp -rf /etc/ssh ./ssh-bak
scp -r 192.168.0.128:/etc/ssh ./

# 执行覆盖
cp -f ./ssh/ssh_host* /etc/ssh/
# 重启sshd
systemctl restart sshd

树莓派重新执行sftp登录命令,结果:

1
2
3
4
5
wise@wise-desktop:~$ sftp -oPort=9222 karbin@127.0.0.1
The authenticity of host '[127.0.0.1]:9222 ([127.0.0.1]:9222)' can't be established.
ED25519 key fingerprint is SHA256:qD4inP4dspacQdGKPUu6q8Pa151jboZzcUYF4VInPaY.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

以上,可以证明,客户端首次登录sftp,弹出的fingerprint,是根据/etc/ssh目录下的全局主机密钥生成的。
当使用负载均衡设备,后端挂载多个sftp实例时,需要同步实例之间的密钥证书。

系统范围的配置文件

  • /etc/ssh/moduli
    包含用于 Diffie-Hellman 密钥交换的 Diffie-Hellman 组,这对于构建安全传输层至关重要。在 SSH 会话开始时交换密钥时,会创建一个共享的秘密值,任何一方都无法单独确定该值。然后使用此值提供主机身份验证。

  • /etc/ssh/ssh_config
    默认的 SSH 客户端配置文件。请注意,~/.ssh/config如果存在,它将被覆盖。

  • /etc/ssh/sshd_config
    守护进程的配置文件sshd。

  • /etc/ssh/ssh_host_dsa_key
    守护进程使用的 DSA 私钥sshd。

  • /etc/ssh/ssh_host_dsa_key.pub
    守护进程使用的 DSA 公钥sshd。

  • /etc/ssh/ssh_host_key
    sshd守护进程用于 SSH 协议版本 1 的RSA 私钥。

  • /etc/ssh/ssh_host_key.pub
    sshd守护进程用于 SSH 协议版本 1 的RSA 公钥。

  • /etc/ssh/ssh_host_rsa_key
    sshd守护进程用于 SSH 协议第 2 版的RSA 私钥。

  • /etc/ssh/ssh_host_rsa_key.pub
    sshd守护进程用于 SSH 协议第 2 版的RSA 公钥。

  • /etc/pam.d/sshd
    守护进程的 PAM 配置文件sshd。

  • /etc/sysconfig/sshd
    服务的配置文件sshd。

用户特定的配置文件

  • ~/.ssh/authorized_keys
    保存服务器的授权公钥列表。
    当客户端连接到服务器时,服务器通过检查存储在此文件中的签名公钥来验证客户端的身份。

  • ~/.ssh/id_dsa
    包含用户的 DSA 私钥。

  • ~/.ssh/id_dsa.pub
    用户的 DSA 公钥。

  • ~/.ssh/id_rsa
    sshSSH 协议第 2 版使用的 RSA 私钥。

  • ~/.ssh/id_rsa.pub
    sshSSH 协议第 2 版使用的 RSA 公钥。

  • ~/.ssh/identity
    sshSSH 协议版本 1使用的 RSA 私钥。

  • ~/.ssh/identity.pub
    sshSSH 协议版本 1使用的 RSA 公钥。

  • ~/.ssh/known_hosts
    包含用户访问的 SSH 服务器的 DSA 主机密钥。此文件对于确保 SSH 客户端连接到正确的 SSH 服务器非常重要。

https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/6/html/deployment_guide/s1-ssh-configuration#s2-ssh-configuration-configs