CentOS安全基线检查是每一位Linux运维工程师和安全管理员必须掌握的核心技能。所谓安全基线,就是一套经过行业验证的最小安全配置标准,涵盖账户管理、权限控制、网络防护、日志审计、文件完整性等多个维度。与其每次手动逐项排查,不如编写一套自动化扫描脚本,让机器替你跑检查、出报告、标风险等级。下面我直接把CentOS 7/8/Stream上最实用的安全基线检查项和完整的自动化脚本给你讲透。

一、CentOS安全基线的核心检查项

安全基线不是一句空话,它落实到具体的配置文件和系统参数上。以下是必须逐项核查的关键领域:

1. 账户与密码策略:检查/etc/login.defs中PASS_MAX_DAYS(密码最大有效期,建议90天)、PASS_MIN_DAYS(最小修改间隔,建议7天)、PASS_WARN_AGE(过期警告天数,建议7天)。确认/etc/passwd中是否存在UID为0的非root用户,是否有空密码账户。用命令awk -F: '($2==""){print $1}' /etc/shadow可以直接抓出空密码账户。

2. SSH安全配置:/etc/ssh/sshd_config中必须设置PermitRootLogin no、Protocol 2、MaxAuthTries 3、ClientAliveInterval 300、ClientAliveCountMax 2、IgnoreRhosts yes、HostbasedAuthentication no。禁用密码登录改用密钥认证是最佳实践。

3. 文件权限与完整性:关键文件如/etc/passwd、/etc/shadow、/etc/gshadow、/etc/group的权限必须是644或600,属主必须是root。用AIDE或Tripwire做文件完整性监控,定期比对哈希值。

4. 网络与防火墙:确认firewalld或iptables规则只开放必要端口,关闭不需要的服务。用systemctl list-unit-files --state=enabled查看所有开机自启服务,逐一评估是否必要。

5. 日志与审计:确保rsyslog正常运行,/etc/rsyslog.conf中远程日志接收配置合理。开启auditd服务,规则文件/etc/audit/rules.d/audit.rules中应包含对/etc/passwd、/etc/shadow等敏感文件的监控规则。

6. 内核参数加固:/etc/sysctl.conf中设置net.ipv4.ip_forward=0(除非做路由)、net.ipv4.conf.all.accept_redirects=0、net.ipv4.conf.all.send_redirects=0、net.ipv4.conf.all.accept_source_route=0、kernel.randomize_va_space=2等。

二、自动化扫描脚本完整实现

下面给出一个生产级的Bash脚本,可以在CentOS 7/8/Stream上直接运行。脚本会逐项检查、打分、输出HTML报告。

#!/bin/bash
# CentOS Security Baseline Check Script v2.0
# Author: Security Ops Team
# Usage: ./centos_baseline_check.sh

REPORT_FILE="/var/log/security_baseline_report_$(date +%Y%m%d_%H%M%S).html"
PASS=0
WARN=0
FAIL=0

