强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

SSH 服务器完全指南 / 第10章 安全加固实战

第10章 安全加固实战

10.1 SSH 安全加固清单

优先级措施影响
🔴 必须禁用密码认证极高
🔴 必须禁止 root 密码登录
🔴 必须使用强加密算法
🟡 强烈推荐安装 Fail2Ban
🟡 强烈推荐限制用户/组访问
🟢 推荐更改默认端口
🟢 推荐端口敲门
🟢 推荐使用 SSH 证书

10.2 禁用密码认证

前提条件

在禁用密码认证之前,必须确保

  1. ✅ 已为所有管理员部署 SSH 公钥
  2. ✅ 已测试密钥认证可以正常登录
  3. ✅ 有带外访问方式(控制台/VNC)以防万一

配置步骤

# 1. 验证密钥认证可用
ssh -o PreferredAuthentications=publickey -i ~/.ssh/id_ed25519 admin@server "echo OK"
# 确认输出 OK

# 2. 修改配置
# /etc/ssh/sshd_config
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no

# 3. 检查语法
sudo sshd -t

# 4. 重新加载(不要重启,保持当前连接)
sudo systemctl reload sshd

# 5. 在**新的终端**测试密钥登录
ssh -i ~/.ssh/id_ed25519 admin@server

⚠️ 警告: 切勿在断开当前 SSH 会话前修改密码认证设置。始终保持一个活动的 SSH 连接作为备用。


10.3 Fail2Ban 防暴力破解

安装

# Debian/Ubuntu
sudo apt install fail2ban

# RHEL/CentOS
sudo yum install epel-release
sudo yum install fail2ban

基本配置

# 创建本地配置文件(不要直接修改 jail.conf)
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

# 或创建更简洁的本地配置
sudo vi /etc/fail2ban/jail.local
# /etc/fail2ban/jail.local

[DEFAULT]
# 忽略的 IP(白名单)
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24

# 封禁时间(秒)
bantime = 3600

# 查找时间窗口(秒)
findtime = 600

# 最大失败次数
maxretry = 5

# 封禁动作
banaction = iptables-multiport

