# SSH

:Secure Shell ,一个加密的网络传输协议,常用于远程登录、传输文件。

  • 于 1995 年发布。
    • 最初是 Unix 系统上的一个程序,后来扩展到其他类 Unix 平台。
    • 主要有两个版本,SSH2 修复了 SSH1 的一些漏洞。
  • 基于 TCP 协议通信,工作在应用层。
  • 采用 C/S 架构工作。
    • SSH 服务器要运行一个守护进程 sshd ,监听发到某个端口(默认是 TCP 22 端口)的 SSH 请求。
    • SSH 客户端可采用 SecureCRT、Putty 等软件,向服务器发起 SSH 请求。
  • FTP、Telnet、POP 等网络传输协议采用明文传输数据,容易被监听、窃取。而 SSH 将数据经过非对称加密之后再传输,很安全,但传输耗时大了点。

# 服务器

# 部署

  • 运行 sshd 服务器:

    yum install ssh
    systemctl start sshd
    systemctl enable sshd
    
  • 或者运行 docker 镜像:

    docker run -d -p 10022:22 --name sshd \
        -e SSH_ENABLE_ROOT=true \
        -e SSH_ENABLE_ROOT_PASSWORD_AUTH=true \
        panubo/sshd:1.5.0
    
    • 需要进入容器,手动修改 root 密码。

# 配置

sshd 的主配置文件是 /etc/ssh/sshd_config ,内容示例:

Port 22                           # 监听的端口号
ListenAddress 0.0.0.0             # 允许连接的 IP

Protocol 2                        # SSH 协议的版本
HostKey /etc/ssh/ssh_host_rsa_key # 使用 SSH2 协议时,RSA 私钥的位置

MaxAuthTries 6                    # 被 SSH 客户端登录时,每个 TCP 连接的最多认证次数。如果客户端输错密码的次数达到该值的一半,则断开连接
MaxSessions 10                    # 同时最多连接的终端数
# ClientAliveCountMax 3           # 如果一个 SSH 连接长时间没有传输数据,则每隔 ClientAliveInterval 秒发送一次空消息给客户端,最多发送 ClientAliveCountMax ,然后关闭空闲的连接
# ClientAliveInterval 0           # 默认为 0 ,表示不会自动关闭空闲的连接

# 当前 SSH 服务器被 SSH 客户端连接时,允许以哪些用户的身份登录
AllowUsers root [email protected]      # 只允许这些用户进行 SSH 登录。可以使用通配符 * 和 ? ,可以指定用户的 IP ,可以指定多个用户(用空格分隔)
AllowGroups *                     # 只允许这些用户组进行 SSH 登录
PermitRootLogin yes               # 允许以 root 用户的身份登录

# 关于密码认证
PasswordAuthentication yes        # 被 SSH 客户端登录时,允许以密码方式进行身份认证(否则只能采用密钥认证)
PermitEmptyPasswords no           # 不允许使用空密码登录

# 关于密钥认证
StrictModes yes                   # 被 SSH 客户端登录时,如果采用密钥认证,则检查该用户的家目录、authorized_keys 的文件权限,只允许被该用户访问,否则存在被其他用户篡改的风险,拒绝 SSH 登录

