简介:这是一份面向运维工程师、系统管理员及安全从业者的服务器安全巡检实操文档,围绕日常巡检的规范流程与落地方法展开,帮助读者建立可执行的服务器安全自查机制。文档以安全管理为主线,依次讲解巡检的重要性、关注点、覆盖内容、具体操作步骤以及异常管理与跟踪,并附有服务器巡检表模板,涵盖用户和组、系统服务、系统进程、防火墙、防病毒系统、系统日志、数据库、备份执行情况、磁盘权限、网站上传目录、FTP用户及权限、系统补丁升级等检查项,便于直接套用或按需裁剪。资源包内含1个docx文件,整体约163KB,采用A4打印与可编辑排版,适合打印成册或二次修改后作为团队巡检规范使用。目前已有164人学习下载,适合需要梳理巡检清单、规范操作流程或补齐安全管理短板的读者参考。
1. 服务器安全巡检到底在巡什么:从一次被忽略的日志说起
很多团队第一次认真做服务器安全巡检,都是被一次事故逼出来的。某公司内网一台对外提供接口的机器,磁盘告警没人管,运维重启后才发现/var/log里塞满了异常登录记录,而真正的入侵入口是一个三个月没改过密码、还开着 22 端口的测试账号。事后复盘,问题不是没有工具,而是没人把「巡检」当成一件有固定内容、固定方法、固定输出的事来做。
服务器安全巡检,说白了就是按一套清单,周期性地检查服务器的账号、权限、端口、进程、日志、补丁、文件完整性这些面,把「当前状态」和「应有状态」做比对,发现偏差就处理。它和渗透测试不是一回事:渗透是主动找漏洞,巡检是持续确认防线还在不在。适合谁?适合手里管着几台到几百台机器、没有专职安全团队、但又必须对线上负责的开发和运维。这篇就把巡检的内容清单和方法路径拆开讲,让你能照着搭一套自己的巡检流程。
2. 巡检内容清单:账号、端口、进程、日志四件套怎么查
巡检内容看着杂,其实可以收敛成四类:谁在系统里(账号与权限)、系统开了哪些门(端口与服务)、门后面在跑什么(进程与计划任务)、以及有没有人撬过门(日志与文件变更)。这四类覆盖了绝大多数日常风险,先把它们查透,再谈加固。
2.1 账号与权限:先找出不该存在的登录入口
账号巡检的核心是回答三个问题:有哪些可登录账号、哪些账号有 sudo 权限、哪些账号的密码或密钥长期没动。很多入侵留下的后门就是一个 UID 为 0 的隐藏账号,或者一个被塞进authorized_keys的公钥。
先看可登录账号。系统里 UID 小于 1000 的一般是系统账号,正常不应该有登录 shell。下面这条命令把 shell 不是 nologin/false 的账号列出来:
# 列出所有拥有可登录 shell 的账号 awk -F: '($3 >= 1000 || $3 == 0) && $7 !~ /(nologin|false)/ {print $1, $3, $7}' /etc/passwd # 单独检查 UID 为 0 的账号,正常只应有 root 一个 awk -F: '$3 == 0 {print $1}' /etc/passwd逻辑说明:/etc/passwd每行七个字段,$3是 UID,$7是登录 shell。第一条把 UID 大于等于 1000 或等于 0、且 shell 可登录的账号挑出来,这些是真正能进系统的入口。第二条专门盯 UID 为 0,因为多出来的 UID 0 账号几乎可以断定是后门。参数上,阈值 1000 是多数发行版普通用户的起始 UID,如果你用的是特殊发行版,先确认login.defs里的UID_MIN。
再看 sudo 权限。/etc/sudoers和/etc/sudoers.d/下的文件决定了谁能提权:
# 检查 sudoers 配置,过滤掉注释行 grep -vE '^\s*#|^\s*$' /etc/sudoers ls -l /etc/sudoers.d/ grep -rE 'NOPASSWD|ALL=' /etc/sudoers.d/ 2>/dev/nullNOPASSWD意味着免密提权,如果它出现在一个普通业务账号上,等于把 root 权限敞开了。巡检时要确认每一条 sudo 规则都有明确归属,找不到主人的规则就是风险。
最后是密钥和密码时效。检查authorized_keys里有没有陌生公钥,检查密码最后修改时间:
# 查看各账号密码最后修改时间(第二字段为天数,从1970-01-01算起) awk -F: '{print $1, $3}' /etc/shadow # 列出所有用户的 authorized_keys 及其内容指纹 find /home /root -name authorized_keys -exec ls -l {} \; 2>/dev/null/etc/shadow第三字段是最后一次改密码的天数,换算成日期就能看出哪些账号长期没改。常见做法是给巡检脚本设一个阈值,比如超过 90 天就标黄,超过 180 天标红。
2.2 端口与服务:把不该对外开的门关上
端口巡检要区分两件事:哪些端口在监听、监听在哪个地址。监听在0.0.0.0意味着所有网卡都能访问,监听在127.0.0.1则只限本机。很多数据库事故就是 MySQL 监听在0.0.0.0:3306且密码很弱。
# 查看所有监听端口及对应进程(需要 root 才能看到进程名) ss -tulnp # 只看对外监听(排除 127.0.0.1 和 ::1) ss -tuln | grep -vE '127\.0\.0\.1|::1'ss的-t是 TCP,-u是 UDP,-l是监听状态,-n是显示端口号而非服务名,-p显示进程。巡检时把这份输出和一份「已知服务白名单」比对,白名单外的监听端口都要问一句:这是谁开的、能不能关。
服务层面还要看开机自启项,因为有些后门会注册成服务:
# systemd 系统查看启用的服务 systemctl list-unit-files --type=service --state=enabled # 传统 init 系统查看自启脚本 ls -l /etc/rc*.d/ 2>/dev/null | grep -v '^total'常见做法是维护一份基线:新机器装好后记录一次 enabled 服务列表,之后巡检只报差异。这样比每次全量看要省力得多,也更容易发现异常新增。
2.3 进程与计划任务:找出手动藏起来的持久化
进程巡检的重点不是看 CPU 占用,而是找「不该在跑的」和「藏得很深的」。挖矿木马、反弹 shell 通常表现为一个名字奇怪、路径在/tmp或/dev/shm下的进程。
# 列出进程的可执行文件路径,重点看 /tmp /dev/shm /var/tmp ps -eo pid,user,comm,args | grep -E '/tmp|/dev/shm|/var/tmp' # 查看进程打开的网络连接,找出对外通信的可疑进程 ss -tnp | grep ESTABps -eo自定义输出列,args是完整命令行,比comm更能暴露真实路径。ss -tnp看已建立的 TCP 连接,如果某个陌生进程正连着外部 IP,基本可以确定有问题。
计划任务是另一个高频持久化点。cron 和 systemd timer 都要查:
# 查看所有用户的 crontab for u in $(cut -d: -f1 /etc/passwd); do crontab -l -u "$u" 2>/dev/null; done # 查看系统级 cron 目录 ls -l /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/ 2>/dev/null # 查看 systemd timer systemctl list-timers --all巡检时对每一条计划任务都要能说清用途。如果发现一条*/5 * * * * curl http://某地址 | sh这种,不用犹豫,直接按事件处理。
2.4 日志与文件完整性:确认有没有人撬过门
日志巡检解决的是「已经发生了什么」。重点看认证日志、sudo 日志和关键目录的文件变更。
# 查看最近登录失败记录(Debian/Ubuntu 系) grep 'Failed password' /var/log/auth.log | tail -50 # CentOS/RHEL 系 grep 'Failed password' /var/log/secure | tail -50 # 统计失败次数最多的来源 IP grep 'Failed password' /var/log/auth.log | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | headawk '{print $(NF-3)}'取的是倒数第四个字段,在标准 sshd 失败日志里正好是来源 IP。统计出来如果某个 IP 有几千次失败,说明正在被暴力破解,要么封 IP,要么直接改端口加密钥登录。
文件完整性靠基线比对。没有商业工具时,可以用find按时间筛出近期被改过的关键文件:
# 找出 /etc 下最近 7 天被修改过的文件 find /etc -type f -mtime -7 -ls 2>/dev/null # 找出 SUID 文件(提权后门常设 SUID) find / -perm -4000 -type f 2>/dev/null-mtime -7表示修改时间在 7 天以内,-perm -4000匹配 SUID 位。SUID 文件列表应该相对稳定,新增一个就要查来源。常见做法是把这两条命令的输出存成基线文件,巡检时用diff比对。
3. 巡检方法落地:从手动命令到可复现脚本
内容清单有了,接下来要解决「怎么持续做」。纯手动敲命令只能应付一两台机器,机器一多就必须脚本化。这一章讲怎么把上面的检查项组织成一个能定时跑、能出报告、能告警的巡检流程。
3.1 巡检脚本的结构:采集、比对、报告三段式
一个能长期用的巡检脚本,结构上分三段:采集原始数据、和基线比对、生成可读报告。不要把所有逻辑塞在一个大函数里,否则加一项检查就要动全身。
下面是一个最小骨架,用 bash 实现,采集部分按检查项拆成独立函数:
#!/bin/bash # 服务器安全巡检最小骨架 # 用法: ./inspect.sh [输出目录] OUT_DIR="${1:-/var/log/inspect}" TS=$(date +%Y%m%d_%H%M%S) REPORT="$OUT_DIR/report_$TS.txt" mkdir -p "$OUT_DIR" # 采集:账号检查 check_accounts() { echo "=== 可登录账号 ===" awk -F: '($3 >= 1000 || $3 == 0) && $7 !~ /(nologin|false)/ {print $1, $3, $7}' /etc/passwd echo "=== UID 0 账号 ===" awk -F: '$3 == 0 {print $1}' /etc/passwd } # 采集:端口检查 check_ports() { echo "=== 对外监听端口 ===" ss -tuln | grep -vE '127\.0\.0\.1|::1' } # 采集:SUID 文件 check_suid() { echo "=== SUID 文件 ===" find / -perm -4000 -type f 2>/dev/null } # 主流程 { echo "巡检时间: $(date)" echo "主机名: $(hostname)" check_accounts check_ports check_suid } > "$REPORT" 2>&1 echo "报告已生成: $REPORT"逻辑说明:每个check_*函数只负责采集一类数据并打印,主流程用花括号把标准输出和标准错误一起重定向到报告文件。这样加检查项只需新增一个函数并在主流程里调用。参数上,$1是输出目录,默认/var/log/inspect,建议放在有轮转策略的目录下,避免报告把磁盘写满。
3.2 基线比对:用 diff 找出「今天和昨天不一样」
采集只是第一步,真正有价值的是比对。把第一次(干净状态)的报告存为基线,之后每次巡检都和基线做 diff,差异部分就是需要人工确认的。
# 首次运行,生成基线 ./inspect.sh /var/log/inspect cp /var/log/inspect/report_*.txt /var/log/inspect/baseline.txt # 后续巡检后比对 diff /var/log/inspect/baseline.txt /var/log/inspect/report_最新.txtdiff的输出里,<开头是基线有、现在没有的,>开头是现在新增的。新增的监听端口、新增的 SUID 文件、新增的 UID 0 账号,这三类差异优先级最高。
不过直接 diff 全文会有噪音,因为报告里带了时间戳、进程 PID 这类每次都变的内容。常见做法是在采集阶段就把易变字段过滤掉,比如端口检查只保留端口号和监听地址,不保留 PID:
# 端口检查去掉 PID 列,只留协议、地址、端口 ss -tuln | awk 'NR>1 {print $1, $5}' | sort这样基线才稳定,diff 出来的才是真差异。这一步是很多自建巡检脚本翻车的地方——基线不稳定,天天报一堆假差异,最后没人看。
3.3 定时执行与告警:让巡检自己跑起来
脚本能跑通后,用 cron 或 systemd timer 定时执行。频率上,账号、端口、SUID 这类变化不频繁的,每天一次足够;日志类检查可以更频繁,但要注意日志轮转。
# 每天凌晨 3 点执行巡检,输出追加到日志 0 3 * * * /opt/inspect/inspect.sh /var/log/inspect >> /var/log/inspect/cron.log 2>&1告警部分,最简单的做法是巡检脚本在发现高危差异时返回非零退出码,再由 cron 的邮件机制或外部监控收走。更实用的做法是在脚本里直接判断:
# 检查是否有新增 UID 0 账号,有则告警 EXTRA_ROOT=$(awk -F: '$3 == 0 {print $1}' /etc/passwd | grep -v '^root$') if [ -n "$EXTRA_ROOT" ]; then echo "告警: 发现额外 UID 0 账号: $EXTRA_ROOT" >&2 exit 1 figrep -v '^root$'排除正常的 root,剩下的就是异常。退出码 1 会被 cron 捕获并触发邮件。参数上,告警阈值要按团队实际响应能力设,报得太多等于没报。
4. 避坑与排查:巡检脚本上线后最容易翻车的五个点
巡检脚本写完只是开始,真正折磨人的是上线后的各种意外。下面五条是我和身边同行踩过的坑,按「现象 → 原因 → 解决」写清楚。
现象一:报告每天都有大量差异,看不过来。原因:采集内容里混入了 PID、时间戳、临时文件路径这类每次都变的字段。解决:在采集阶段就用awk、sort把易变字段过滤掉,只保留稳定标识;基线生成后先手动跑三天,确认 diff 为空再正式启用。
现象二:脚本在测试机正常,到生产机报权限错误。原因:ss -p、读取/etc/shadow、find /都需要 root,而 cron 默认以普通用户执行。解决:把巡检脚本的 cron 任务配成 root 执行,或者用 sudo 精确授权需要的命令;不要图省事给脚本整体 SUID。
现象三:find /把磁盘 IO 打满,影响业务。原因:全盘扫描 SUID 文件在大磁盘上很重,如果和业务高峰重叠就会拖慢服务。解决:把find限制在关键目录(/usr、/bin、/sbin、/etc),或者用ionice降低优先级:ionice -c3 find / -perm -4000。
现象四:日志检查报「文件不存在」。原因:不同发行版日志路径不同,Debian 系是/var/log/auth.log,RHEL 系是/var/log/secure,还有的用 journald。解决:脚本里先判断文件是否存在,不存在就回退到journalctl:
if [ -f /var/log/auth.log ]; then LOG=/var/log/auth.log elif [ -f /var/log/secure ]; then LOG=/var/log/secure else journalctl -u sshd --since "1 day ago" > /tmp/sshd_tmp.log LOG=/tmp/sshd_tmp.log fi现象五:基线被误更新,之后所有差异都检测不到。原因:有人图省事,每次巡检后直接把最新报告覆盖成基线,等于把「异常状态」当成了「正常状态」。解决:基线更新必须人工确认,且只在确认差异都是合理变更后才执行;把基线文件设为只读,或者放到需要额外权限才能写的目录。
5. 进阶技巧:把巡检结果变成可追溯的安全档案
巡检做到后面,真正的价值不在于「今天发现了什么」,而在于「过去三个月这台机器的安全状态是怎么变化的」。把每次巡检结果结构化存储,就能回答「这个端口是什么时候开的」「这个账号是什么时候出现的」这类追溯问题。
一个轻量做法是把报告转成 CSV,每次巡检追加一行,字段固定为:日期、主机名、可登录账号数、对外监听端口数、SUID 文件数、新增差异数。这样用表格软件就能画出趋势,异常增长一眼可见。
# 从报告里提取关键指标,追加到 CSV REPORT=$(ls -t /var/log/inspect/report_*.txt | head -1) DATE=$(date +%F) HOST=$(hostname) ACC=$(awk '/可登录账号/,/UID 0/' "$REPORT" | grep -cE '^[a-z]') PORT=$(awk '/对外监听端口/,/SUID/' "$REPORT" | grep -cE '^(tcp|udp)') SUID=$(awk '/SUID 文件/,0' "$REPORT" | grep -c '/') echo "$DATE,$HOST,$ACC,$PORT,$SUID" >> /var/log/inspect/trend.csv逻辑说明:awk '/起始标记/,/结束标记/'截取报告里对应段落,grep -c统计行数。参数上,起始和结束标记要和报告里的标题文字完全一致,改报告格式时记得同步改这里。这个 CSV 积累几个月后,配合简单的折线图,就能看出哪些指标在缓慢漂移。
再进一步,可以把巡检和配置管理工具结合。比如用 Ansible 批量拉取所有机器的巡检报告,集中比对;或者把基线文件纳入版本控制,每次基线变更都有 commit 记录,谁改的、为什么改,一目了然。这一步的门槛不高,但能让巡检从「个人习惯」变成「团队资产」。
我自己的习惯是:每次处理完一个巡检告警,都在基线文件的 commit message 里写清楚原因,比如「开放 8080 给新服务,已确认」。半年后回头看,这份记录比任何文档都管用。巡检这件事,工具只是辅助,真正靠的是把「确认异常」和「更新基线」这两个动作坚持做下去。希望帮到你。
本文还有配套的精品资源,点击获取