[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
maxretry = 3
bantime = 7200
findtime = 300

使用 systemd 日志(推荐)

# /etc/fail2ban/jail.local

[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = systemd
maxretry = 3
bantime = 7200
findtime = 300

高级配置

# /etc/fail2ban/jail.local

[DEFAULT]
# 使用 firewalld(RHEL/CentOS)
# banaction = firewallcmd-ipset

# 使用 nftables
# banaction = nftables-multiport

# 发送邮件通知
destemail = admin@example.com
sender = fail2ban@example.com
mta = sendmail
action = %(action_mwl)s

# 多次封禁递增时间
# 第一次:1小时,第二次:1天,第三次:1周
bantime.increment = true
bantime.factor = 24
bantime.maxtime = 604800

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = %(sshd_log)s
maxretry = 3
bantime = 3600

# 针对特定攻击模式
[sshd-ddos]
enabled = true
port = ssh
filter = sshd-ddos
logpath = %(sshd_log)s
maxretry = 10
bantime = 3600

# 保护其他服务
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 5
bantime = 3600

Fail2Ban 管理命令

# 启动/停止/重启
sudo systemctl start fail2ban
sudo systemctl stop fail2ban
sudo systemctl restart fail2ban
sudo systemctl status fail2ban

# 查看状态
sudo fail2ban-client status
sudo fail2ban-client status sshd

# 手动封禁 IP
sudo fail2ban-client set sshd banip 192.168.1.100

# 手动解封 IP
sudo fail2ban-client set sshd unbanip 192.168.1.100

# 查看封禁日志
sudo journalctl -u fail2ban | grep Ban

# 查看所有被封禁的 IP
sudo fail2ban-client banned

自定义过滤规则

# /etc/fail2ban/filter.d/custom-sshd.conf
[Definition]
failregex = ^.*Failed password for .* from <HOST>.*$
            ^.*Failed keyboard-interactive for .* from <HOST>.*$
            ^.*Invalid user .* from <HOST>.*$
            ^.*Connection closed by authenticating user .* <HOST>.*\[preauth\]$
ignoreregex =

10.4 更改默认端口

# /etc/ssh/sshd_config
Port 2222

# 更新 SELinux(RHEL/CentOS)
sudo semanage port -a -t ssh_port_t -p tcp 2222

# 更新防火墙
# UFW
sudo ufw allow 2222/tcp
sudo ufw delete allow ssh

# firewalld
sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --permanent --remove-service=ssh
sudo firewall-cmd --reload

注意: 更改端口只是减少噪音日志,不能替代真正的安全措施。自动化扫描器会扫描所有端口。


10.5 端口敲门(Port Knocking)

什么是端口敲门?

端口敲门是一种通过按特定顺序访问关闭的端口来临时打开 SSH 端口的技术。

步骤 1: 访问 7000 端口
步骤 2: 访问 8000 端口
步骤 3: 访护 9000 端口
→ SSH 端口 22 自动打开
→ 连接完成后自动关闭

使用 knockd 实现

# 安装 knockd
sudo apt install knockd

# 编辑配置
sudo vi /etc/knockd.conf
# /etc/knockd.conf

[options]
    logfile = /var/log/knockd.log
    UseSyslog

[openSSH]
    sequence    = 7000,8000,9000
    seq_timeout = 10
    command     = /sbin/iptables -I INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
    tcpflags    = syn

[closeSSH]
    sequence    = 9000,8000,7000
    seq_timeout = 10
    command     = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
    tcpflags    = syn
# 启动 knockd
sudo systemctl enable knockd
sudo systemctl start knockd

# 默认接口配置
# /etc/default/knockd
START_KNOCKD=1
KNOCKD_OPTS="-i eth0"

客户端使用

# 安装 knock 客户端
sudo apt install knockd

# 敲门(打开 SSH)
knock server-ip 7000 8000 9000

# 等待 1 秒
sleep 1

# 连接 SSH
ssh user@server-ip

# 敲门(关闭 SSH)
knock server-ip 9000 8000 7000

一键脚本

#!/bin/bash
# knock-ssh.sh

SERVER=$1
USER=$2
SEQUENCE="7000 8000 9000"

if [ -z "$SERVER" ] || [ -z "$USER" ]; then
    echo "Usage: $0 <server> <user>"
    exit 1
fi

echo "Knocking on $SERVER..."
knock $SERVER $SEQUENCE
sleep 2

echo "Connecting..."
ssh $USER@$SERVER

echo "Closing port..."
knock $SERVER 9000 8000 7000

使用 iptables 实现(不依赖 knockd)

# 使用 iptables recent 模块
# 只允许在"敲门"后连接

iptables -A INPUT -p tcp --dport 7000 -m recent --set --name KNOCK1
iptables -A INPUT -p tcp --dport 8000 -m recent --set --name KNOCK2 -m recent --rcheck --name KNOCK1
iptables -A INPUT -p tcp --dport 9000 -m recent --set --name KNOCK3 -m recent --rcheck --name KNOCK2
iptables -A INPUT -p tcp --dport 22 -m recent --rcheck --seconds 30 --name KNOCK3 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j DROP

10.6 iptables / nftables 防火墙规则

iptables 限制 SSH 访问

# 只允许特定 IP 访问 SSH
sudo iptables -A INPUT -p tcp --dport 22 -s 192.168.1.0/24 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 22 -j DROP

# 限制连接速率(每分钟最多 3 个新连接)
sudo iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set
sudo iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 4 -j DROP
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# 使用 hashlimit(更灵活)
sudo iptables -A INPUT -p tcp --dport 22 -m hashlimit --hashlimit-above 3/min --hashlimit-burst 3 --hashlimit-mode srcip --hashlimit-name ssh -j DROP

nftables 配置

#!/usr/sbin/nft -f
# /etc/nftables.conf

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        
        # 允许已建立的连接
        ct state established,related accept
        
        # 允许本地回环
        iif lo accept
        
        # SSH 速率限制
        tcp dport 22 ct state new limit rate 3/minute accept
        tcp dport 22 ct state new drop
        
        # 其他允许的服务
        tcp dport { 80, 443 } accept
        icmp type echo-request accept
    }
}