html_header() {
    cat <<EOF > "$REPORT_FILE"
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>CentOS安全基线检查报告 - $(date '+%Y-%m-%d %H:%M:%S')</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
.container { max-width: 900px; margin: 0 auto; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
h1 { color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px; }
h2 { color: #34495e; margin-top: 30px; }
table { width: 100%; border-collapse: collapse; margin: 10px 0; }
th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
th { background: #3498db; color: #fff; }
.pass { background: #d4edda; color: #155724; }
.warn { background: #fff3cd; color: #856404; }
.fail { background: #f8d7da; color: #721c24; }
.summary { font-size: 18px; margin: 20px 0; padding: 15px; border-radius: 5px; }
</style>
</head>
<body>
<div class="container">
<h1>CentOS 安全基线检查报告</h1>
<p>主机名: $(hostname)<br>IP地址: $(hostname -I | awk '{print $1}')<br>系统版本: $(cat /etc/redhat-release 2>/dev/null || cat /etc/os-release | grep PRETTY_NAME | cut -d'"' -f2)<br>检查时间: $(date '+%Y-%m-%d %H:%M:%S')</p>
<div class="summary">
<strong>检查结果汇总:</strong> 通过 $PASS 项 | 警告 $WARN 项 | 失败 $FAIL 项
</div>
<table>
<tr><th>序号</th><th>检查项</th><th>状态</th><th>详情</th></tr>
EOF
}

html_footer() {
    cat <<EOF >> "$REPORT_FILE"
</table>
</div>
</body>
</html>
EOF
}

add_row() {
    local item="$1"
    local status="$2"
    local detail="$3"
    local cls=""
    case $status in
        PASS) cls="pass"; ((PASS++));;
        WARN) cls="warn"; ((WARN++));;
        FAIL) cls="fail"; ((FAIL++));;
    esac
    echo "<tr><td>$i</td><td>$item</td><td class=\"$cls\">$status</td><td>$detail</td></tr>" >> "$REPORT_FILE"
}

html_header
i=1

# 1. 检查空密码账户
empty_pw=$(awk -F: '($2==""){print $1}' /etc/shadow 2>/dev/null)
if [ -z "$empty_pw" ]; then
    add_row "空密码账户检查" "PASS" "未发现空密码账户"
else
    add_row "空密码账户检查" "FAIL" "发现空密码账户: $empty_pw"
fi
((i++))

# 2. 检查密码有效期策略
max_days=$(grep "^PASS_MAX_DAYS" /etc/login.defs | awk '{print $2}')
if [ -z "$max_days" ] || [ "$max_days" -le 90 ] 2>/dev/null; then
    add_row "密码最大有效期(≤90天)" "PASS" "PASS_MAX_DAYS=$max_days"
else
    add_row "密码最大有效期(≤90天)" "WARN" "PASS_MAX_DAYS=$max_days,建议设置为90"
fi
((i++))

# 3. 检查SSH Root登录
ssh_root=$(grep -i "^PermitRootLogin" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}')
if [ "$ssh_root" = "no" ]; then
    add_row "SSH禁止Root登录" "PASS" "PermitRootLogin=no"
elif [ -z "$ssh_root" ]; then
    add_row "SSH禁止Root登录" "WARN" "未配置,默认值为yes"
else
    add_row "SSH禁止Root登录" "FAIL" "PermitRootLogin=$ssh_root"
fi
((i++))

# 4. 检查SSH协议版本
ssh_proto=$(grep -i "^Protocol" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}')
if [ "$ssh_proto" = "2" ]; then
    add_row "SSH协议版本" "PASS" "Protocol=2"
else
    add_row "SSH协议版本" "FAIL" "Protocol=$ssh_proto,应为2"
fi
((i++))

# 5. 检查关键文件权限
check_perm() {
    local file=$1
    local expected=$2
    local actual=$(stat -c "%a" "$file" 2>/dev/null)
    if [ "$actual" = "$expected" ]; then
        add_row "$file 权限检查" "PASS" "权限为 $actual"
    else
        add_row "$file 权限检查" "FAIL" "当前权限 $actual,应为 $expected"
    fi
}
check_perm "/etc/passwd" "644"
((i++))
check_perm "/etc/shadow" "600"
((i++))
check_perm "/etc/group" "644"
((i++))
check_perm "/etc/gshadow" "600"
((i++))

# 6. 检查UID 0非root用户
uid0=$(awk -F: '($3==0 && $1!="root"){print $1}' /etc/passwd)
if [ -z "$uid0" ]; then
    add_row "UID=0非root用户检查" "PASS" "未发现异常"
else
    add_row "UID=0非root用户检查" "FAIL" "发现: $uid0"
fi
((i++))

# 7. 检查firewalld状态
if systemctl is-active --quiet firewalld 2>/dev/null; then
    add_row "防火墙服务状态" "PASS" "firewalld正在运行"
else
    add_row "防火墙服务状态" "WARN" "firewalld未运行"
fi
((i++))

# 8. 检查不必要的自启服务
unnecessary_services=("cups" "avahi-daemon" "bluetooth" "rpcbind" "rpc-statd" "nfs-utils")
for svc in "${unnecessary_services[@]}"; do
    if systemctl is-enabled --quiet "$svc" 2>/dev/null; then
        add_row "服务 $svc 自启检查" "WARN" "$svc 已设置开机自启,建议禁用"
    else
        add_row "服务 $svc 自启检查" "PASS" "$svc 未自启"
    fi
    ((i++))
done

# 9. 检查内核参数
sysctl_checks=("net.ipv4.ip_forward=0" "net.ipv4.conf.all.accept_redirects=0" "net.ipv4.conf.all.send_redirects=0" "kernel.randomize_va_space=2")
for param in "${sysctl_checks[@]}"; do
    key=$(echo "$param" | cut -d= -f1)
    expected=$(echo "$param" | cut -d= -f2)
    actual=$(sysctl -n "$key" 2>/dev/null)
    if [ "$actual" = "$expected" ]; then
        add_row "内核参数 $key" "PASS" "值为 $actual"
    else
        add_row "内核参数 $key" "FAIL" "当前值 $actual,应为 $expected"
    fi
    ((i++))
done

# 10. 检查auditd服务
if systemctl is-active --quiet auditd 2>/dev/null; then
    add_row "auditd审计服务" "PASS" "auditd正在运行"
else
    add_row "auditd审计服务" "FAIL" "auditd未运行,建议启用"
fi
((i++))

html_footer
echo "报告已生成: $REPORT_FILE"
echo "通过: $PASS | 警告: $WARN | 失败: $FAIL"
三、脚本使用方法与报告解读

将上面的脚本保存为/usr/local/bin/centos_baseline_check.sh,赋予执行权限:chmod +x /usr/local/bin/centos_baseline_check.sh,然后以root身份运行即可。脚本会在/var/log/目录下生成带时间戳的HTML报告文件,直接用浏览器打开就能看到红绿黄三色标注的检查结果。

报告中绿色PASS代表合规,黄色WARN代表需要关注但不致命,红色FAIL代表必须立即修复。对于FAIL项,建议在24小时内完成整改。特别是SSH Root登录、空密码账户、UID 0异常用户这三项,属于高危漏洞,一旦被利用后果严重。

四、定期执行与持续合规

安全基线检查不是一次性工作。建议通过crontab设置每周自动执行一次:0 3 * * 1 /usr/local/bin/centos_baseline_check.sh,每周一凌晨3点自动扫描。同时把报告文件通过邮件或内部工单系统推送给安全团队,形成闭环。

如果企业规模较大,可以结合Ansible批量部署这套脚本到所有CentOS节点,统一收集报告。也可以将脚本集成到CI/CD流程中,在新服务器上线前自动跑一遍基线检查,不合规不上线。

五、几个容易被忽视的加固细节

很多人做完上面的检查就觉得够了,但以下几点同样关键:第一,/tmp和/var/tmp目录要设置sticky位(chmod 1777),防止普通用户互相删除文件;第二,/etc/crontab和/var/spool/cron下的定时任务文件权限设为600;第三,关闭不需要的内核模块,用lsmod查看,用/etc/modprobe.d/禁用;第四,确保GRUB引导加载器设置了密码,防止单用户模式绕过认证;第五,定期更新系统补丁,yum update -y --security只打安全补丁,减少变更风险。

CentOS 7已于2024年6月停止维护,建议新项目迁移到Rocky Linux或AlmaLinux,它们是CentOS的社区替代发行版,完全兼容RHEL生态。安全基线的检查逻辑和脚本在这些发行版上同样适用,只需把/etc/redhat-release的读取方式稍作调整即可。

总结一句话:安全基线是底线不是上限。脚本帮你守住底线,持续监控和应急响应才是真正的安全能力。把这套东西跑起来,你的CentOS服务器安全水位至少提升一个档次。