UseDNS no                         # 是否检查 SSH 客户端的主机名、 IP 是否与 DNS 名称一致。建议禁用该功能,从而减少 SSH 连接的耗时
GSSAPIAuthentication yes          # 是否允许以 Kerberos 协议进行身份认证。该功能通常没用,不过也不影响 SSH 连接的耗时
  • 修改了 sshd_config 文件之后,要重启 sshd 服务才会生效:systemctl restart sshd

  • 使用 SSH 客户端登录 SSH 服务器时,主要有两种方式进行身份认证:

    • 密码认证
      • :登录时指定用户名,然后按提示输入密码。
      • 优点:
        • 可设置方便记忆的密码。
      • 缺点:
        • 每次登录时需要手动输入密码。
        • 采用弱密码时容易被暴力破解。
    • 密钥认证
      • :先将 SSH 客户端的公钥放在 SSH 服务器上,当 SSH 客户端要登录时会发出该公钥的指纹,而 SSH 服务器会根据指纹检查 authorized_keys 中是否有匹配的公钥,有的话就允许该客户端登录。然后 SSH 服务器会用该公钥加密一个消息回复给 SSH 客户端,该消息只能被 SSH 客户端的私钥解密,这样就完成了双向认证。
      • 优点:
        • 很安全。
        • 每次登录时不需要手动输入密码,又称为免密登录。
      • 缺点:
        • 密钥文件只能拷贝,不方便人记忆。
  • 如果 StrictModes 检查不通过,则 sshd 会拒绝 SSH 登录,并在 /var/log/secure 文件中报错 Authentication refused: bad ownership or modes for file ~/.ssh/authorized_keys 。此时需要调整文件权限:

    chown `id -u`:`id -g` ~  ~/.ssh
    chmod 700 ~  ~/.ssh
    chmod 600 ~/.ssh/authorized_keys
    
  • 即使其它主机不知道本机的 root 密码,但可能尝试用一些常见密码 SSH 登录本机,进行暴力破解。可采取以下防范措施:

    • 给本机配置复杂的 SSH 密码,或者只允许使用 SSH 密钥进行登录。
    • 禁止以 root 用户的身份登录,然后创建其它用户并分配 root 权限,这样暴力破解时还需要猜测有效的用户名。
    • 配置 /etc/hosts.allow/etc/hosts.deny ,只允许被指定的 IP 登录。
    • 如果本机被一个 IP 连续多次 SSH 登录失败,则自动封禁该 IP 。
      • Linux 发行版默认没有自动封禁 IP 的功能,用户可以安装 fail2ban 等工具来实现,或者添加 crontab 定时任务来自动扫描日志。
      • fail2ban (opens new window) 的工作原理:扫描 /var/log/btmp 等日志文件,找出需要封禁的 IP ,然后添加 iptables 规则来禁止该 IP 连接本机。

# 白名单、黑名单

  • 当 Linux 收到一个 ssh 或 telnet 的连接请求时,会先查看 /etc/hosts.allow 文件:

    • 如果包含该 IP ,则建立 TCP 连接,开始身份认证。
    • 如果不包含该 IP ,则查看 /etc/hosts.deny 文件:
      • 如果包含该 IP ,则拒绝连接。
      • 如果不包含该 IP ,则允许连接。
    • 修改这两个配置文件之后,要重启 sshd、telnet 服务才会生效。
  • /etc/hosts.allow 的内容示例:

    sshd:10.0.0.1
    sshd:10.0.0.    # 允许一个网段
    in.telnetd:10.0.0.1
    
  • /etc/hosts.deny 的内容示例:

    sshd:ALL        # 禁止所有 IP
    in.telnetd:ALL
    

# 客户端

# 命令

$ ssh <user>@<host>   # 使用 ssh 协议,以 user 用户(默认为 root )的身份登录 host 主机
      -p 22           # 指定端口号
      [command]       # 不打开远程主机的 shell ,而是以非交互模式执行一条命令

      # 建立 SSH 连接的同时,可以运行一个代理服务器
      # bind_address 默认为 localhost ,绑定到其它地址时需要在 sshd_config 中设置 GatewayPorts=yes
      -L [bind_address:]<bind_port>:<host>:<port>   # 让本地的 ssh 进程监听 bind_port 端口,将该端口收到的 TCP 数据包传输到远程主机,由后者转发到任意主机的 <host>:<port>
      -R [bind_address:]<bind_port>:<host>:<port>   # 让远程主机的 sshd 进程监听 bind_port 端口,将其 TCP 数据包传输到本机,由后者转发到任意主机的 <host>:<port>
  • 例:
    ssh [email protected]  ls -lh
    ssh [email protected]  "hostname | xargs echo"  # 用引号将待执行的命令标明为一个字符串,以免被特殊字符截断
    ssh [email protected]  'echo $HOSTNAME'         # 用单引号时,不会在本机读取变量的值,而是直接先将命令发送到远端去执行
    
  • ssh 登录成功之后,会在目标主机上打开一个 shell 终端,并将其 stdin、stdout 绑定到当前终端。
  • ssh 登录时报错 timeout 的可能原因:
    • 与目标主机的网络不通。
    • 目标主机没有运行 sshd 服务器,或者防火墙没有开通 22 端口。
    • 目标主机的负载太大,接近卡死,不能响应 ssh 连接请求。
  • 可通过以下格式发送多行命令:
    ssh -tt [email protected] << EOL
      uname -n
      echo hello
    exit
    EOL
    
    • -tt 选项用于强制分配伪终端,从而将远程终端的内容显示到当前终端。
    • 这里将 EOF 声明为定界符,将两个定界符之间的所有内容当作 sh 脚本发送到远程终端执行。甚至两个定界符之间的缩进空格,也会被一起发送。
    • 最后一条命令应该是 exit ,用于主动退出远程终端。

