简介
SSH:Secure Shell Protocol,安全的远程登录,实现加密通信,替代传统telnet协议。
端口:22/tcp
软件实现:
- OpenSSH:ssh协议的开源实现,CentOS默认安装
- Dropbear:另一个ssh协议的开源项目的实现
SSH协议版本:
- V1:基于CRC-32做MAC,不安全
- V2:双方主机协议选择安全的MAC方式,基于DH算法做秘钥交换,基于RSA或DSA实现身份验证
公钥交换原理
- 客户端发起链接请求
- 服务端返回自己的公钥,以及一个会话ID(这一步客户端得到服务端公钥)
- 客户端生成密钥对
- 客户端用自己的公钥异或会话ID,计算出一个值Res,并用服务端的公钥加密
- 客户端发送加密后的值到服务端,服务端用私钥解密,得到Res
- 服务端用解密后的值Res异或会话ID,计算出客户端的公钥(这一步服务端得到客户端公钥)
- 最终:双方各自持有三个秘钥,分别为自己的一对公、私钥,以及对方的公钥,之后的所有通讯都会被加密
异或
如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。
a ^ b=c,则a ^ c =b,b ^ c=a。互相异或可得出另一个数
a | b | a^b |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
SSH加密通讯原理
OpenSSH
C/S架构;ssh协议的开源实现,CentOS默认安装
软件包
- openssh
- openssh-clients
- openssh-server
服务器端程序:/usr/sbin/sshd
Unit文件:/usr/lib/systemd/system/sshd.service
客户端:
- Linux:ssh、scp、sftp
- Windows:Xshell、MobaXterm、Putty、SecureCRT
客户端SSH命令
ssh命令是ssh客户端,允许实现对远程系统经过验证的加密安全访问。
当用户远程访问ssh服务器时,会复制ssh服务器 /etc/ssh/ssh_host*key.pub 文件中公钥到客户机的 ~/.ssh/know_hosts 文件中;下次连接时,会自动匹配响应的私钥,不能匹配,将拒绝连接。
ssh客户端配置文件:/etc/ssh/ssh_config
首次登录是否检查,可设为 no
#StrictHostKeyChecking ask
私钥文件路径
# IdentityFile ~/.ssh/id_rsa
# IdentityFile ~/.ssh/id_dsa
# IdentityFile ~/.ssh/id_ecdsa
# IdentityFile ~/.ssh/id_ed25519
连接服务器端口
# Port 22
格式
ssh user@host command
选项:
-p port:远程服务器监听的端口
-b:指定连接的源IP
-x:支持X11转发
-t:强制伪tty分配。
-o 选项:如-o StrictHostKeyChecking=no
-i:指定私钥文件路径,实现基于key验证,默认使用文件: ~/.ssh/id_dsa,
~/.ssh/id_ecdsa, ~/.ssh/id_ed25519,~/.ssh/id_rsa等
从 192.168.28.10 通过多个跳板登录 192.168.28.40
[root@centos8 ~]#ssh -t 192.168.28.20 ssh -t 192.168.28.30 ssh -t 192.168.28.40
[root@centos8 ~]#ssh -o StrictHostKeyChecking=no root@192.168.28.40 "cat /etc/os-release"
root@192.168.28.40's password:
NAME="Ubuntu"
VERSION="20.04.3 LTS (Focal Fossa)"
ID=ubuntu
...
在远程主机上执行本地脚本
[root@centos8 ~]#cat test.sh
#!/bin/bash
#********************************************************************
#FileName: test.sh
#Version: 1.0
#Date: 2024-01-02
#Author: wenzi
#Description: This is description
#********************************************************************
hostname -I
[root@centos8 ~]#ssh root@192.168.28.20 < test.sh
Pseudo-terminal will not be allocated because stdin is not a terminal.
root@192.168.28.20's password:
192.168.28.20
统计ssh登录失败次数最多的前2个远程IP 并 记录再 temp.txt 文件中
[root@wenzi ~]#lastb
root ssh:notty 192.168.28.30 Tue Jan 2 13:48 - 13:48 (00:00)
root ssh:notty 192.168.28.30 Tue Jan 2 13:48 - 13:48 (00:00)
root ssh:notty 192.168.28.30 Tue Jan 2 13:48 - 13:48 (00:00)
root ssh:notty 192.168.28.20 Tue Jan 2 13:47 - 13:47 (00:00)
root ssh:notty 192.168.28.10 Tue Jan 2 13:47 - 13:47 (00:00)
root ssh:notty 192.168.28.10 Tue Jan 2 13:47 - 13:47 (00:00)
btmp begins Tue Jan 2 13:47:26 2024
[root@wenzi ~]#lastb -f temp.txt | awk '{print $3}' | sort | uniq -c | sort -rn | head -n 2
3 192.168.28.30
2 192.168.28.10
其它ssh客户端工具
scp
scp 选项 源地址 目标地址
常见选项:
-r:递归复制
-P:指定远程主机监听的端口
-p:保持原文件的属性信息
-q:静默模式
rsync
rsync可以基于ssh和rsync协议实现高效的远程系统之间复制文件;比SCP更快,基于增量数据同步,只复制两方不同的文件。通信双方都需要安装rsync软件
rsync 选项 源地址 目标地址
rsync -av /etc ...表示复制目录和目录下的文件
rsync -av /etc/ ...只复制目录下文件
常见选项:
-v:显示详细过程
-a:以递归方式传输文件,并保持所有文件属性。相当于-rlptgod
-r:递归复制目录树
-p:保留权限
-t:保留修改时间戳
-g:保留组信息
-o:保留所有者信息
-l:将软链接文件本身进行复制(默认)
-L:将软链接文件指向的文件复制
-u:如果接受者文件比发送者文件新,将忽略同步
sshpass
自动登录ssh工具。多用在shell脚本中,无需输入密码(本机know_hosts文件中有的主机才能生效)
sshpass 选项 命令 参数
常见选项:
-p password:后面跟密码,直接登录远程服务器
-f 文件名:后跟保存密码的文件名,密码是文件内容的第一行
-e:将环境变量SSHPASS作为密码
SSH登录验证方式
- 用户/口令
- 秘钥
基于用户和口令验证
- 客户端发起ssh请求,服务器会把自己的公钥发送给用户(即上方公钥交换原理)
- 用户会根据服务器发来的公钥对密码进行加密。前两个过程
- 加密后的信息回传给服务器,服务器用自己的私钥解密,如果密码正确,则用户登录成功
注意:服务器端的公钥保存到客户端
基于秘钥验证
- 首先在客户端生成一对密钥(ssh-keygen)
- 并将客户端的公钥ssh-copy-id 拷贝到服务端
- 当客户端再次发送一个连接请求,包括ip、用户名
- 服务端得到客户端的请求后,会到authorized_keys中查找,如果有响应的IP和用户,就会随机生成一个字符串,如13579
- 服务端将使用客户端拷贝过来的公钥进行加密,然后发送给客户端
- 得到服务端发来的消息后,客户端会使用私钥进行解密,然后将解密后的字符串发送给服务端
- 服务端接受到客户端发来的字符串后,跟之前的字符串进行对比,如果一致,就允许免密码登录
注意:客户端的公钥保存到服务器端
实现基于秘钥登录
在客户端生成密钥对
-P给秘钥加密;-f指定秘钥保存位置,默认保存至 ~/.ssh/ 目录
ssh-keygen -t rsa [-P 'password'] [-f “~/.ssh/id_rsa"]
生成秘钥
[root@centos8 ~]#ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Created directory '/root/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:+bcCLIGsYuC7JgDBcNBTqFSw9Nx+JKlG6NpIP3tGmOs root@centos8.magedu.com
The key's randomart image is:
+---[RSA 3072]----+
|==++. |
|o+O . . |
|.= * = . |
|= . = + . |
|+o +o. +S |
|==+o .o o. |
|=.ooo . .. . |
|.o .oo .. . |
|o.oEo .. |
+----[SHA256]-----+
查看秘钥对
[root@centos8 ~]#ll /root/.ssh/
total 8
-rw------- 1 root root 2610 Jan 3 18:49 id_rsa 私钥
-rw-r--r-- 1 root root 577 Jan 3 18:49 id_rsa.pub 公钥
把公钥文件传输至远程服务器对应用户的家目录
-i指定公钥文件;复制到远程主机上后是 /root/.ssh/authorized_keys 文件
ssh-copy-id [-i [identity_file]] [user@]host
将本机公钥复制到远程主机上
[root@centos8 ~]#ssh-copy-id root@192.168.28.20
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
The authenticity of host '192.168.28.20 (192.168.28.20)' can't be established.
ECDSA key fingerprint is SHA256:4n3yr6WWNPQlI+F3fUDh71yOMsibBLJMBjqJawfaEE0.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@192.168.28.20's password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh 'root@192.168.28.20'"
and check to make sure that only the key(s) you wanted were added.
在远程主机上查看收到的公钥
[root@wenzi ~]#ll .ssh/
total 4
-rw------- 1 root root 577 Jan 3 18:50 authorized_keys
[root@wenzi ~]#cat .ssh/authorized_keys
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDtXegd7leQgFRBYPoPtNHrSA7L5g6lSy2HAfFmM03OKas97yeRuATlmKekymUACHOVsP6CJQRm8Sn33y2aVJaQ8wcerm/+EFDVk7PWtrqIjyf8Hov6st64t4xEeIDIxjq6wqDGFqeZ0GPoI0HlQC2OBjSs9XAG0GCbqEOx7kUBYKBINWrq1K3wWiJDWrycV/AaHA7gI6nPctIwD4vrF44279CdVE+ih6QcRlglFyXZFTCDvdAeDIeyITZfe/Iie5Vlr1OKEFnbUYEmMfBZIG9YPo7DESYLi0J7UHr/om9j0r6e3HfmyqxUHTyvwp7ZFEa6BvnJwrbiejdFmflJmpecl96c1qzB/Jx/4y2Jbrp2kKWTCH4yiFBxuwpX3Ezws2Sn23F3o4pFpmy5AJp94RZKOPBM0/0v/a/tmDWxnr4h9eEKPiSE9l4SIYN1xFSvY59+gCNV3WWMEYvgxK5lsD2YAeRnzQuiUtXID6n/M6xs7KBHQyKhcmxLoqU9zbQuIBk= root@centos8.magedu.com
重设私钥口令
ssh-keygen -p
SSH服务器端配置
服务器端:sshd
服务器端的配置文件: /etc/ssh/sshd_config
服务器端的配置文件帮助:man 5 sshd_config
常用参数
Port 22 默认的SSH端口,生产中建议修改
ListenAddress ip 指定SSH服务器监听的IP地址
LoginGraceTime 2m 在此时间内,如果用户未成功登录,连接将被终止。可防止暴力破解
PermitRootLogin yes 允许root用户登录,建议改为no;ubuntu默认禁止root远程登录
StrictModes yes 检查.ssh/文件的所有者,权限等
MaxAuthTries 6 用户在登录时尝试的最大次数,超过此次数,链接被终止
MaxSessions 10 一个客户端可以同时打开的最大SSH会话数
PubkeyAuthentication yes 允许使用公钥key进行身份验证
PermitEmptyPasswords no 禁止使用空密码登录
PasswordAuthentication yes 允许密码验证登录,建议使用更安全的key验证登录
ClientAliveInterval 10 服务器将发送一个消息给客户端,检查它是否仍然活动的间隔时间(以秒为单位)。这有助于检测“僵尸”客户端连接。
ClientAliveCountMax 3 在此间隔时间内,服务器发送的消息的数量。如果达到这个数量而客户端没有响应,连接将被终止。
UseDNS yes 在尝试解析远程用户名时使用DNS查找其IP地址,建议设置为no,可提高速度减少风险
GSSAPIAuthentication yes 提高速度可改为no
MaxStartups 通常用于限制同时尝试连接到SSH服务的客户端的最大数量。它通常与ClientAliveInterval和ClientAliveCountMax一起使用,以防止暴力破解攻击。
Banner /path/file 当客户端尝试连接到SSH服务器时,它会显示指定的文件中的消息作为“横幅”。这通常用于显示警告消息或指导客户端如何正确登录
限制可登录用户方法
[root@centos8 ~]#vim /etc/ssh/sshd_config
...
AllowUsers user1 user2 user3
DenyUsers user1 user2 user3
AllowGroups g1 g2
DenyGroups g1 g2
[root@centos8 ~]#systemctl restart sshd
设置ssh空闲60s自动注销
[root@centos8 ~]#vim /etc/ssh/sshd_config
...
ClientAliveInterval 60
ClientAliveCountMax 0
[root@centos8 ~]#systemctl restart sshd
解决ssh登录缓慢的问题
[root@centos8 ~]#vim /etc/ssh/sshd_config
...
UseDNS no
GSSAPIAuthentication no
[root@centos8 ~]#systemctl restart sshd
ssh服务最佳实践
建议使用非默认端口
Port 15413
禁止使用protocol version 1
Protocol 2
限制可登录用户
AllowUsers user1 user2 或 DenyUsers user1 user2
设定空闲会话超时时长
ClientAliveInterval 300(每5分钟发送一次活跃请求)
ClientAliveCountMax 2(如果客户端连续两次未响应则断开连接)
利用防火墙设置ssh访问策略
iptables
仅监听特定的IP地址
ListenAddress 192.168.1.10
基于口令认证时,使用强密码策略,
生成随机密码 tr -dc A-Za-z0-9_ < /dev/urandom | head -c 12| xargs
使用基于密钥的认证
RSAAuthentication yes, PubkeyAuthentication yes, AuthorizedKeysFile .ssh/authorized_keys
禁止使用空密码
PermitEmptyPasswords no
禁止root用户直接登录
PermitRootLogin no
限制ssh的访问频度和并发在线数
MaxAuthTries 6 在连接被断开之前,允许用户尝试认证的最大次数
MaxSessions 10 定了一个用户能够同时开启的最大会话数
经常分析日志
案例
免输入密码直接登录目标主机
[root@centos8 ~]#sshpass -p admin ssh root@192.168.28.20
批量修改root密码为随机密码
[root@wenzi data]#vim chgpass.sh
#!/bin/bash
#********************************************************************
#FileName: chgpass.sh
#Version: 1.0
#Date: 2024-01-02
#Author: wenzi
#Description: This is description
#********************************************************************
rpm -q sshpass &> /dev/null && yum -y install sshpass &> /dev/null
#服务器原密码
export SSHPASS=123456
net=192.168.28
for i in {1..254};do
{
pass=`openssl rand -base64 9`
sshpass -e ssh -o StrictHostKeyChecking=no ${net}.${i} "echo root:$pass | chpasswd"
echo ${net}.${i}:${pass} >> host.txt
}&
done
wait
基于key批量部署多台主机验证
[root@wenzi data]#cat keytest.sh
#!/bin/bash
#********************************************************************
#FileName: keytest.sh
#Version: 1.0
#Date: 2024-01-03
#Author: wenzi
#Description: This is description
#********************************************************************
hosts="
192.168.28.20
192.168.28.30
192.168.28.40
"
pass=wenzi
#-P表示无需给私钥加密;-f指定秘钥存储位置
ssh-keygen -P "" -f /root/.ssh/id_rsa &> /dev/null
rpm -q sshpass &> /dev/null || yum -y install sshpass &> /dev/null
for i in $hosts;do
{
sshpass -p $pass ssh-copy-id -o StrictHostKeyChecking=no -i /root/.ssh/id_rsa.pub $i &> /dev/null
}&
done
wait
基于key实现多台主机无障碍互访
原理:多台主机共用一套秘钥
PASS=centos1
#设置网段最后的地址,4-255之间,越小扫描越快
END=254
IP=`ip a s eth0 | awk -F'[ /]+' 'NR==3{print $3}'`
#取IP前三位,即网段
NET=${IP%.*}.
. /etc/os-release
rm -f /root/.ssh/id_rsa
[ -e ./SCANIP.log ] && rm -f SCANIP.log
for((i=3;i<="$END";i++));do
ping -c 1 -w 1 ${NET}$i &> /dev/null && echo "${NET}$i" >> SCANIP.log &
done
wait
ssh-keygen -P "" -f /root/.ssh/id_rsa
if [ $ID = "centos" -o $ID = "rocky" ];then
rpm -q sshpass || yum -y install sshpass
else
dpkg -i sshpass &> /dev/null ||{ apt update; apt -y install sshpass; }
fi
sshpass -p $PASS ssh-copy-id -o StrictHostKeyChecking=no $IP
AliveIP=(`cat SCANIP.log`)
for n in ${AliveIP[*]};do
sshpass -p $PASS scp -o StrictHostKeyChecking=no -r /root/.ssh root@${n}:
done
#把.ssh/known_hosts拷贝到所有主机,使它们第一次互相访问时不需要输入回车
for n in ${AliveIP[*]};do
scp /root/.ssh/known_hosts ${n}:.ssh/
done