前言
一台默认安装的 Linux 服务器,暴露的攻击面远超想象:多余的 RPM 包、开放的网络服务、宽松的 SSH 配置、未启用的审计系统。安全加固不是一次性操作,而是一套从安装到运行的全生命周期清单。本文按 CIS Benchmark 框架梳理加固要点,每一步都给出可直接执行的配置。
系统最小化安装原则
包管理裁剪
最小化安装的核心思路:只装需要的,不装可能需要的。
# 安装时选择 Minimal Install(RHEL 系)或 minimal image(Debian 系)
# 安装后审计已安装的包组,移除不需要的
yum grouplist # RHEL/CentOS
dnf group list # Fedora/RHEL 8+
# 移除不需要的包组
dnf group remove "GNOME Desktop" "Graphical Administration Tools"
# 审计已安装的包,按大小排序
rpm -qa --queryformat '%{SIZE} %{NAME}\n' | sort -rn | head -30
# 移除已知不需要的包
dnf remove -y ypbind rsh-server ypserv telnet-server talk-server
Debian/Ubuntu 系:
# 查看手动安装的包(可安全移除的候选)
apt-mark showmanual
# 移除不需要的服务包
apt purge -y rsh-client rsh-redone-server telnetd tftpd xinetd
服务裁剪
安装后审计运行中的服务,关闭一切非必要服务:
# 列出所有已启用的服务
systemctl list-unit-files --type=service --state=enabled
# 逐项审计并关闭不需要的服务
systemctl disable --now avahi-daemon cups bluetooth nfs-server 2>/dev/null
# 禁用不需要的服务的同时,确保关键服务不被意外关闭
# 常见可关闭: avahi-daemon cups bluetooth rpcbind nfs-server
# 谨慎处理: NetworkManager firewalld sshd
一个判断原则:如果不确定某个服务是否需要,用以下命令查看它的说明和依赖关系:
systemctl cat rpcbind.service # 查看服务定义
systemctl list-dependencies rpcbind # 查看谁依赖它
SSH 加固完整配置
SSH 是 Linux 服务器最主要的远程入口,也是攻击者首要目标。以下是生产环境推荐的加固配置:
# /etc/ssh/sshd_config
Port 22022 # 修改默认端口(减少自动化扫描噪声)
# 协议与加密
Protocol 2
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
# 认证
PermitRootLogin no # 禁止 root 直接登录
PasswordAuthentication no # 禁用密码认证(仅密钥)
PubkeyAuthentication yes
PermitEmptyPasswords no
MaxAuthTries 3 # 最大认证尝试次数
LoginGraceTime 30 # 认证超时 30 秒
# 访问控制
AllowGroups sshusers # 仅允许 sshusers 组成员登录
# 会话管理
ClientAliveInterval 300 # 5 分钟无操作发送心跳
ClientAliveCountMax 2 # 2 次无响应则断开
X11Forwarding no # 禁用 X11 转发
AllowTcpForwarding no # 禁用 TCP 转发(按需开放)
AllowAgentForwarding no # 禁用 Agent 转发
# 日志
LogLevel VERBOSE # 记录详细日志(含密钥指纹)
创建 SSH 用户组并配置密钥:
# 创建专用 SSH 用户组
groupadd sshusers
# 将运维用户加入组
usermod -aG sshusers ops_user
# 配置密钥认证
mkdir -p /home/ops_user/.ssh
chmod 700 /home/ops_user/.ssh
# 将公钥写入
echo "ssh-ed25519 AAAAC3Nza... user@workstation" > /home/ops_user/.ssh/authorized_keys
chmod 600 /home/ops_user/.ssh/authorized_keys
chown -R ops_user:ops_user /home/ops_user/.ssh
# 重启 sshd 前务必验证配置语法
sshd -t && systemctl restart sshd
Fail2ban 暴力破解防护
即使修改了端口,仍可能被针对性扫描。Fail2ban 通过监控日志自动封禁恶意 IP:
# 安装
dnf install -y fail2ban # RHEL
apt install -y fail2ban # Debian/Ubuntu
# /etc/fail2ban/jail.local
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
bantime.increment = true
bantime.factor = 2
banaction = firewallcmd-ipset
[sshd]
enabled = true
port = 22022
filter = sshd
logpath = %(sshd_log)s
backend = systemd
systemctl enable --now fail2ban
# 查看封禁状态
fail2ban-client status sshd
重要:修改 SSH 配置后,不要关闭当前终端,另开一个终端验证新配置能正常登录后再退出旧会话,避免被自己锁在外面。
SELinux/AppArmor 强制访问控制
SELinux(RHEL/CentOS/Fedora)
SELinux 通过类型标签(Type Enforcement)对进程、文件、端口进行细粒度访问控制。生产环境应保持 Enforcing 模式,而非简单关闭。
# 查看当前模式
getenforce
# Enforcing / Permissive / Disabled
# 查看详细状态
sestatus
# 临时切换模式(重启失效)
setenforce 0 # Permissive(仅记录,不拦截)
setenforce 1 # Enforcing(实际拦截)
# 永久设置
sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config
当服务因 SELinux 无法正常工作时,正确的排查方式是分析审计日志而非关闭 SELinux:
# 查看最近的 AVC 拒绝日志
ausearch -m AVC -ts recent
# 更友好的分析工具
yum install -y setroubleshoot-server
sealert -a /var/log/audit/audit.log
# 如果确认是策略缺失,生成并应用自定义策略模块
ausearch -m AVC -ts today | audit2allow -M mycustompol
semodule -i mycustompol.pp
常见 SELinux 上下文调整:
# 将非标准端口分配给 HTTP 服务
semanage port -a -t http_port_t -p tcp 8080
# 修改文件的 SELinux 上下文
semanage fcontext -a -t httpd_sys_content_t "/data/web(/.*)?"
restorecon -Rv /data/web
AppArmor(Ubuntu/Debian)
AppArmor 基于路径的访问控制,配置比 SELinux 更直观:
# 查看状态
apparmor_status
# 查看某进程的 AppArmor profile
aa-status | grep <process_name>
# 列出所有已加载的 profile
ls /etc/apparmor.d/
# 将 profile 切换为 complain 模式(仅记录,不拦截)
aa-complain /etc/apparmor.d/usr.sbin.mysqld
# 切换回 enforce 模式
aa-enforce /etc/apparmor.d/usr.sbin.mysqld
auditd 审计系统配置
auditd 是 Linux 内核审计框架的用户态组件,记录系统调用和关键文件访问,是合规审计的核心工具。
安装与配置
# 安装
dnf install -y audit # RHEL
apt install -y auditd # Debian/Ubuntu
systemctl enable --now auditd
# /etc/audit/auditd.conf 关键参数
log_file = /var/log/audit/audit.log
log_format = ENRISHED
# 日志轮转:达到 8MB 后轮转,保留 5 份
max_log_file = 8
num_logs = 5
max_log_file_action = ROTATE
# 磁盘满时的行为
disk_full_action = HALT # 磁盘满则暂停系统(确保审计不丢失,合规要求)
审计规则配置
# /etc/audit/rules.d/audit.rules
# 1. 监控登录相关文件篡改
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/gshadow -p wa -k identity
-w /etc/sudoers -p wa -k identity
# 2. 监控 SSH 配置变更
-w /etc/ssh/sshd_config -p wa -k ssh_config
# 3. 监控 sudo 使用
-w /var/log/sudo.log -p wa -k sudo_usage
# 4. 监控系统时间修改
-a always,exit -F arch=b64 -S adjtimex,settimeofday,clock_settime -k time-change
# 5. 监控网络环境变更
-w /etc/hosts -p wa -k system_config
-w /etc/sysconfig/network -p wa -k system_config
# 6. 监控用户/组命令执行
-a always,exit -F arch=b64 -S execve -F uid>=1000 -k user_commands
# 7. 监控内核模块加载
-w /sbin/insmod -p x -k modules
-w /sbin/rmmod -p x -k modules
-w /sbin/modprobe -p x -k modules
# 重新加载规则
augenrules --load
# 验证规则已生效
auditctl -l
日志分析
# 查看最近 10 分钟的所有审计事件
ausearch -ts recent
# 按 key 查询
ausearch -k identity
# 查看某用户的所有操作
ausearch -ui 1000
# 生成可读报告
aureport --summary
aureport -k # 按 key 汇总
aureport -f # 按文件汇总
防火墙规则管理
firewalld(RHEL/CentOS/Fedora)
# 查看当前区域和规则
firewall-cmd --get-default-zone
firewall-cmd --list-all
# 设置默认区域为 public
firewall-cmd --set-default-zone=public
# 放行业务端口(永久生效)
firewall-cmd --permanent --add-port=22022/tcp # SSH
firewall-cmd --permanent --add-port=443/tcp # HTTPS
firewall-cmd --permanent --add-port=8080-8090/tcp # 端口段
# 限制 SSH 来源 IP(仅允许运维网段)
firewall-cmd --permanent --new-zone=ssh-restricted
firewall-cmd --permanent --zone=ssh-restricted --add-source=10.0.1.0/24
firewall-cmd --permanent --zone=ssh-restricted --add-port=22022/tcp
firewall-cmd --permanent --zone=public --remove-port=22022/tcp
# 重新加载
firewall-cmd --reload
# 验证
firewall-cmd --list-all --zone=ssh-restricted
nftables(Debian/Ubuntu/现代 Linux)
nftables 是 iptables 的继任者,配置语法更统一:
# /etc/nftables.conf
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# 允许回环
iif "lo" accept
# 允许已建立连接的回包
ct state established,related accept
# 丢弃无效连接
ct state invalid drop
# ICMP 放行(限制速率防 flood)
icmp type echo-request limit rate 5/second accept
ip6 nexthdr icmpv6 accept
# SSH 限速:每 IP 每分钟最多 5 次新连接
tcp dport 22022 ct state new limit rate 5/minute accept
# HTTPS
tcp dport { 80, 443 } accept
# 默认拒绝记录日志
limit rate 10/minute log prefix "nft-drop: " drop
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
# 应用配置
nft -f /etc/nftables.conf
systemctl enable nftables
# 查看当前规则集
nft list ruleset
# 查看规则匹配计数
nft list ruleset -a
安全基线检查脚本
以下脚本整合上述检查项,可作为定期巡检工具。执行只读检查,不修改任何系统配置:
#!/bin/bash
# security_baseline_check.sh - Linux 安全基线检查脚本
# 用法: sudo bash security_baseline_check.sh
# 参考: CIS Benchmark
set -euo pipefail
PASS=0
FAIL=0
WARN=0
REPORT="/tmp/security_baseline_$(date +%Y%m%d_%H%M%S).txt"
check() {
local desc="$1"
local result="$2"
local detail="${3:-}"
if [ "$result" = "PASS" ]; then
echo "[PASS] $desc" | tee -a "$REPORT"
PASS=$((PASS + 1))
elif [ "$result" = "FAIL" ]; then
echo "[FAIL] $desc - $detail" | tee -a "$REPORT"
FAIL=$((FAIL + 1))
else
echo "[WARN] $desc - $detail" | tee -a "$REPORT"
WARN=$((WARN + 1))
fi
}
echo "===============================" | tee "$REPORT"
echo "Linux 安全基线检查报告" | tee -a "$REPORT"
echo "主机: $(hostname)" | tee -a "$REPORT"
echo "时间: $(date)" | tee -a "$REPORT"
echo "===============================" | tee -a "$REPORT"
# --- 1. 系统更新 ---
if dnf check-update >/dev/null 2>&1 || apt list --upgradable 2>/dev/null | grep -q upgradable; then
check "系统补丁是否最新" "WARN" "有待更新的包"
else
check "系统补丁是否最新" "PASS"
fi
# --- 2. SSH 加固 ---
SSHD_CONFIG="/etc/ssh/sshd_config"
if grep -q "^PermitRootLogin.*no" "$SSHD_CONFIG"; then
check "SSH 禁止 root 登录" "PASS"
else
check "SSH 禁止 root 登录" "FAIL" "未配置 PermitRootLogin no"
fi
if grep -q "^PasswordAuthentication.*no" "$SSHD_CONFIG"; then
check "SSH 禁用密码认证" "PASS"
else
check "SSH 禁用密码认证" "FAIL" "未配置 PasswordAuthentication no"
fi
if grep -q "^Protocol.*2" "$SSHD_CONFIG" 2>/dev/null || true; then
check "SSH 协议版本" "PASS"
else
# OpenSSH 7.0+ 默认仅支持 Protocol 2,此项可跳过
check "SSH 协议版本" "WARN" "未显式配置(7.0+ 默认 Protocol 2)"
fi
if grep -q "^MaxAuthTries" "$SSHD_CONFIG"; then
max_tries=$(grep "^MaxAuthTries" "$SSHD_CONFIG" | awk '{print $2}')
if [ "$max_tries" -le 4 ]; then
check "SSH MaxAuthTries ($max_tries)" "PASS"
else
check "SSH MaxAuthTries ($max_tries)" " " "值过大,建议 <=4"
fi
else
check "SSH MaxAuthTries" "WARN" "未配置(默认6)"
fi
# --- 3. 账户安全 ---
# 检查空密码账户
EMPTY_PW=$(awk -F: '($2 == "" || $2 == "!") {print $1}' /etc/shadow 2>/dev/null | wc -l)
if [ "$EMPTY_PW" -eq 0 ]; then
check "无空密码账户" "PASS"
else
check "无空密码账户" "FAIL" "发现 $EMPTY_PW 个空密码账户"
fi
# 检查 UID=0 的账户(应只有 root)
ROOT_UID=$(awk -F: '($3 == 0) {print $1}' /etc/passwd)
if [ "$ROOT_UID" = "root" ]; then
check "UID=0 仅 root" "PASS"
else
check "UID=0 仅 root" "FAIL" "存在多个 UID=0 账户: $ROOT_UID"
fi
# 密码策略
if [ -f /etc/security/pwquality.conf ]; then
MINLEN=$(grep "^minlen" /etc/security/pwquality.conf 2>/dev/null | awk '{print $2}' || echo 0)
if [ "${MINLEN:-0}" -ge 12 ]; then
check "密码最小长度 ($MINLEN)" "PASS"
else
check "密码最小长度 ($MINLEN)" "WARN" "建议 >=12"
fi
fi
# --- 4. 防火墙 ---
if systemctl is-active --quiet firewalld 2>/dev/null; then
check "firewalld 运行中" "PASS"
elif systemctl is-active --quiet nftables 2>/dev/null; then
check "nftables 运行中" "PASS"
elif systemctl is-active --quiet ufw 2>/dev/null; then
check "ufw 运行中" "PASS"
else
check "防火墙运行状态" "FAIL" "未检测到运行中的防火墙"
fi
# --- 5. SELinux/AppArmor ---
if command -v getenforce >/dev/null 2>&1; then
SELINUX_MODE=$(getenforce)
if [ "$SELINUX_MODE" = "Enforcing" ]; then
check "SELinux 模式 (Enforcing)" "PASS"
elif [ "$SELINUX_MODE" = "Permissive" ]; then
check "SELinux 模式 (Permissive)" "WARN" "建议切换为 Enforcing"
else
check "SELinux 模式 (Disabled)" "FAIL" "SELinux 已禁用"
fi
elif command -v apparmor_status >/dev/null 2>&1; then
AA_PROFILES=$(apparmor_status 2>/dev/null | grep "profiles are loaded" | awk '{print $1}')
if [ "${AA_PROFILES:-0}" -gt 0 ]; then
check "AppArmor profiles ($AA_PROFILES)" "PASS"
else
check "AppArmor profiles" "FAIL" "无已加载 profile"
fi
fi
# --- 6. auditd ---
if systemctl is-active --quiet auditd 2>/dev/null; then
check "auditd 运行中" "PASS"
else
check "auditd 运行状态" "WARN" "auditd 未运行"
fi
# --- 7. 危险服务 ---
DANGER_SERVICES="telnet rsh rlogin tftp xinetd ypbind"
for svc in $DANGER_SERVICES; do
if systemctl is-enabled "$svc" 2>/dev/null | grep -q enabled; then
check "危险服务 $svc 未启用" "FAIL" "$svc 已启用"
fi
done
check "危险服务检查完成" "PASS"
# --- 8. 内核参数加固 ---
SYNCOOKIES=$(sysctl -n net.ipv4.tcp_syncookies 2>/dev/null || echo 0)
if [ "$SYNCOOKIES" = "1" ]; then
check "TCP SYN Cookies 已启用" "PASS"
else
check "TCP SYN Cookies 已启用" "WARN" "建议启用防 SYN Flood"
fi
IP_FORWARD=$(sysctl -n net.ipv4.ip_forward 2>/dev/null || echo 0)
if [ "$IP_FORWARD" = "0" ]; then
check "IP 转发已关闭" "PASS"
else
check "IP 转发已关闭" "WARN" "非路由器建议关闭 ip_forward"
fi
# --- 9. 文件权限 ---
# 检查 /etc/passwd 权限
PASSWD_PERM=$(stat -c %a /etc/passwd)
if [ "$PASSWD_PERM" = "644" ]; then
check "/etc/passwd 权限 (644)" "PASS"
else
check "/etc/passwd 权限 ($PASSWD_PERM)" "WARN" "标准为 644"
fi
# 检查 /etc/shadow 权限
SHADOW_PERM=$(stat -c %a /etc/shadow)
if [ "$SHADOW_PERM" = "000" ] || [ "$SHADOW_PERM" = "640" ]; then
check "/etc/shadow 权限 ($SHADOW_PERM)" "PASS"
else
check "/etc/shadow 权限 ($SHADOW_PERM)" "WARN" "建议 000 或 640"
fi
# --- 汇总 ---
echo "===============================" | tee -a "$REPORT"
echo "汇总: PASS=$PASS FAIL=$FAIL WARN=$WARN" | tee -a "$REPORT"
echo "详细报告已保存: $REPORT" | tee -a "$REPORT"
if [ "$FAIL" -gt 0 ]; then
exit 1
fi
部署使用:
# 赋予执行权限
chmod +x security_baseline_check.sh
# 执行检查(建议以 root 运行)
sudo ./security_baseline_check.sh
# 配置定时检查(每周一早上 8 点)
# crontab -e
0 8 * * 1 /opt/scripts/security_baseline_check.sh >> /var/log/security_baseline.log 2>&1
加固清单总结
| 类别 | 关键措施 | 优先级 |
|---|---|---|
| 系统最小化 | 最小安装、裁剪服务、移除危险包 | 高 |
| SSH 加固 | 密钥认证、改端口、禁 root、Fail2ban | 高 |
| 强制访问控制 | SELinux Enforcing / AppArmor | 高 |
| 审计系统 | auditd 配置规则、定期分析 | 中 |
| 防火墙 | 默认拒绝、最小端口放行 | 高 |
| 密码策略 | 最小长度 12、复杂度、定期检查 | 中 |
| 内核加固 | SYN Cookies、禁 IP 转发、核心参数 | 中 |
| 定期巡检 | 基线脚本定时运行、漏洞修复 | 中 |
安全加固不是一劳永逸的工作。建议将基线检查脚本纳入 CI/CD 或定时任务,每次系统变更后自动执行,确保安全状态持续符合预期。