# 配置

  • ~/.ssh/config 文件记录了 ssh 命令的配置参数。示例:

    Host *
        StrictHostKeyChecking ask         # 是否检查远程主机的公钥。默认为 ask ,即在登录时询问是否检查。可改为 yes 或 no ,即总是检查、不检查
        # Port 22                         # SSH 登录端口
        # User root                       # SSH 登录用户
        # IdentityFile ~/.ssh/id_rsa.pub  # 用于免密登录的私钥文件
        # UserKnownHostsFile ~/.ssh/known_hosts   # known_hosts 文件的路径
    
    Host 10.0.*
        StrictHostKeyChecking no
    
    Host centos* !centos-1                # 匹配所有名称以 centos 开头的主机,排除 centos-1 主机
        StrictHostKeyChecking yes
    
    • 该文件默认不存在,可主动创建。
    • 可定义多组 Host 配置,它们会分别生效。如果某个主机匹配一个或多个 Host 模式,则采用对应的配置参数。
  • ~/.ssh/known_hosts 文件记录了所有与本机进行过 SSH 通信的远程主机的公钥,用于验证远程主机的身份。

    • ssh 命令默认会在第一次尝试登录远程主机时(不需要成功登录),按 SSH 协议进行一次通信,将其公钥记录到 known_hosts 文件。
      • 以后再尝试登录远程主机时,如果其密钥与记录的值不同,则终止登录并发出警告,怀疑该远程主机是假冒的。
    • ssh 命令第一次尝试登录远程主机时,known_hosts 文件未记录该主机的密钥,不能验证身份。默认配置了 StrictHostKeyChecking yes ,会询问是否依然要登录。如下:
      [root@CentOS ~]# ssh 10.0.0.1
      The authenticity of host '10.0.0.1 (10.0.0.1)' can't be established.
      ECDSA key fingerprint is SHA256:4v11FRemHSDNLDPETHgi7TBKLzg6STOCNXsN1aX18Ko.
      ECDSA key fingerprint is MD5:69:8a:63:97:37:2f:13:5c:4f:d4:27:46:9d:e8:7d:43.
      Are you sure you want to continue connecting (yes/no)?
      
  • ~/.ssh/authorized_keys 文件记录了一些远程主机的公钥,允许使用这些公钥对应的私钥进行认证,登录本机。

# 相关命令

# sshpass

  • 通过 sshpass 命令可以传递密码给 ssh、scp 命令,如下:
    yum install sshpass
    sshpass -p 123456 ssh [email protected]
    
    但这样会将明文密码泄露到终端,不安全。

# ssh-keygen

$ ssh-keygen                        # 生成一对 SSH 密钥(默认采用 RSA 加密算法)
            -r rsa                  # 指定加密算法(默认是 rsa)
            -C "[email protected]"   # 添加备注信息
  • 默认会将私钥文件 id_rsa 、公钥文件 id_rsa.pub 保存到 ~/.ssh/ 目录下。
  • id_rsa.pub 的内容示例如下,只占一行,包括加密算法名称、公钥值、备注信息三个字段。
    ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEArFUEBouVr03Wr8qpS2BDSDxi/HCIykWnepfVdafRHoAVcp/YxjiuszjKMRiNXY78yg4P5j9NB1+r0M9OrKkg0yspluWQDLX06EWr3l48+tVHLaCCF+JNJQIuFILvbu+/paKnM3pnCw3WROmJL/o/E75bLNowT5NSIEU2nDbJCvNIslD/VnhdXAoLyqio28McOp2Wie0fJ2x8s1vbLsjdURsr3AUO+KlAoVOgg5Ok7/RZ0ywcWE78IWkIluBQV7I0K5wia2TM+X0I3KvaX1xj5zp18+1X9UeQOEDU10mCZN+mig3Z1qJov1MPS19bMN4BhS5HXDTihW1yW+oRYptG0Q== root@CentOS-1
    

# ssh-copy-id

$ ssh-copy-id [email protected]         # 将本机的 SSH 公钥拷贝给目标主机上的指定用户
              -i ~/.ssh/id_rsa.pub  # 指定要拷贝的 SSH 公钥
  • 执行该命令时,需要先通过目标主机的 SSH 认证,才有拷贝 SSH 公钥的权限。
  • 该 SSH 公钥会被拷贝到目标主机的指定用户的 ~/.ssh/authorized_keys 文件中,允许以后本机以该用户的 SSH 私钥登录到目标主机。