10.7 AllowUsers / AllowGroups

# /etc/ssh/sshd_config

# 只允许特定用户
AllowUsers admin deploy monitoring

# 只允许特定组
AllowGroups ssh-users admins

# 组合条件
AllowUsers admin@192.168.1.* deploy@10.0.0.*

# 禁止特定用户
DenyUsers guest test
DenyGroups visitors

10.8 安全审计与监控

实时监控 SSH 登录

# 实时查看认证日志
sudo tail -f /var/log/auth.log | grep ssh

# 查看失败的登录尝试
sudo grep "Failed password" /var/log/auth.log | tail -20

# 统计失败次数(按 IP)
sudo grep "Failed password" /var/log/auth.log | \
    awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -20

# 查看成功的登录
sudo grep "Accepted" /var/log/auth.log | tail -20

# 查看当前登录用户
who
w
last

设置登录通知

# /etc/ssh/sshrc
# 每次 SSH 登录时发送通知

IP=$(echo $SSH_CONNECTION | awk '{print $1}')
HOSTNAME=$(hostname)
USER=$(whoami)
DATE=$(date '+%Y-%m-%d %H:%M:%S')

# 发送邮件通知
echo "SSH login from $IP to $HOSTNAME as $USER at $DATE" | \
    mail -s "SSH Login Alert: $HOSTNAME" admin@example.com

# 或发送到 Slack/Telegram
# curl -X POST -H 'Content-type: application/json' \
#     --data '{"text":"SSH login from '$IP' to '$HOSTNAME' as '$USER'"}' \
#     https://hooks.slack.com/services/xxx/yyy/zzz

使用 wtmp/lastlog 审计

# 查看登录历史
last -i | head -30

# 查看失败登录
lastb | head -30

# 查看最后一次登录
lastlog | grep -v "Never"

# 使用 ausearch 查看审计
sudo ausearch -m USER_LOGIN --start today

10.9 安全加固脚本

#!/bin/bash
# ssh-hardening.sh - SSH 安全加固脚本

set -e

SSHD_CONFIG="/etc/ssh/sshd_config"
BACKUP="/etc/ssh/sshd_config.backup.$(date +%Y%m%d%H%M%S)"

echo "=== SSH 安全加固 ==="

# 备份原始配置
cp "$SSHD_CONFIG" "$BACKUP"
echo "✅ 已备份到 $BACKUP"

# 加固配置
cat >> "$SSHD_CONFIG" << 'EOF'

# ===== 安全加固 (ssh-hardening.sh) =====
Protocol 2
MaxAuthTries 3
LoginGraceTime 60
PermitEmptyPasswords no
X11Forwarding no
AllowTcpForwarding no
PermitTunnel no
GatewayPorts no
PermitUserEnvironment no
UseDNS no
GSSAPIAuthentication no
ClientAliveInterval 300
ClientAliveCountMax 2
MaxStartups 10:30:60
PrintMotd no
PrintLastLog yes
EOF

echo "✅ 已应用加固配置"

# 检查语法
if sshd -t; then
    echo "✅ 配置语法正确"
else
    echo "❌ 配置语法错误,正在恢复..."
    cp "$BACKUP" "$SSHD_CONFIG"
    exit 1
fi

# 重新加载
systemctl reload sshd
echo "✅ SSH 已重新加载"

echo ""
echo "⚠️  注意:请确认密钥认证可用后再禁用密码认证"
echo "   运行以下命令禁用密码认证:"
echo "   sudo sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' $SSHD_CONFIG"
echo "   sudo systemctl reload sshd"

扩展阅读


下一章: 第11章 跳板机与堡垒机 → 学习 ProxyJump、ProxyCommand 和堡垒机架构。