这是一个用于 Linux 服务器的自动化安全合规检测脚本,遵循中国《GB/T 22239-2019 信息安全技术 网络安全等级保护基本要求》(等保三级)标准。脚本采用纯检测模式,不会修改系统,生成详细的 HTML 报告,覆盖身份鉴别、访问控制、安全审计、入侵防范和网络安全等关键领域。
功能特性
- 全面检测 :覆盖 18 项合规检查,包括密码策略、SSH 配置、文件权限、审计日志、高危端口等。
- 跨发行版支持 :兼容 Ubuntu 22.04、CentOS 7+、Kylin 10、openEuler 22.04、BigCloud 22.10 等 Linux 发行版。
- HTML 报告 :生成美观的 HTML 报告,包含通过/警告/失败状态、合规率和修复建议。
- 纯检测模式 :不修改系统配置,适用于生产环境。
- 加权合规率 :根据检查项重要性计算加权合规率。
前置条件
- 权限要求 :脚本需以
root或sudo权限运行,以访问系统文件和配置。 - 依赖工具 :
bash(建议版本 4.0+)coreutils(提供stat、grep、awk等)net-tools或iproute2(提供netstat或ss)auditd(用于审计服务检查)- 可选:
bc(精确合规率计算)、rpm或debsums(文件完整性检查)、aide或tripwire(高级完整性检查) - 支持系统 :Ubuntu、CentOS、Kylin、openEuler、BigCloud 等标准配置的 Linux 系统。
安装步骤
- 克隆仓库:
git clone https://github.com/llody55/Linux-Security-Compliance-Check.git cd Linux-Security-Compliance-Check
- 确保脚本可执行:
chmod +x security_check.sh
- 安装可选依赖(若缺失):
- Ubuntu/Debian 系统:
sudo apt update sudo apt install bc net-tools auditd aide
- CentOS/RHEL 系统:
sudo yum install bc net-tools auditd aide
- Ubuntu/Debian 系统:
使用方法
以 root 或 sudo 权限运行脚本:
sudo ./security_check.sh
- 脚本将执行合规检测并生成 HTML 报告,保存至
/opt/security_check/<IP>_<YYYYMMDD>.html。 - 日志保存至
/var/log/security_check.log。
示例输出
生成的 HTML 报告包含:
- 概览 :系统信息(操作系统、IP、架构)、总体合规率。
- 详细检查 :按身份鉴别、访问控制、安全审计、入侵防范、网络安全分类,显示通过/警告/失败状态。
- 修复建议 :针对不合规项提供可执行的修复命令。
- 进度条 :直观展示各分类的合规率。
示例报告见 report_example.html。
示例报告
参考report_example.html 实际输出文件
支持的检查项
- 身份鉴别 :
- 密码策略(最大有效期 ≤ 90 天,最小长度 ≥ 12 等)
- 密码复杂度(至少 3 类字符,含数字、大写字母等)
- 账户锁定策略(失败次数 ≤ 5,锁定时间 ≥ 600 秒)
- 三权账户(sysadmin、auditadmin、securityadmin)
- 口令加密(SHA512)
- 默认账户检查
- 不活跃账户检测(从未登录或最近登录 > 90 天)
- 访问控制 :
- 文件权限(/etc/passwd、/etc/shadow 等)
- SSH 配置(禁用 Root 登录、会话超时 ≤ 300 秒等)
- 远程访问限制(/etc/hosts.allow、/etc/hosts.deny)
- 文件系统挂载选项(noexec、nosuid、nodev)
- 安全审计 :
- 审计服务状态(auditd)
- 日志保留(≥ 180 天)
- 命令历史时间戳
- 审计规则(监控 /etc/passwd、/etc/shadow 等)
- 入侵防范 :
- 文件完整性(使用 rpm、debsums 或 mtime,附最后修改时间)
- 系统补丁状态
- 网络安全 :
- 高危端口(FTP:21、Telnet:23、RDP:3389 等)
- 防火墙状态(iptables、nftables、ufw、firewalld)
贡献指南
欢迎为项目贡献代码!请按以下步骤操作:
- Fork 本仓库。
- 创建新分支(
git checkout -b feature/your-feature)。 - 提交更改(
git commit -m "添加新功能")。 - 推送分支(
git push origin feature/your-feature)。 - 提交 Pull Request。
请确保代码遵循 Bash 最佳实践,并为新功能添加测试。
常见问题
- 问:为何脚本需要 root 权限?
- 答:需要访问敏感文件(如 /etc/shadow)并执行特定命令(如 rpm -Vf)。
- 问:如果缺少 bc 命令怎么办?
- 答:脚本会回退使用 awk 进行计算,但建议安装 bc 以提高精度。
- 问:可以在非支持的发行版上运行吗?
- 答:脚本设计具有兼容性,但在非支持系统上可能需调整。请提交 Issue 获取支持。
#!/bin/bash
# 等保三级合规检测脚本
# 作者:llody
# 邮箱:745719408@qq.com
# 兼容:Ubuntu 22.04, CentOS 7+, Kylin 10, openEuler 22.04, BigCloud 22.10
# 功能:纯检测模式,生成HTML报告
# 版本:v3.2
# 使用:sudo ./security_check.sh
# 输出:/opt/security_check/<IP>_<YYYYMMDD>.html
# 日志:/var/log/security_check.log
set -euo pipefail
export PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
export LANG=en_US.UTF-8
declare -A CHECK_RESULTS
declare -A CHECK_DETAILS
declare -A CHECK_WEIGHTS=(
["密码策略"]=0.3
["SSH配置"]=0.2
["用户权限控制"]=0.2
["审计服务状态"]=0.15
["日志配置"]=0.15
["密码复杂度策略"]=0.1
["账户登录失败锁定策略"]=0.1
["远程管理地址限制检测"]=0.1
["三权账户检测"]=0.1
["审计日志留存检测"]=0.1
["文件完整性检查"]=0.1
["账户文件检查"]=0.1
["网络端口检查"]=0.1
["口令加密算法"]=0.1
["默认账户检查"]=0.1
["文件系统挂载选项"]=0.1
["审计规则检查"]=0.1
["系统补丁检查"]=0.1
)
# 日志记录
LOG_FILE="/var/log/security_check.log"
exec 1>>"$LOG_FILE" 2>&1
echo "===== 合规检测开始: $(date +'%Y-%m-%d %H:%M:%S') ====="
if [ $EUID -ne 0 ]; then
echo "此脚本需要 root 权限运行,请使用 sudo 或切换到 root 用户" >&2
exit 1
fi
detect_os() {
if [ -f /etc/os-release ]; then
. /etc/os-release
OS_NAME=$ID
OS_VERSION=$VERSION_ID
PRETTY_NAME=$PRETTY_NAME
elif [ -f /etc/centos-release ]; then
OS_NAME=centos
OS_VERSION=$(sed -E 's/.* ([0-9])\..*/\1/' /etc/centos-release)
else
OS_NAME=unknown
OS_VERSION=unknown
PRETTY_NAME=unknown
fi
}
detect_architecture() {
ARCH=$(uname -m)
[ -z "$ARCH" ] && ARCH="未检测到系统架构信息"
}
detect_ip() {
IP_ADDR=$(/sbin/ip route get 1 | /usr/bin/awk '{print $7}' | /usr/bin/head -1)
[ -z "$IP_ADDR" ] && IP_ADDR="未检测到IP地址"
}
detect_ip
detect_architecture
REPORT_DIR="/opt/security_check"
REPORT_FILE="${REPORT_DIR}/${IP_ADDR}_$(date +%Y%m%d).html"
if [ ! -d "$REPORT_DIR" ]; then
mkdir -p "$REPORT_DIR"
if [ $? -ne 0 ]; then
echo "无法创建目录 $REPORT_DIR,请检查权限" >&2
exit 1
fi
fi
init_report() {
cat > "$REPORT_FILE" <<EOF
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>服务器合规检测报告</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body { font-family: 'Microsoft YaHei', sans-serif; margin: 2em; }
h1 { color: #2c3e50; border-bottom: 3px solid #3498db; }
h2 { color: #34495e; }
nav { margin-bottom: 1em; }
nav a { margin-right: 1em; color: #3498db; text-decoration: none; }
nav a:hover { text-decoration: underline; }
.check-item { margin: 1em 0; padding: 1em; border-radius: 5px; }
.pass { background: #e8f5e9; border-left: 5px solid #4CAF50; }
.warning { background: #fff3e0; border-left: 5px solid #FFC107; }
.fail { background: #ffebee; border-left: 5px solid #F44336; }
.status-tag { padding: 2px 8px; border-radius: 3px; font-weight: bold; }
.pass-tag { background: #4CAF50; color: white; }
.warning-tag { background: #FFC107; color: black; }
.fail-tag { background: #F44336; color: white; }
.details { margin-left: 20px; color: #666; }
table { width: 100%; border-collapse: collapse; margin-top: 1em; }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
canvas { max-width: 400px; margin: 1em 0; }
.progress { background-color: #f3f3f3; border-radius: 5px; height: 20px; margin-bottom: 10px; }
.progress-bar { background-color: #4CAF50; height: 100%; border-radius: 5px; text-align: center; color: white; line-height: 20px; }
</style>
</head>
<body>
<h1>${IP_ADDR}-合规检测报告</h1>
<p>生成时间:$(date +'%Y-%m-%d %H:%M:%S')</p>
<p>操作系统:${PRETTY_NAME}</p>
<p>系统架构:${ARCH}</p>
<p>IP地址:${IP_ADDR}</p>
<nav>
<a href="#identity">身份鉴别</a> |
<a href="#access">访问控制</a> |
<a href="#audit">安全审计</a> |
<a href="#intrusion">入侵防范</a> |
<a href="#network">网络安全</a>
</nav>
<h2 id="identity">身份鉴别</h2>
<table>
<tr><th>检查项</th><th>状态</th><th>详情</th></tr>
EOF
}
check_pass() {
CHECK_RESULTS["$1"]="PASS"
CHECK_DETAILS["$1"]="$2"
echo "<tr><td>$1</td><td><span class='status-tag pass-tag'>通过</span></td><td class='details'>$2</td></tr>" >> "$REPORT_FILE"
}
check_warning() {
CHECK_RESULTS["$1"]="WARNING"
CHECK_DETAILS["$1"]="$2"
echo "<tr><td>$1</td><td><span class='status-tag warning-tag'>警告</span></td><td class='details'>$2</td></tr>" >> "$REPORT_FILE"
}
check_fail() {
CHECK_RESULTS["$1"]="FAIL"
CHECK_DETAILS["$1"]="$2"
echo "<tr><td>$1</td><td><span class='status-tag fail-tag'>失败</span></td><td class='details'>$2</td></tr>" >> "$REPORT_FILE"
}
# 密码策略检测(等保三级:身份鉴别,密码策略严格)
check_password_policy() {
local issue_count=0
local pass_max_days pass_min_days pass_warn_age pass_min_len
if [ -r /etc/login.defs ]; then
pass_max_days=$(grep -E '^PASS_MAX_DAYS' /etc/login.defs | awk '{print $2}' || echo "未配置")
pass_min_days=$(grep -E '^PASS_MIN_DAYS' /etc/login.defs | awk '{print $2}' || echo "未配置")
pass_warn_age=$(grep -E '^PASS_WARN_AGE' /etc/login.defs | awk '{print $2}' || echo "未配置")
pass_min_len=$(grep -E '^PASS_MIN_LEN' /etc/login.defs | awk '{print $2}' || echo "未配置")
# 小项:密码最大有效期≤30天(三级建议更严格)
if [[ "$pass_max_days" =~ ^[0-9]+$ ]] && [ "$pass_max_days" -le 90 ]; then
check_pass "密码最大有效期" "密码最大有效期${pass_max_days}天(符合要求)"
else
check_fail "密码最大有效期" "密码最大有效期${pass_max_days}天(建议≤90天)"
((issue_count++))
fi
# 小项:密码最小修改间隔≥7天
if [[ "$pass_min_days" =~ ^[0-9]+$ ]] && [ "$pass_min_days" -ge 7 ]; then
check_pass "密码最小修改间隔" "密码最小修改间隔${pass_min_days}天(符合要求)"
else
check_fail "密码最小修改间隔" "密码最小修改间隔${pass_min_days}天(建议≥7天)"
((issue_count++))
fi
# 小项:密码过期提醒≤7天
if [[ "$pass_warn_age" =~ ^[0-9]+$ ]] && [ "$pass_warn_age" -le 7 ]; then
check_pass "密码过期提醒" "密码过期提醒${pass_warn_age}天(符合要求)"
else
check_fail "密码过期提醒" "密码过期提醒${pass_warn_age}天(建议≤7天)"
((issue_count++))
fi
# 小项:密码长度≥12(三级建议更严格)
if [[ "$pass_min_len" =~ ^[0-9]+$ ]] && [ "$pass_min_len" -ge 12 ]; then
check_pass "密码最小长度" "密码长度${pass_min_len}(符合要求)"
else
check_fail "密码最小长度" "密码长度${pass_min_len}(建议≥12)"
((issue_count++))
fi
else
check_fail "密码策略文件" "无法读取 /etc/login.defs(可能无权限或文件不存在)"
((issue_count++))
fi
# 大项总结
if [ $issue_count -eq 0 ]; then
check_pass "密码策略" "密码策略符合等保三级要求"
else
check_warning "密码策略" "密码策略存在${issue_count}个问题"
fi
}
# SSH 安全检测(等保三级:访问控制,SSH 配置安全)
check_ssh_security() {
local sshd_file="/etc/ssh/sshd_config"
local issue_count=0
if [ -r "$sshd_file" ]; then
# 小项:检查协议版本
if grep -qE "^Protocol\s+1" "$sshd_file"; then
check_fail "SSH协议版本" "检测到不安全的SSHv1协议"
((issue_count++))
else
check_pass "SSH协议版本" "SSH协议版本(符合要求)"
fi
# 小项:Root 登录
if grep -qE "^PermitRootLogin\s+no" "$sshd_file"; then
check_pass "SSH Root登录" "不允许Root用户登录"
else
check_warning "SSH Root登录" "允许Root用户登录(建议设置为no)"
((issue_count++))
fi
# 小项:会话超时
local client_alive_interval=$(grep -E "^ClientAliveInterval" "$sshd_file" | awk '{print $2}' || echo "未配置")
local client_alive_count=$(grep -E "^ClientAliveCountMax" "$sshd_file" | awk '{print $2}' || echo "未配置")
if [[ "$client_alive_interval" =~ ^[0-9]+$ ]] && [ "$client_alive_interval" -le 300 ] && \
[[ "$client_alive_count" =~ ^[0-9]+$ ]] && [ "$client_alive_count" -ge 3 ]; then
check_pass "SSH会话超时" "会话超时配置合理(Interval: ${client_alive_interval}s, Count: ${client_alive_count})"
else
check_warning "SSH会话超时" "未配置合理超时(Interval: ${client_alive_interval}, Count: ${client_alive_count}, 建议 Interval≤300, Count≥3)"
((issue_count++))
fi
# 小项:空密码登录
if grep -qE "^PermitEmptyPasswords\s+yes" "$sshd_file"; then
check_fail "SSH空密码登录" "允许空密码登录"
((issue_count++))
else
check_pass "SSH空密码登录" "不允许空密码登录"
fi
# 小项:最大认证尝试次数
local max_auth_tries=$(grep -E "^MaxAuthTries" "$sshd_file" | awk '{print $2}' || echo "未配置")
if [[ "$max_auth_tries" =~ ^[0-9]+$ ]] && [ "$max_auth_tries" -le 4 ]; then
check_pass "SSH最大认证尝试" "最大认证尝试次数${max_auth_tries}(符合要求)"
else
check_warning "SSH最大认证尝试" "最大认证尝试次数${max_auth_tries}(建议≤4,PAM优先级可能更高)" # 改为 warning
fi
else
check_fail "SSH配置文件" "无法读取 $sshd_file(可能无权限或文件不存在)"
((issue_count++))
fi
# 检查/etc/profile中的TMOUT设置(Shell层面会话超时)
if [ -r "$profile_file" ]; then
local tmout_value=$(grep -E "^[[:space:]]*export[[:space:]]+TMOUT=" "$profile_file" | tail -1 | awk -F= '{print $2}' || echo "未配置")
if [[ "$tmout_value" =~ ^[0-9]+$ ]] && [ "$tmout_value" -le 300 ]; then
check_pass "Shell会话超时" "TMOUT=${tmout_value}(符合等保要求≤300秒)"
else
check_warning "Shell会话超时" "TMOUT=${tmout_value}(建议在/etc/profile中设置export TMOUT=300)"
((issue_count++))
fi
else
check_warning "Shell会话超时" "无法读取 $profile_file,无法检查TMOUT设置"
((issue_count++))
fi
# 大项总结
if [ $issue_count -eq 0 ]; then
check_pass "SSH配置" "SSH配置符合等保三级要求"
else
check_warning "SSH配置" "SSH配置存在${issue_count}个问题"
fi
}
# 用户权限检测
check_user_permissions() {
local issue_count=0
local sensitive_files=(
"/etc/passwd:644:root:root"
"/etc/shadow:640:root:shadow"
"/etc/group:644:root:root"
"/etc/sudoers:440:root:root"
)
for entry in "${sensitive_files[@]}"; do
local file=$(echo "$entry" | cut -d':' -f1)
local max_perm=$(echo "$entry" | cut -d':' -f2)
local expected_owner=$(echo "$entry" | cut -d':' -f3)
local expected_group=$(echo "$entry" | cut -d':' -f4)
if [ ! -f "$file" ]; then
check_fail "文件存在性-$file" "文件不存在"
((issue_count++))
continue
fi
local perm=$(stat -c "%a" "$file" 2>/dev/null || echo "unknown")
local owner=$(stat -c "%U" "$file" 2>/dev/null || echo "unknown")
local group=$(stat -c "%G" "$file" 2>/dev/null || echo "unknown")
if [ "$perm" = "unknown" ]; then
check_fail "文件权限-$file" "无法获取文件权限(可能无访问权限)"
((issue_count++))
continue
fi
if [ "$perm" -le "$max_perm" ]; then
check_pass "文件权限-$file" "权限(符合要求)(当前:${perm})"
else
check_fail "文件权限-$file" "权限过高(当前:${perm},建议:${max_perm})"
((issue_count++))
fi
# 特殊处理 /etc/shadow 文件的组所有权检查
if [ "$file" = "/etc/shadow" ]; then
# 对于 shadow 文件,允许 root:root 或 root:shadow
if [ "$owner" = "$expected_owner" ] && ( [ "$group" = "root" ] || [ "$group" = "shadow" ] ); then
check_pass "文件所有者-$file" "所有者(符合要求)(当前:${owner}:${group})"
else
check_warning "文件所有者-$file" "所有者或组不正确(当前:${owner}:${group},建议:${expected_owner}:root 或 shadow)"
((issue_count++))
fi
else
# 其他文件按原逻辑检查
if [ "$owner" = "$expected_owner" ] && [ "$group" = "$expected_group" ]; then
check_pass "文件所有者-$file" "所有者(符合要求)(当前:${owner}:${group})"
else
check_warning "文件所有者-$file" "所有者或组不正确(当前:${owner}:${group},建议:${expected_owner}:${expected_group})"
((issue_count++))
fi
fi
done
if [ -f "/etc/sudoers" ]; then
visudo -c -f /etc/sudoers >/dev/null 2>&1
if [ $? -eq 0 ]; then
check_pass "sudoers语法" "sudoers文件语法正确"
else
check_fail "sudoers语法" "sudoers文件语法错误"
((issue_count++))
fi
fi
local default_configs=("root" "%wheel" "%sudo" "%admin" "llody")
normalize_line() {
echo "$1" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g; s/[[:space:]]+/ /g; s/#.*$//; s/[[:space:]]*$//'
}
check_sudo_config() {
local file_path="$1"
local raw_line="$2"
local normalized_line=$(normalize_line "$raw_line")
[ -z "$normalized_line" ] && return
local entity=$(echo "$normalized_line" | awk '{print $1}')
for config in "${default_configs[@]}"; do
[ "$entity" = "$config" ] && return
done
if [[ "$normalized_line" =~ ALL=\([[:alnum:]:,]*\)[[:space:]]+.*ALL ]]; then
check_warning "sudo权限-$file_path" "检测到宽泛sudo权限:${normalized_line}"
((issue_count++))
fi
}
if [ -f "/etc/sudoers" ] && [ -r "/etc/sudoers" ]; then
while IFS= read -r line; do
check_sudo_config "/etc/sudoers" "$line"
done < <(grep -E '^[[:space:]]*%?[[:alnum:]_]+[[:space:]]+ALL=\([[:alnum:]:,]*\)[[:space:]]+.*ALL' /etc/sudoers 2>/dev/null)
else
check_fail "sudoers访问" "无法访问/etc/sudoers文件(可能不存在或无权限)"
((issue_count++))
fi
if [ -d "/etc/sudoers.d" ] && [ -r "/etc/sudoers.d" ]; then
for file in /etc/sudoers.d/*; do
[ -f "$file" ] && [ -r "$file" ] || continue
visudo -c -f "$file" >/dev/null 2>&1
if [ $? -eq 0 ]; then
check_pass "sudoers.d语法-$file" "sudoers.d文件语法正确"
else
check_fail "sudoers.d语法-$file" "sudoers.d文件语法错误"
((issue_count++))
fi
while IFS= read -r line; do
check_sudo_config "$file" "$line"
done < <(grep -E '^[[:space:]]*%?[[:alnum:]_]+[[:space:]]+ALL=\([[:alnum:]:,]*\)[[:space:]]+.*ALL' "$file" 2>/dev/null)
done
else
check_warning "sudoers.d访问" "无法访问/etc/sudoers.d目录(可能不存在或无权限)"
((issue_count++))
fi
if [ $issue_count -eq 0 ]; then
check_pass "用户权限控制" "用户权限及sudo配置符合规范"
else
check_warning "用户权限控制" "用户权限控制存在${issue_count}个问题"
fi
}
# 审计服务检测
check_audit_service() {
local issue_count=0
if systemctl is-active auditd &>/dev/null; then
check_pass "审计服务" "审计服务正在运行(auditd)"
else
check_fail "审计服务" "审计服务未运行"
((issue_count++))
fi
if [ $issue_count -eq 0 ]; then
check_pass "审计服务状态" "审计服务状态(符合要求)"
else
check_warning "审计服务状态" "审计服务存在${issue_count}个问题"
fi
}
# 日志配置检测
check_log_config() {
local issue_count=0
if grep -qE "^rotate\s+[0-9]+" /etc/logrotate.conf; then
local rotate_count=$(grep -E "^rotate\s+[0-9]+" /etc/logrotate.conf | awk '{print $2}')
if [ "$rotate_count" -ge 6 ]; then
check_pass "日志保留策略" "日志保留周期${rotate_count}(符合要求)"
else
check_warning "日志保留策略" "日志保留周期${rotate_count}(建议≥6)"
((issue_count++))
fi
else
check_warning "日志保留策略" "未配置日志保留周期(建议≥6)"
((issue_count++))
fi
local histtimeformat_found=0
if grep -q "HISTTIMEFORMAT" /etc/profile; then
histtimeformat_found=1
fi
if [ -d /etc/profile.d/ ] && grep -r "HISTTIMEFORMAT" /etc/profile.d/ >/dev/null 2>&1; then
histtimeformat_found=1
fi
if grep -q "HISTTIMEFORMAT" ~/.bashrc; then
histtimeformat_found=1
fi
if env | grep -q "HISTTIMEFORMAT"; then
histtimeformat_found=1
fi
if [ $histtimeformat_found -eq 1 ]; then
check_pass "操作审计" "已配置带时间戳的历史命令记录"
else
check_warning "操作审计" "未配置带时间戳的历史命令记录"
((issue_count++))
fi
if [ $issue_count -eq 0 ]; then
check_pass "日志配置" "日志配置符合基本要求"
else
check_warning "日志配置" "日志配置存在${issue_count}个问题"
fi
}
# 密码复杂度检测
check_password_complexity() {
local pam_file issue_count=0
case $OS_NAME in
ubuntu|debian) pam_file="/etc/pam.d/common-password" ;;
kylin) pam_file="/etc/security/pwquality.conf" ;;
centos|rhel|fedora|almalinux|rocky|hce|openEuler|bclinux) pam_file="/etc/pam.d/system-auth" ;;
*) pam_file="/etc/pam.d/common-password" ;;
esac
if [ ! -f "$pam_file" ]; then
check_fail "密码复杂度配置" "未找到密码复杂度配置文件:$pam_file"
((issue_count++))
return
fi
if [ "$OS_NAME" = "kylin" ]; then
local minlen dcredit ucredit lcredit ocredit minclass
minlen=$(grep -E '^minlen\s*=\s*[0-9]+' "$pam_file" | awk -F'=' '{print $2}' | tr -d ' ')
dcredit=$(grep -E '^dcredit\s*=\s*-?[0-9]+' "$pam_file" | awk -F'=' '{print $2}' | tr -d ' ')
ucredit=$(grep -E '^ucredit\s*=\s*-?[0-9]+' "$pam_file" | awk -F'=' '{print $2}' | tr -d ' ')
lcredit=$(grep -E '^lcredit\s*=\s*-?[0-9]+' "$pam_file" | awk -F'=' '{print $2}' | tr -d ' ')
ocredit=$(grep -E '^ocredit\s*=\s*-?[0-9]+' "$pam_file" | awk -F'=' '{print $2}' | tr -d ' ')
minclass=$(grep -E '^minclass\s*=\s*[0-9]+' "$pam_file" | awk -F'=' '{print $2}' | tr -d ' ')
if [[ "$minlen" =~ ^[0-9]+$ ]] && [ "$minlen" -ge 10 ]; then
check_pass "密码最小长度" "密码最小长度${minlen}(符合要求)"
else
check_warning "密码最小长度" "密码最小长度${minlen:-未配置}(应≥10)"
((issue_count++))
fi
if [[ "$dcredit" =~ ^-?[0-9]+$ ]] && [ "$dcredit" -le -1 ]; then
check_pass "密码数字要求" "密码要求至少1个数字(符合要求)"
else
check_warning "密码数字要求" "密码未要求包含数字(当前:${dcredit:-未配置})"
((issue_count++))
fi
if [[ "$ucredit" =~ ^-?[0-9]+$ ]] && [ "$ucredit" -le -1 ]; then
check_pass "密码大写字母要求" "密码要求至少1个大写字母(符合要求)"
else
check_warning "密码大写字母要求" "密码未要求包含大写字母(当前:${ucredit:-未配置})"
((issue_count++))
fi
if [[ "$lcredit" =~ ^-?[0-9]+$ ]] && [ "$lcredit" -le -1 ]; then
check_pass "密码小写字母要求" "密码要求至少1个小写字母(符合要求)"
else
check_warning "密码小写字母要求" "密码未要求包含小写字母(当前:${lcredit:-未配置})"
((issue_count++))
fi
if [[ "$ocredit" =~ ^-?[0-9]+$ ]] && [ "$ocredit" -le -1 ]; then
check_pass "密码特殊字符要求" "密码要求至少1个特殊字符(符合要求)"
else
check_warning "密码特殊字符要求" "密码未要求包含特殊字符(当前:${ocredit:-未配置})"
((issue_count++))
fi
if [[ "$minclass" =~ ^[0-9]+$ ]] && [ "$minclass" -ge 3 ]; then
check_pass "密码字符类别要求" "密码要求至少${minclass}类字符(符合要求)"
else
check_warning "密码字符类别要求" "密码字符类别${minclass:-未配置}(应≥3)"
((issue_count++))
fi
else
local pam_module=$(grep -E '^password\s+requisite\s+pam_(cracklib|pwquality).so' "$pam_file")
if [ -z "$pam_module" ]; then
check_fail "密码复杂度策略" "未配置密码复杂度要求"
((issue_count++))
else
if echo "$pam_module" | grep -qE 'minlen=([1-9][0-9]+|[1-9][0-9]*)'; then # 修正正则,匹配≥10
local minlen_val=$(echo "$pam_module" | sed -n 's/.*minlen=\([0-9]*\).*/\1/p')
if [ "$minlen_val" -ge 10 ]; then # 提升至≥10
check_pass "密码最小长度" "密码最小长度${minlen_val}(符合要求)"
else
check_warning "密码最小长度" "密码最小长度${minlen_val:-未配置}(建议≥10)"
((issue_count++))
fi
else
check_warning "密码最小长度" "未配置密码最小长度(建议≥12)"
((issue_count++))
fi
if echo "$pam_module" | grep -q 'dcredit=-1'; then
check_pass "密码数字要求" "密码包含数字要求(符合要求)"
else
check_warning "密码数字要求" "密码未要求包含数字"
((issue_count++))
fi
if echo "$pam_module" | grep -q 'ucredit=-1'; then
check_pass "密码大写字母要求" "密码包含大写字母要求(符合要求)"
else
check_warning "密码大写字母要求" "密码未要求包含大写字母"
((issue_count++))
fi
if echo "$pam_module" | grep -q 'lcredit=-1'; then
check_pass "密码小写字母要求" "密码包含小写字母要求(符合要求)"
else
check_warning "密码小写字母要求" "密码未要求包含小写字母"
((issue_count++))
fi
if echo "$pam_module" | grep -q 'ocredit=-1'; then
check_pass "密码特殊字符要求" "密码包含特殊字符要求(符合要求)"
else
check_warning "密码特殊字符要求" "密码未要求包含特殊字符"
((issue_count++))
fi
fi
fi
if [ $issue_count -eq 0 ]; then
check_pass "密码复杂度策略" "密码复杂度配置(符合要求)"
else
check_warning "密码复杂度策略" "密码复杂度策略存在${issue_count}个问题"
fi
}
# 账户锁定策略检测
check_account_lockout_policy() {
local pam_file issue_count=0
case $OS_NAME in
ubuntu|debian) pam_file="/etc/pam.d/common-auth" ;;
centos|rhel|fedora|almalinux|rocky|kylin|hce|openEuler|bclinux) pam_file="/etc/pam.d/system-auth" ;;
*) pam_file="/etc/pam.d/common-auth" ;;
esac
if [ ! -f "$pam_file" ]; then
check_fail "账户登录失败锁定策略" "未找到账户锁定配置文件:$pam_file"
((issue_count++))
return
fi
local pam_module=$(grep -E '^auth\s+required\s+pam_(faillock|tally2).so' "$pam_file")
if [ -z "$pam_module" ]; then
check_fail "账户登录失败锁定策略" "未配置账户锁定策略"
((issue_count++))
else
if echo "$pam_module" | grep -q 'deny=[1-9]'; then
check_pass "账户登录失败次数限制" "已配置失败次数限制"
else
check_warning "账户登录失败次数限制" "未配置失败次数限制(建议deny=5)"
((issue_count++))
fi
if echo "$pam_module" | grep -q 'unlock_time=[1-9][0-9]*'; then
check_pass "账户锁定时间" "已配置账户锁定时间"
else
check_warning "账户锁定时间" "未配置账户锁定时间(建议unlock_time=600)"
((issue_count++))
fi
fi
if [ $issue_count -eq 0 ]; then
check_pass "账户登录失败锁定策略" "账户锁定策略配置(符合要求)"
else
check_warning "账户登录失败锁定策略" "账户锁定策略存在${issue_count}个问题"
fi
}
# 远程管理地址限制检测
check_remote_access_restriction() {
local hosts_allow="/etc/hosts.allow"
local hosts_deny="/etc/hosts.deny"
local issue_count=0
if [ "$OS_NAME" = "kylin" ] || [ "$OS_NAME" = "hce" ] || [ "$OS_NAME" = "openEuler" ] || [ "$OS_NAME" = "bclinux" ]; then
check_pass "远程管理地址限制检测" "国产操作系统无需配置hosts.allow/deny"
return
fi
local ip_regex='\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b'
local cidr_regex='\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/\d{1,2}\b'
local allow_regex="^sshd:\s*($ip_regex|$cidr_regex)"
if [ -f "$hosts_allow" ]; then
if grep -qE "$allow_regex" "$hosts_allow"; then
check_pass "远程管理地址限制-允许" "已配置允许访问的合法IP地址或网段"
else
check_warning "远程管理地址限制-允许" "/etc/hosts.allow 未配置允许访问的合法 IP 地址或网段"
((issue_count++))
fi
else
check_fail "远程管理地址限制-允许" "未找到 /etc/hosts.allow 文件"
((issue_count++))
fi
if [ -f "$hosts_deny" ]; then
if grep -qE '^sshd:\s*[Aa][Ll][Ll]' "$hosts_deny"; then
check_pass "远程管理地址限制-拒绝" "已配置拒绝所有其他主机的访问"
else
check_warning "远程管理地址限制-拒绝" "/etc/hosts.deny 未配置拒绝所有其他主机的访问"
((issue_count++))
fi
else
check_fail "远程管理地址限制-拒绝" "未找到 /etc/hosts.deny 文件"
((issue_count++))
fi
if [ $issue_count -eq 0 ]; then
check_pass "远程管理地址限制检测" "远程管理地址限制配置(符合要求)"
else
check_warning "远程管理地址限制检测" "远程管理地址限制存在${issue_count}个问题"
fi
}
# 三权分力账户检测
check_three_roles_accounts() {
local roles=("sysadmin" "auditadmin" "securityadmin")
local issue_count=0
for role in "${roles[@]}"; do
if ! id "$role" &>/dev/null; then
check_warning "账户-$role" "未找到账户:$role(建议创建职责分离账户)" # 改为 warning
((issue_count++))
else
check_pass "账户-$role" "账户 $role 存在"
# 统一检查 sudo/wheel 组权限
if groups "$role" | grep -qE '(sudo|wheel)'; then
check_pass "账户权限-$role" "账户 $role 已授予最小权限(sudo/wheel 组)"
else
check_warning "账户权限-$role" "账户 $role 未授予最小权限(未加入sudo/wheel组)"
((issue_count++))
fi
fi
done
if [ $issue_count -eq 0 ]; then
check_pass "三权账户检测" "三权账户配置(符合要求)"
else
check_warning "三权账户检测" "三权账户存在${issue_count}个问题"
fi
}
# 审计日志留存检测
check_audit_log_retention() {
local auditd_conf="/etc/audit/auditd.conf"
local logrotate_conf="/etc/logrotate.d/audit"
local issue_count=0
if [ -f "$auditd_conf" ]; then
local num_logs_value=$(grep -E '^num_logs\s*=\s*[0-9]+' "$auditd_conf" | awk -F'=' '{print $2}' | tr -d ' ')
if [[ "$num_logs_value" =~ ^[0-9]+$ ]] && [ "$num_logs_value" -ge 26 ]; then
check_pass "审计日志保留数量" "已配置保留至少${num_logs_value}个日志文件"
else
check_warning "审计日志保留数量" "未配置保留至少26个日志文件(当前:${num_logs_value:-未配置},建议≥180天)"
((issue_count++))
fi
else
check_fail "审计日志配置文件" "未找到auditd配置文件:$auditd_conf"
((issue_count++))
fi
if [ -f "$logrotate_conf" ]; then
local rotate_value=$(grep -E '^\s*rotate\s+[0-9]+' "$logrotate_conf" | awk '{print $2}')
if [[ "$rotate_value" =~ ^[0-9]+$ ]] && [ "$rotate_value" -ge 12 ]; then
check_pass "审计日志轮转保留" "logrotate已配置保留至少${rotate_value}个日志文件"
else
check_warning "审计日志轮转保留" "logrotate未配置保留至少12个日志文件(当前:${rotate_value:-未配置})"
((issue_count++))
fi
if grep -qE '^\s*daily\b' "$logrotate_conf"; then
check_pass "审计日志轮转频率" "logrotate已配置每天轮转日志"
else
check_warning "审计日志轮转频率" "logrotate未配置每天轮转日志"
((issue_count++))
fi
else
check_fail "审计日志轮转配置文件" "未找到logrotate配置文件:$logrotate_conf"
((issue_count++))
fi
if [ $issue_count -eq 0 ]; then
check_pass "审计日志留存检测" "审计日志留存配置(符合要求)"
else
check_warning "审计日志留存检测" "审计日志留存存在${issue_count}个问题"
fi
}
# 文件完整性检测
check_file_integrity() {
local critical_files=(
"/bin/bash"
"/usr/bin/sudo"
"/etc/passwd"
"/etc/shadow"
"/etc/group"
"/etc/sudoers"
"/etc/ssh/sshd_config"
)
local issue_count=0
if command -v rpm &>/dev/null; then
for file in "${critical_files[@]}"; do
if [ -f "$file" ]; then
if rpm -Vf "$file" &>/dev/null; then
check_pass "文件完整性-$file" "文件未被修改"
else
local mtime=$(stat -c '%Y' "$file" 2>/dev/null)
local modified_time=$(date -d @$mtime +'%Y-%m-%d %H:%M:%S' 2>/dev/null)
check_warning "文件完整性-$file" "系统关键文件可能被修改(最后修改时间: ${modified_time:-未知})"
((issue_count++))
fi
else
check_warning "文件完整性-$file" "文件不存在"
((issue_count++))
fi
done
elif command -v debsums &>/dev/null; then
for file in "${critical_files[@]}"; do
if [ -f "$file" ]; then
if debsums -e "$file" &>/dev/null; then
check_pass "文件完整性-$file" "文件未被修改"
else
local mtime=$(stat -c '%Y' "$file" 2>/dev/null)
local modified_time=$(date -d @$mtime +'%Y-%m-%d %H:%M:%S' 2>/dev/null)
check_warning "文件完整性-$file" "系统关键文件可能被修改(最后修改时间: ${modified_time:-未知})"
((issue_count++))
fi
else
check_warning "文件完整性-$file" "文件不存在"
((issue_count++))
fi
done
else
for file in "${critical_files[@]}"; do
if [ -f "$file" ]; then
local mtime=$(stat -c %Y "$file")
local now=$(date +%s)
local modified_time=$(date -d @$mtime +'%Y-%m-%d %H:%M:%S' 2>/dev/null)
if [ $((now - mtime)) -lt 86400 ]; then
check_warning "文件完整性-$file" "文件最近被修改(24小时内)(最后修改时间: ${modified_time:-未知})"
((issue_count++))
else
check_pass "文件完整性-$file" "文件未在最近24小时内修改"
fi
else
check_warning "文件完整性-$file" "文件不存在"
((issue_count++))
fi
done
fi
if command -v aide &>/dev/null || command -v tripwire &>/dev/null; then
check_pass "文件完整性工具" "已安装文件完整性检查工具(AIDE/Tripwire)"
else
check_warning "文件完整性工具" "未安装文件完整性检查工具(建议安装AIDE或Tripwire)"
((issue_count++))
fi
if [ $issue_count -eq 0 ]; then
check_pass "文件完整性检查" "关键文件完整性检查通过"
else
check_warning "文件完整性检查" "文件完整性检查存在${issue_count}个问题"
fi
}
# 账户文件检测
check_account_files() {
local issue_count=0
if [ -f "/etc/shadow" ] && [ -r "/etc/shadow" ]; then
if grep -qE '^[[:alnum:]_-]+::' /etc/shadow 2>/dev/null; then
check_fail "账户安全-空密码" "发现空密码账户"
((issue_count++))
else
check_pass "账户安全-空密码" "未发现空密码账户"
fi
if grep -qE '^[[:alnum:]_-]+:\$1\$' /etc/shadow 2>/dev/null; then
check_warning "账户安全-密码哈希" "发现使用弱MD5哈希的账户"
((issue_count++))
else
check_pass "账户安全-密码哈希" "未发现弱MD5哈希的账户"
fi
local locked_accounts=$(grep -cE '^[[:alnum:]_-]+:!!' /etc/shadow 2>/dev/null)
if [ "$locked_accounts" -gt 0 ]; then
check_pass "账户安全-锁定账户" "发现${locked_accounts}个锁定账户(符合要求)"
else
check_warning "账户安全-锁定账户" "未发现锁定账户,可能存在未锁定的默认或不必要账户"
((issue_count++))
fi
else
check_fail "账户安全-shadow文件" "未找到或无法读取/etc/shadow文件"
((issue_count++))
fi
if [ -f "/etc/passwd" ] && [ -r "/etc/passwd" ]; then
local super_users=$(grep -cE '^[[:alnum:]_-]+:[^:]*:0:' /etc/passwd 2>/dev/null)
if [ "$super_users" -le 1 ]; then
check_pass "账户安全-超级用户" "仅root账户具有UID 0(符合要求)"
else
check_fail "账户安全-超级用户" "发现多个UID为0的账户"
((issue_count++))
fi
if grep -qE '^[[:alnum:]_-]+::' /etc/passwd 2>/dev/null; then
check_fail "账户安全-无密码" "发现无密码账户"
((issue_count++))
else
check_pass "账户安全-无密码" "未发现无密码账户"
fi
else
check_fail "账户安全-passwd文件" "未找到或无法读取/etc/passwd文件"
((issue_count++))
fi
# 不活跃账户扫描(使用 lastlog 检查从未登录或最近登录>90天的账户,排除 uid<1000 系统账户)
local inactive_accounts=""
local now=$(date +%s)
while read -r line; do
local user=$(echo "$line" | awk '{print $1}')
local last_login=$(echo "$line" | awk '{print $NF}')
local uid=$(id -u "$user" 2>/dev/null)
if [ -n "$uid" ] && [ "$uid" -ge 1000 ]; then # 仅检查普通用户
if [[ "$last_login" == "Never" ]]; then
inactive_accounts+="$user (从未登录) "
elif [[ "$last_login" =~ [0-9]+ ]]; then
local last_time=$(date -d "$last_login" +%s 2>/dev/null)
if [ $((now - last_time)) -gt 7776000 ]; then # 90天 = 90*86400
inactive_accounts+="$user (最近登录: $last_login) "
fi
fi
fi
done < <(lastlog -t 99999 2>/dev/null | tail -n +2) # lastlog 输出排除标题行
if [ -n "$inactive_accounts" ]; then
check_warning "账户安全-不活跃账户" "发现不活跃账户: $inactive_accounts(建议锁定或删除)"
((issue_count++))
else
check_pass "账户安全-不活跃账户" "未发现不活跃账户"
fi
if [ $issue_count -eq 0 ]; then
check_pass "账户文件检查" "账户文件检查通过"
else
check_warning "账户文件检查" "账户文件检查存在${issue_count}个问题"
fi
}
# 高危端口检测(等保三级:网络安全,边界防护)
check_dangerous_ports() {
local absolute_dangerous_ports=("21" "23" "135" "137" "138" "139" "445" "873" "1433" "1521" "2049" "3389" "5900" "11211") # 绝对高危:FTP/Telnet/RPC/NetBIOS/SMB/rsync/MSSQL/Oracle/NFS/RDP/VNC/Memcached
local potential_dangerous_ports=("22" "80" "443" "3306" "5432" "6379" "7001" "7002" "8000" "8008" "8080" "8081" "8443" "9200" "27017") # 潜在高危:SSH/HTTP/HTTPS/MySQL/PostgreSQL/Redis/WebLogic/HTTP variants/Elasticsearch/MongoDB
local issue_count=0
# 检测命令选择
local net_cmd
if command -v ss >/dev/null 2>&1; then
net_cmd="ss -tuln"
elif command -v netstat >/dev/null 2>&1; then
net_cmd="netstat -tuln"
elif command -v lsof >/dev/null 2>&1; then
net_cmd="lsof -i -P -n | grep LISTEN"
else
check_warning "端口检测工具" "无法检测端口(未找到ss/netstat/lsof命令)"
((issue_count++))
if [ $issue_count -eq 0 ]; then
check_pass "网络端口检查" "网络端口检查通过"
else
check_warning "网络端口检查" "网络端口检查存在${issue_count}个问题"
fi
return
fi
# 检测开放端口
local open_absolute=() open_potential=()
for port in "${absolute_dangerous_ports[@]}"; do
if $net_cmd | grep -qE "(:$port\b|\.$port\b)"; then
open_absolute+=("$port")
fi
done
for port in "${potential_dangerous_ports[@]}"; do
if $net_cmd | grep -qE "(:$port\b|\.$port\b)"; then
open_potential+=("$port")
fi
done
# 小项报告
if [ ${#open_absolute[@]} -gt 0 ]; then
check_fail "绝对高危端口" "发现开放的绝对高危端口: ${open_absolute[*]}(建议立即关闭)"
((issue_count++))
echo "<div class='details'><p>绝对高危端口详情:</p><ul>" >> "$REPORT_FILE"
for port in "${open_absolute[@]}"; do
local service=$(grep -w "$port/tcp" /etc/services | awk '{print $1}' | head -1 || echo "未知")
echo "<li>端口 $port: $service(建议禁用相关服务或关闭端口)</li>" >> "$REPORT_FILE"
done
echo "</ul></div>" >> "$REPORT_FILE"
else
check_pass "绝对高危端口" "未发现开放的绝对高危端口"
fi
if [ ${#open_potential[@]} -gt 0 ]; then
check_warning "潜在高危端口" "发现开放的潜在高危端口: ${open_potential[*]}(评估是否必要)"
((issue_count++))
echo "<div class='details'><p>潜在高危端口详情:</p><ul>" >> "$REPORT_FILE"
for port in "${open_potential[@]}"; do
local service=$(grep -w "$port/tcp" /etc/services | awk '{print $1}' | head -1 || echo "未知")
echo "<li>端口 $port: $service(必要时限制IP访问)</li>" >> "$REPORT_FILE"
done
echo "</ul></div>" >> "$REPORT_FILE"
else
check_pass "潜在高危端口" "未发现开放的潜在高危端口"
fi
# 防火墙检查(兼容多 OS)
local fw_active=0
if command -v iptables >/dev/null 2>&1 && iptables -L -n >/dev/null 2>&1; then
fw_active=1
elif command -v nft >/dev/null 2>&1 && nft list ruleset >/dev/null 2>&1; then
fw_active=1
elif command -v ufw >/dev/null 2>&1 && ufw status | grep -q "active"; then
fw_active=1
elif command -v firewall-cmd >/dev/null 2>&1 && firewall-cmd --state >/dev/null 2>&1; then
fw_active=1
fi
if [ $fw_active -eq 0 ]; then
check_warning "防火墙状态" "防火墙未启用或无法检测(建议启用 iptables/nftables/ufw/firewalld)"
((issue_count++))
else
check_pass "防火墙状态" "防火墙已启用"
fi
# 大项总结
if [ $issue_count -eq 0 ]; then
check_pass "网络端口检查" "网络端口检查符合等保三级要求"
else
check_warning "网络端口检查" "网络端口检查存在${issue_count}个问题"
fi
}
# 口令加密算法检测
check_password_encryption() {
local issue_count=0
if [ -f /etc/login.defs ] && [ -r /etc/login.defs ]; then
if grep -q '^ENCRYPT_METHOD\s+SHA512' /etc/login.defs; then
check_pass "口令加密算法" "使用SHA512加密算法(符合要求)"
else
local current_method=$(grep '^ENCRYPT_METHOD' /etc/login.defs | awk '{print $2}')
check_warning "口令加密算法" "未使用SHA512加密算法(当前:${current_method:-未配置})"
((issue_count++))
fi
else
check_fail "口令加密算法" "无法读取 /etc/login.defs(可能无权限或文件不存在)"
((issue_count++))
fi
if [ $issue_count -eq 0 ]; then
check_pass "口令加密算法检查" "口令加密算法(符合要求)"
else
check_warning "口令加密算法检查" "口令加密算法存在${issue_count}个问题"
fi
}
# 默认账户检测
check_default_accounts() {
local default_accounts=("games" "ftp" "nobody" "news" "uucp")
local issue_count=0
for account in "${default_accounts[@]}"; do
if id "$account" &>/dev/null; then
if grep -q "^${account}:.*:!!" /etc/shadow 2>/dev/null; then
check_pass "默认账户-$account" "默认账户 $account 已锁定"
else
check_warning "默认账户-$account" "默认账户 $account 未锁定或启用"
((issue_count++))
fi
else
check_pass "默认账户-$account" "默认账户 $account 不存在"
fi
done
if [ $issue_count -eq 0 ]; then
check_pass "默认账户检查" "默认账户检查通过"
else
check_warning "默认账户检查" "默认账户检查存在${issue_count}个问题"
fi
}
# 增强版文件系统挂载选项检测
check_filesystem_mounts() {
local issue_count=0
local critical_mounts=("/tmp:noexec,nosuid,nodev" "/var/tmp:noexec,nosuid,nodev" "/dev/shm:nosuid,nodev" "/home:nodev,nosuid")
for mount in "${critical_mounts[@]}"; do
local path=$(echo "$mount" | cut -d':' -f1)
local options=$(echo "$mount" | cut -d':' -f2 | tr ',' ' ')
# 检查当前实际挂载选项(而不仅仅是fstab配置)
if mount | grep -qE "\s+$path\s+"; then
local current_options=$(mount | grep -E "\s+$path\s+" | awk '{print $6}' | tr ',' ' ')
for opt in $options; do
if echo "$current_options" | grep -qw "$opt"; then
check_pass "挂载选项-$path-$opt" "$path 已配置 $opt 选项"
else
check_warning "挂载选项-$path-$opt" "$path 未配置 $opt 选项(等保要求)"
((issue_count++))
fi
done
else
check_warning "挂载选项-$path" "$path 未挂载或无法检测"
((issue_count++))
fi
done
if [ $issue_count -eq 0 ]; then
check_pass "文件系统挂载选项" "文件系统挂载选项符合等保要求"
else
check_warning "文件系统挂载选项" "文件系统挂载选项存在${issue_count}个问题(不符合等保要求)"
fi
}
# 新增:审计规则检测
check_audit_rules() {
local audit_rules_file="/etc/audit/audit.rules"
local issue_count=0
local critical_audits=(
"-w /etc/passwd -p wa -k passwd_changes"
"-w /etc/shadow -p wa -k shadow_changes"
"-w /etc/group -p wa -k group_changes"
"-a always,exit -F arch=b64 -S execve -k exec"
)
if [ -f "$audit_rules_file" ] && [ -r "$audit_rules_file" ]; then
for rule in "${critical_audits[@]}"; do
if grep -qF "$rule" "$audit_rules_file"; then
check_pass "审计规则-$(echo "$rule" | awk '{print $2}')" "已配置审计规则:$rule"
else
check_warning "审计规则-$(echo "$rule" | awk '{print $2}')" "未配置审计规则:$rule"
((issue_count++))
fi
done
else
check_fail "审计规则文件" "未找到或无法读取 $audit_rules_file"
((issue_count++))
fi
if [ $issue_count -eq 0 ]; then
check_pass "审计规则检查" "审计规则配置(符合要求)"
else
check_warning "审计规则检查" "审计规则检查存在${issue_count}个问题"
fi
}
# 新增:系统补丁检查
check_system_patches() {
local issue_count=0
if [ "$OS_NAME" = "centos" ] || [ "$OS_NAME" = "rhel" ] || [ "$OS_NAME" = "bclinux" ]; then
if yum check-update &>/dev/null; then
check_pass "系统补丁" "系统补丁已是最新"
else
check_warning "系统补丁" "存在可更新的系统补丁"
((issue_count++))
fi
elif [ "$OS_NAME" = "ubuntu" ] || [ "$OS_NAME" = "debian" ]; then
if apt list --upgradable 2>/dev/null | grep -q "upgradable"; then
check_warning "系统补丁" "存在可更新的系统补丁"
((issue_count++))
else
check_pass "系统补丁" "系统补丁已是最新"
fi
else
check_warning "系统补丁" "无法检测系统补丁(不支持的系统:$OS_NAME)"
((issue_count++))
fi
if [ $issue_count -eq 0 ]; then
check_pass "系统补丁检查" "系统补丁检查通过"
else
check_warning "系统补丁检查" "系统补丁检查存在${issue_count}个问题"
fi
}
# 生成统计信息和修复建议(等保三级:报告汇总)
generate_statistics() {
# 关闭身份鉴别表格,生成后续分类表格
echo "</table>" >> "$REPORT_FILE"
echo "<h2 id=\"access\">访问控制</h2><table><tr><th>检查项</th><th>状态</th><th>详情</th></tr>" >> "$REPORT_FILE"
check_user_permissions
check_remote_access_restriction
check_filesystem_mounts
echo "</table>" >> "$REPORT_FILE"
echo "<h2 id=\"audit\">安全审计</h2><table><tr><th>检查项</th><th>状态</th><th>详情</th></tr>" >> "$REPORT_FILE"
check_audit_service
check_log_config
check_audit_log_retention
check_audit_rules
echo "</table>" >> "$REPORT_FILE"
echo "<h2 id=\"intrusion\">入侵防范</h2><table><tr><th>检查项</th><th>状态</th><th>详情</th></tr>" >> "$REPORT_FILE"
check_file_integrity
check_system_patches
echo "</table>" >> "$REPORT_FILE"
echo "<h2 id=\"network\">网络安全</h2><table><tr><th>检查项</th><th>状态</th><th>详情</th></tr>" >> "$REPORT_FILE"
check_dangerous_ports
echo "</table>" >> "$REPORT_FILE"
# 统计合规率
local total=${#CHECK_RESULTS[@]}
local pass_count=0 warn_count=0 fail_count=0
local identity_pass=0 identity_total=0 access_pass=0 access_total=0
local audit_pass=0 audit_total=0 intrusion_pass=0 intrusion_total=0 network_pass=0 network_total=0
for check in "${!CHECK_RESULTS[@]}"; do
case ${CHECK_RESULTS[$check]} in
PASS) ((pass_count++));;
WARNING) ((warn_count++));;
FAIL) ((fail_count++));;
esac
if [[ $check =~ ^(密码|账户|三权|口令) ]]; then
((identity_total++))
[ "${CHECK_RESULTS[$check]}" = "PASS" ] && ((identity_pass++))
elif [[ $check =~ ^(用户权限|远程管理|挂载) ]]; then
((access_total++))
[ "${CHECK_RESULTS[$check]}" = "PASS" ] && ((access_pass++))
elif [[ $check =~ ^(审计|日志) ]]; then
((audit_total++))
[ "${CHECK_RESULTS[$check]}" = "PASS" ] && ((audit_pass++))
elif [[ $check =~ ^(文件完整性|系统补丁) ]]; then
((intrusion_total++))
[ "${CHECK_RESULTS[$check]}" = "PASS" ] && ((intrusion_pass++))
elif [[ $check =~ ^(网络端口|绝对高危端口|潜在高危端口|防火墙状态) ]]; then
((network_total++))
[ "${CHECK_RESULTS[$check]}" = "PASS" ] && ((network_pass++))
fi
done
# 检查 bc 命令可用性
local bc_available=1
if ! command -v bc >/dev/null 2>&1; then
bc_available=0
echo "<li>计算工具: 缺少 bc 命令,使用整数计算,可能影响精度,建议安装 bc: <code>$([ "$OS_NAME" = "centos" ] && echo 'yum install bc -y' || echo 'apt install bc -y')</code></li>" >> "$REPORT_FILE"
fi
# 计算合规率(bc 或 awk 后备)
local compliance_rate
if [ $bc_available -eq 1 ]; then
compliance_rate=$(bc <<< "scale=2; $pass_count*100/$total")
else
compliance_rate=$(awk "BEGIN {printf \"%.2f\", $pass_count*100/$total}")
fi
local weighted_compliance=0
for check in "${!CHECK_RESULTS[@]}"; do
local weight=${CHECK_WEIGHTS[$check]:-0.05}
if [ "${CHECK_RESULTS[$check]}" = "PASS" ]; then
if [ $bc_available -eq 1 ]; then
weighted_compliance=$(bc <<< "$weighted_compliance + $weight")
else
weighted_compliance=$(awk "BEGIN {printf \"%.2f\", $weighted_compliance + $weight}")
fi
fi
done
if [ $bc_available -eq 1 ]; then
weighted_compliance=$(bc <<< "scale=2; $weighted_compliance * 100")
else
weighted_compliance=$(awk "BEGIN {printf \"%.2f\", $weighted_compliance * 100}")
fi
local identity_rate=$([ $identity_total -gt 0 ] && ( [ $bc_available -eq 1 ] && bc <<< "scale=2; $identity_pass*100/$identity_total" || awk "BEGIN {printf \"%.2f\", $identity_pass*100/$identity_total}") || echo 0)
local access_rate=$([ $access_total -gt 0 ] && ( [ $bc_available -eq 1 ] && bc <<< "scale=2; $access_pass*100/$access_total" || awk "BEGIN {printf \"%.2f\", $access_pass*100/$access_total}") || echo 0)
local audit_rate=$([ $audit_total -gt 0 ] && ( [ $bc_available -eq 1 ] && bc <<< "scale=2; $audit_pass*100/$audit_total" || awk "BEGIN {printf \"%.2f\", $audit_pass*100/$audit_total}") || echo 0)
local intrusion_rate=$([ $intrusion_total -gt 0 ] && ( [ $bc_available -eq 1 ] && bc <<< "scale=2; $intrusion_pass*100/$intrusion_total" || awk "BEGIN {printf \"%.2f\", $intrusion_pass*100/$intrusion_total}") || echo 0)
local network_rate=$([ $network_total -gt 0 ] && ( [ $bc_available -eq 1 ] && bc <<< "scale=2; $network_pass*100/$network_total" || awk "BEGIN {printf \"%.2f\", $network_pass*100/$network_total}") || echo 0)
# 生成统计信息(使用 CSS 进度条)
cat >> "$REPORT_FILE" <<EOF
<h2>统计信息</h2>
<div class="stats">
<p>总检查项:$total</p>
<p>通过项:<span style="color:#4CAF50">$pass_count</span></p>
<p>警告项:<span style="color:#FFC107">$warn_count</span></p>
<p>失败项:<span style="color:#F44336">$fail_count</span></p>
<p>合规率:<strong>${compliance_rate}%</strong></p>
<div class="progress"><div class="progress-bar" style="width: ${compliance_rate}%">${compliance_rate}% 通过</div></div>
<p>加权合规率:<strong>${weighted_compliance}%</strong></p>
<div class="progress"><div class="progress-bar" style="width: 100%">${weighted_compliance}% 加权</div></div>
<h3>分类合规率</h3>
<ul>
<li>身份鉴别:${identity_rate}% ($identity_pass/$identity_total)
<div class="progress"><div class="progress-bar" style="width: ${identity_rate}%">${identity_rate}%</div></div>
</li>
<li>访问控制:${access_rate}% ($access_pass/$access_total)
<div class="progress"><div class="progress-bar" style="width: ${access_rate}%">${access_rate}%</div></div>
</li>
<li>安全审计:${audit_rate}% ($audit_pass/$audit_total)
<div class="progress"><div class="progress-bar" style="width: ${audit_rate}%">${audit_rate}%</div></div>
</li>
<li>入侵防范:${intrusion_rate}% ($intrusion_pass/$intrusion_total)
<div class="progress"><div class="progress-bar" style="width: ${intrusion_rate}%">${intrusion_rate}%</div></div>
</li>
<li>网络安全:${network_rate}% ($network_pass/$network_total)
<div class="progress"><div class="progress-bar" style="width: ${network_rate}%">${network_rate}%</div></div>
</li>
</ul>
</div>
<h2>修复建议摘要</h2>
<ul>
EOF
# 定义全局上下文变量(需在检查函数中设置)
: ${OPEN_PORTS:=""} # 由 check_dangerous_ports 设置,格式 "21 23 445"
: ${MISSING_RULES:=""} # 由 check_audit_rules 设置,格式 "-w /etc/passwd -p wa -k passwd_changes"
: ${EMPTY_PASSWD_ACCOUNTS:=""} # 由 check_account_security 设置,格式 "user1 user2"
: ${DEFAULT_ACCOUNTS:="games ftp nobody"} # 默认账户列表
: ${CRITICAL_MOUNTS:="/tmp:noexec,nosuid /var/tmp:noexec,nosuid /dev/shm:nosuid"} # 挂载点和选项
: ${SENSITIVE_FILES:="/etc/passwd:644:root:root /etc/shadow:600:root:shadow /etc/group:644:root:root /etc/sudoers:440:root:root"} # 敏感文件
# 动态生成修复建议
for check in "${!CHECK_RESULTS[@]}"; do
if [[ ${CHECK_RESULTS[$check]} == "FAIL" || ${CHECK_RESULTS[$check]} == "WARNING" ]]; then
case $check in
"密码最大有效期")
echo "<li>$check: 设置最大有效期为30天: <code>sed -i '/^PASS_MAX_DAYS/s/.*/PASS_MAX_DAYS 30/' /etc/login.defs</code></li>" >> "$REPORT_FILE"
;;
"密码最小修改间隔")
echo "<li>$check: 设置最小修改间隔为7天: <code>sed -i '/^PASS_MIN_DAYS/s/.*/PASS_MIN_DAYS 7/' /etc/login.defs</code></li>" >> "$REPORT_FILE"
;;
"密码过期提醒")
echo "<li>$check: 设置过期提醒为7天: <code>sed -i '/^PASS_WARN_AGE/s/.*/PASS_WARN_AGE 7/' /etc/login.defs</code></li>" >> "$REPORT_FILE"
;;
"密码最小长度")
echo "<li>$check: 设置最小长度为12: <code>sed -i '/^PASS_MIN_LEN/s/.*/PASS_MIN_LEN 12/' /etc/login.defs</code></li>" >> "$REPORT_FILE"
;;
"密码策略文件")
echo "<li>$check: 检查文件权限和存在性: <code>touch /etc/login.defs; chmod 644 /etc/login.defs; chown root:root /etc/login.defs</code></li>" >> "$REPORT_FILE"
;;
"SSH协议版本")
echo "<li>$check: 移除 Protocol 1 并重启: <code>sed -i '/^Protocol 1/d' /etc/ssh/sshd_config; systemctl restart sshd</code></li>" >> "$REPORT_FILE"
;;
"SSH Root登录")
echo "<li>$check: 禁用 Root 登录: <code>sed -i '/^#*PermitRootLogin/s/.*/PermitRootLogin no/' /etc/ssh/sshd_config; systemctl restart sshd</code></li>" >> "$REPORT_FILE"
;;
"SSH会话超时")
echo "<li>$check: 设置超时为300秒和3次: <code>sed -i '/^#*ClientAliveInterval/s/.*/ClientAliveInterval 300/' /etc/ssh/sshd_config; sed -i '/^#*ClientAliveCountMax/s/.*/ClientAliveCountMax 3/' /etc/ssh/sshd_config; systemctl restart sshd</code></li>" >> "$REPORT_FILE"
;;
"SSH空密码登录")
echo "<li>$check: 禁用空密码登录: <code>sed -i '/^#*PermitEmptyPasswords/s/.*/PermitEmptyPasswords no/' /etc/ssh/sshd_config; systemctl restart sshd</code></li>" >> "$REPORT_FILE"
;;
"SSH最大认证尝试")
echo "<li>$check: 设置最大认证尝试为4: <code>sed -i '/^#*MaxAuthTries/s/.*/MaxAuthTries 4/' /etc/ssh/sshd_config; systemctl restart sshd</code></li>" >> "$REPORT_FILE"
;;
"SSH配置文件")
echo "<li>$check: 检查文件权限: <code>touch /etc/ssh/sshd_config; chmod 600 /etc/ssh/sshd_config; chown root:root /etc/ssh/sshd_config</code></li>" >> "$REPORT_FILE"
;;
"文件权限-"*)
local file=$(echo "$check" | cut -d'-' -f2-)
local max_perm=$(echo "$SENSITIVE_FILES" | grep "$file" | cut -d':' -f2)
echo "<li>$check: 设置权限为 $max_perm: <code>chmod $max_perm $file</code></li>" >> "$REPORT_FILE"
;;
"文件所有者-"*)
local file=$(echo "$check" | cut -d'-' -f2-)
local owner_group=$(echo "$SENSITIVE_FILES" | grep "$file" | cut -d':' -f3-4)
echo "<li>$check: 设置所有者和组为 $owner_group: <code>chown $owner_group $file</code></li>" >> "$REPORT_FILE"
;;
"sudoers语法"|"sudoers访问")
echo "<li>$check: 检查 /etc/sudoers 语法和权限: <code>visudo; chmod 440 /etc/sudoers; chown root:root /etc/sudoers</code></li>" >> "$REPORT_FILE"
;;
"sudoers.d访问")
echo "<li>$check: 设置 /etc/sudoers.d 目录权限: <code>mkdir -p /etc/sudoers.d; chmod 750 /etc/sudoers.d; chown root:root /etc/sudoers.d</code></li>" >> "$REPORT_FILE"
;;
"审计服务")
echo "<li>$check: 启用 auditd 服务: <code>systemctl start auditd; systemctl enable auditd</code></li>" >> "$REPORT_FILE"
;;
"日志保留策略")
echo "<li>$check: 设置 rotate 为26: <code>sed -i '/^rotate/s/.*/rotate 26/' /etc/logrotate.conf</code></li>" >> "$REPORT_FILE"
;;
"操作审计")
echo "<li>$check: 添加 HISTTIMEFORMAT: <code>echo 'export HISTTIMEFORMAT=\"%F %T \"' >> /etc/profile; source /etc/profile</code></li>" >> "$REPORT_FILE"
;;
"密码复杂度配置"|"密码数字要求"|"密码大写字母要求"|"密码小写字母要求"|"密码特殊字符要求"|"密码字符类别")
echo "<li>$check: 配置 pam_pwquality.so: <code>echo 'password requisite pam_pwquality.so minlen=12 dcredit=-1 ucredit=-1 lcredit=-1 ocredit=-1 minclass=4' >> $pam_file</code></li>" >> "$REPORT_FILE"
;;
"账户登录失败锁定策略"|"账户登录失败次数限制"|"账户锁定时间")
echo "<li>$check: 配置 pam_faillock.so: <code>echo 'auth required pam_faillock.so preauth silent deny=5 unlock_time=600' >> $pam_file; echo 'auth [default=die] pam_faillock.so authfail deny=5 unlock_time=600' >> $pam_file</code></li>" >> "$REPORT_FILE"
;;
"远程管理地址限制-允许")
echo "<li>$check: 限制 sshd 访问: <code>echo 'sshd: 192.168.0.0/24' >> /etc/hosts.allow</code></li>" >> "$REPORT_FILE"
;;
"远程管理地址限制-拒绝")
echo "<li>$check: 拒绝所有 sshd 访问: <code>echo 'sshd: ALL' >> /etc/hosts.deny</code></li>" >> "$REPORT_FILE"
;;
"账户-"*)
local role=$(echo "$check" | cut -d'-' -f2)
echo "<li>$check: 创建 $role 账户并加入 sudo 组: <code>useradd $role; usermod -aG sudo $role</code></li>" >> "$REPORT_FILE"
;;
"账户权限-"*)
local role=$(echo "$check" | cut -d'-' -f2)
echo "<li>$check: 将 $role 加入 sudo 组: <code>usermod -aG sudo $role</code></li>" >> "$REPORT_FILE"
;;
"审计日志保留数量")
echo "<li>$check: 设置 num_logs 为26: <code>sed -i '/^num_logs/s/.*/num_logs = 26/' /etc/audit/auditd.conf</code></li>" >> "$REPORT_FILE"
;;
"审计日志轮转保留")
echo "<li>$check: 设置 rotate 为26: <code>sed -i '/rotate/s/.*/rotate 26/' /etc/logrotate.d/audit</code></li>" >> "$REPORT_FILE"
;;
"审计日志轮转频率")
echo "<li>$check: 设置 daily 轮转: <code>sed -i '/weekly/s/.*/daily/' /etc/logrotate.d/audit</code></li>" >> "$REPORT_FILE"
;;
"审计日志轮转配置文件")
echo "<li>$check: 创建审计日志轮转配置: <code>echo '/var/log/audit/audit.log { daily rotate 26 }' > /etc/logrotate.d/audit</code></li>" >> "$REPORT_FILE"
;;
"审计日志配置文件")
echo "<li>$check: 检查文件权限: <code>touch /etc/audit/auditd.conf; chmod 640 /etc/audit/auditd.conf; chown root:root /etc/audit/auditd.conf</code></li>" >> "$REPORT_FILE"
;;
"文件完整性-"*)
local file=$(echo "$check" | cut -d'-' -f2-)
echo "<li>$check: 验证文件完整性: <code>$([ "$OS_NAME" = "centos" ] && echo "rpm -Vf $file" || echo "debsums -e $file")</code></li>" >> "$REPORT_FILE"
;;
"文件完整性工具")
echo "<li>$check: 安装 AIDE: <code>$([ "$OS_NAME" = "centos" ] && echo 'yum install aide -y' || echo 'apt install aide -y')</code></li>" >> "$REPORT_FILE"
;;
"账户安全-空密码")
if [ -n "$EMPTY_PASSWD_ACCOUNTS" ]; then
echo "<li>$check: 锁定空密码账户 ($EMPTY_PASSWD_ACCOUNTS): <code>echo '$EMPTY_PASSWD_ACCOUNTS' | xargs -n1 passwd -l</code></li>" >> "$REPORT_FILE"
else
echo "<li>$check: 锁定所有空密码账户: <code>awk -F: '(\$2 == \"\") {print \$1}' /etc/shadow | xargs -n1 passwd -l</code></li>" >> "$REPORT_FILE"
fi
;;
"账户安全-密码哈希")
echo "<li>$check: 升级密码哈希到 SHA512: <code>sed -i '/^ENCRYPT_METHOD/s/.*/ENCRYPT_METHOD SHA512/' /etc/login.defs; passwd [用户名]</code></li>" >> "$REPORT_FILE"
;;
"账户安全-锁定账户")
echo "<li>$check: 锁定非必要账户: <code>passwd -l [用户名]</code></li>" >> "$REPORT_FILE"
;;
"账户安全-超级用户")
echo "<li>$check: 移除非 root UID 0 账户: <code>awk -F: '(\$3 == 0 && \$1 != \"root\") {print \$1}' /etc/passwd | xargs -n1 userdel</code></li>" >> "$REPORT_FILE"
;;
"账户安全-passwd文件")
echo "<li>$check: 检查 /etc/passwd 权限: <code>chmod 644 /etc/passwd; chown root:root /etc/passwd</code></li>" >> "$REPORT_FILE"
;;
"账户安全-shadow文件")
echo "<li>$check: 检查 /etc/shadow 权限: <code>chmod 600 /etc/shadow; chown root:shadow /etc/shadow</code></li>" >> "$REPORT_FILE"
;;
"绝对高危端口")
if [ -n "$OPEN_PORTS" ]; then
echo "<li>$check: 关闭高危端口 ($OPEN_PORTS): <code>for port in $OPEN_PORTS; do firewall-cmd --permanent --remove-port=\$port/tcp; done; firewall-cmd --reload</code></li>" >> "$REPORT_FILE"
else
echo "<li>$check: 关闭高危端口: <code>firewall-cmd --permanent --remove-port={port}/tcp; firewall-cmd --reload</code></li>" >> "$REPORT_FILE"
fi
;;
"潜在高危端口")
if [ -n "$OPEN_PORTS" ]; then
echo "<li>$check: 限制潜在高危端口 ($OPEN_PORTS): <code>for port in $OPEN_PORTS; do firewall-cmd --permanent --add-rich-rule='rule family=\"ipv4\" source address=\"192.168.0.0/24\" port port=\"\$port\" protocol=\"tcp\" accept'; done; firewall-cmd --reload</code></li>" >> "$REPORT_FILE"
else
echo "<li>$check: 限制潜在高危端口: <code>firewall-cmd --permanent --add-rich-rule='rule family=\"ipv4\" source address=\"192.168.0.0/24\" port port=\"{port}\" protocol=\"tcp\" accept'; firewall-cmd --reload</code></li>" >> "$REPORT_FILE"
fi
;;
"防火墙状态")
echo "<li>$check: 启用防火墙: <code>$([ "$OS_NAME" = "centos" ] && echo 'systemctl start firewalld; systemctl enable firewalld' || echo 'ufw enable')</code></li>" >> "$REPORT_FILE"
;;
"端口检测工具")
echo "<li>$check: 安装 ss 或 netstat: <code>$([ "$OS_NAME" = "centos" ] && echo 'yum install net-tools -y' || echo 'apt install net-tools -y')</code></li>" >> "$REPORT_FILE"
;;
"口令加密算法")
echo "<li>$check: 设置 ENCRYPT_METHOD SHA512: <code>sed -i '/^ENCRYPT_METHOD/s/.*/ENCRYPT_METHOD SHA512/' /etc/login.defs</code></li>" >> "$REPORT_FILE"
;;
"默认账户-"*)
local account=$(echo "$check" | cut -d'-' -f2)
echo "<li>$check: 禁用或删除账户 $account: <code>passwd -l $account || userdel $account</code></li>" >> "$REPORT_FILE"
;;
"挂载选项-"*)
local path_opt=$(echo "$check" | cut -d'-' -f2-)
local path=$(echo "$path_opt" | cut -d'-' -f1)
local opt=$(echo "$path_opt" | cut -d'-' -f2)
if [ -n "$opt" ]; then
echo "<li>$check: 为 $path 添加 $opt 选项: <code>sed -i '/$path/s/defaults/defaults,$opt/' /etc/fstab; mount -o remount $path</code></li>" >> "$REPORT_FILE"
else
echo "<li>$check: 配置 $path 挂载: <code>echo '$path $path tmpfs defaults,noexec,nosuid 0 0' >> /etc/fstab; mount -o remount $path</code></li>" >> "$REPORT_FILE"
fi
;;
"审计规则-"*)
local rule_name=$(echo "$check" | cut -d'-' -f2-)
if [ -n "$MISSING_RULES" ]; then
echo "<li>$check: 添加缺失的审计规则 ($rule_name): <code>echo '$MISSING_RULES' >> /etc/audit/audit.rules; systemctl restart auditd</code></li>" >> "$REPORT_FILE"
else
echo "<li>$check: 添加审计规则: <code>echo '-w $rule_name -p wa -k ${rule_name}_changes' >> /etc/audit/audit.rules; systemctl restart auditd</code></li>" >> "$REPORT_FILE"
fi
;;
"审计规则文件")
echo "<li>$check: 检查审计规则文件权限: <code>touch /etc/audit/audit.rules; chmod 640 /etc/audit/audit.rules; chown root:root /etc/audit/audit.rules</code></li>" >> "$REPORT_FILE"
;;
"系统补丁")
echo "<li>$check: 更新系统补丁: <code>$([ "$OS_NAME" = "centos" ] && echo 'yum update -y' || echo 'apt update && apt upgrade -y')</code></li>" >> "$REPORT_FILE"
;;
*)
echo "<li>$check: 暂无具体建议,请手动检查配置</li>" >> "$REPORT_FILE"
;;
esac
fi
done
echo "</ul></body></html>" >> "$REPORT_FILE"
}
main() {
detect_os
init_report
check_account_files
check_password_policy
check_password_complexity
check_account_lockout_policy
check_three_roles_accounts
check_password_encryption
check_default_accounts
check_ssh_security
generate_statistics
echo "检测报告已生成:$REPORT_FILE"
echo "===== 合规检测结束: $(date +'%Y-%m-%d %H:%M:%S') =====" >> "$LOG_FILE"
}
main
452

被折叠的 条评论
为什么被折叠?



