一次服务器暴露面收敛Checklist:从端口扫描到凭据治理的落地清单

把暴露面收敛拆成资产盘点、端口收敛、身份凭据、云侧边界、持续扫描五步 Checklist,避免公网裸奔与密钥外泄再发生。

问题背景

近几年我们连续踩过几类「看起来不相关、根因都是暴露面没收住」的坑:公网 Elasticsearch 9200 裸奔被 Meow 类脚本删库、云账号 AccessKey 硬编码进公开仓库被 11 分钟捡走挖矿、Web 上传接口只信前端后缀被种 Webshell、临时联调安全组 0.0.0.0/0 放完忘记回收。每一起单独复盘都能写一篇排查文,但复盘越多越发现——真正缺的不是又一个应急手册,而是上线前就能挡住裸奔的收敛清单

这篇不再还原某一场事故的时间线,而是把过去半年处置经验压成一份可勾选的 服务器暴露面收敛 Checklist。对象覆盖:Linux/Windows 主机、容器节点、云安全组/NSG、数据库与中间件监听面、运维入口(SSH/RDP/堡垒机)、静态密钥与临时凭证。目标很具体:任何一台新机器、任何一个新端口、任何一把新密钥,上线前都能在清单上找到对应勾选项;历史资产也能用同一套清单做季度收敛。

故障现象

暴露面失控通常不会以「安全事件」的名义出现,而是以下列「业务/运维症状」伪装登场:

  1. 账单异常先于告警:境外地域凭空多出高配实例、GPU 矿机,费用曲线夜间断崖上翘,业务监控却一片绿。
  2. 数据组件忽然只剩勒索索引:ES / Mongo / Redis / Kafka 管理口对公网开放,脚本化扫描器 24 小时内完成发现→删库→勒索索引三连。
  3. 主机出现 UID 0 影子账户与异常外联:应用低权限账户却能通过 sudoers 提权,或上传目录同源可执行导致 Webshell 落地后直接出网。
  4. 临时放行永久生效:联调同学开了 0.0.0.0/0:9200/3306/6379/22,工单关了规则还在,九十天后某次全网扫描直接命中。
  5. 「内网服务」其实在公网:NLB/CLB 误绑公网 IP、K8s Service type=LoadBalancer 没走内网注解、NAT 网关 SNAT 表把管理口映出去——资产台账里写着内网,扫描器从公网就能打到。

共同特征只有一句:攻击面比资产台账大,而台账又比监控面大。下面用一次模拟演练场景(对照真实处置经验)把 Checklist 跑通。

排查过程

1. 先画「谁在听、谁能连」两张图

不做资产盘点就谈收敛,等于盲人摸象。我们用三层命令把「真实监听面」拉出来,和 CMDB/云厂商资产清单对账:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 主机层:所有对外监听(含 0.0.0.0 / :: / 内网多网卡)
ss -lntup
# 或
netstat -lntup

# 进程归属:哪个二进制占了这个端口
lsof -iTCP -sTCP:LISTEN -P -n

# 云侧:安全组/NSG 入站 0.0.0.0/0 规则(以阿里云 CLI 为例,其他云换对应命令)
aliyun ecs DescribeSecurityGroupAttribute --SecurityGroupId sg-xxx \
  | jq '.Permissions.Permission[] | select(.SourceCidrIp=="0.0.0.0/0")'

对账结果常见三类漂移:

漂移类型 典型表现 风险
台账有、实际无 CMDB 记着 8080,进程早下线 低,但干扰优先级
实际有、台账无 临时 NodePort / 调试端口未登记 ,盲区
规则宽、业务窄 安全组放 0-65535,应用只听 443 ,横向面过大

容器节点还要多查一层:

1
2
3
4
kubectl get svc -A -o wide | awk '$5 ~ /LoadBalancer|NodePort/'
kubectl get networkpolicy -A
# CNI 层真实放行(Calico 示例)
calicoctl get gnp,np -o yaml | grep -E 'cidr:|protocol:|ports:'

2. 端口收敛:默认拒绝,按业务建白名单

收敛顺序建议 从外到内:公网入口 → 跨 VPC/专线 → 同 VPC 子网 → 主机本地防火墙。原则:

  • 公网只留 443(及必要的 80 跳转);SSH/RDP/数据库/缓存/消息队列 一律禁止对 0.0.0.0/0
  • 运维入口收敛到 堡垒机 / VPN / 零信任网关 之后,源 IP 写成堡垒机出口段,而不是「公司出口 + 家庭宽带 + 个人 4G」。
  • 数据库与中间件监听绑定 内网网卡或 127.0.0.1 + 本地 socket,禁止直接绑 0.0.0.0 再靠安全组兜底——安全组被误改时,本地绑定是最后一道闸。
  • 容器环境优先用 ClusterIP + Ingress,慎用 NodePort;必须用 LoadBalancer 时强制内网注解(各云字段不同,写入变更模板)。

主机防火墙示例(firewalld,策略与云安全组对齐,双保险):

1
2
3
4
firewall-cmd --permanent --set-default-zone=drop
firewall-cmd --permanent --zone=public --add-service=https
firewall-cmd --permanent --zone=public --add-rich-rule='rule family=ipv4 source address=10.20.0.0/16 port port=22 protocol=tcp accept'
firewall-cmd --reload

Windows 对等动作:Get-NetFirewallRule 清点 + GPO 下发「禁止高危端口入站」,RDP 仅放行堡垒机网段。

3. 身份与凭据:比关端口更难、也更致命

端口收敛解决的是「门开没开」;凭据治理解决的是「钥匙有几把、谁还握着」。对照真实事故,清单里这几项一项不能少:

  1. 静态长期密钥清零目标:云 AK/SK、对象存储签名密钥、镜像仓库 robot token、CI PAT——默认生命周期 ≤ 90 天,能上 STS/实例角色的全部上。
  2. 代码与镜像禁硬编码:pre-commit + CI 强制 gitleaks/trufflehog;历史仓库做过一次 git filter-repo 的,要默认「仍按已泄露处置」轮换全部命中密钥。
  3. sudoers / 本地管理员最小化:禁止 NOPASSWD: ALL,禁止给 www-data/nginx 配 find/tar/vim/python 等 GTFOBins 高危组合;Windows 本地 Administrators 组成员季度审计。
  4. SSH 密钥与 authorized_keys 基线:禁用密码登录、禁用 root 直登;authorized_keys 纳入 FIM(AIDE/OSSEC/自研);跳板机登录全量审计。
  5. 应用密钥进密钥管理系统:HashiCorp Vault / 云 KMS / 密封 secrets,禁止 .env 进镜像层。

快速体检命令:

1
2
3
4
5
6
7
8
9
# 机器上是否残留明文密钥文件(示例,按环境改路径)
grep -RInE 'AKIA[0-9A-Z]{16}|LTAI[0-9A-Za-z]{12,}|BEGIN (RSA |OPENSSH )?PRIVATE KEY' \
  /home /opt /var/www /root 2>/dev/null | head

# sudo 高危组合
grep -RInE 'NOPASSWD|ALL\s*=\s*\(.*\)\s*ALL' /etc/sudoers /etc/sudoers.d/ 2>/dev/null

# SSH 配置基线
sshd -T | grep -E 'passwordauthentication|permitrootlogin|pubkeyauthentication'

4. 云侧边界与「以为内网」的坑

安全组/NSG 之外,还有三类高发误配:

  • 负载均衡误绑公网:内网 CLB 创建时点错「公网」,或后续被变更成公网 IP。
  • 对象存储桶策略过宽Principal: * + s3:GetObject 组合等于公开读;再叠预签名 URL 长期有效就更糟。
  • 安全组引用安全组 形成隐式大环:A 放行 B,B 放行 C,C 对公网 22 开放——人眼 ban 单组时看不出链路。

建议每月导出全量安全组做离线分析,用脚本标红:

  • 入站源为 0.0.0.0/0::/0 且端口 ∈ {22, 3389, 3306, 5432, 6379, 9200, 27017, 9092, 2379, 6443}
  • 出站完全放开且实例角色权限过大(可被 SSRF 换成凭证盗用)

5. 持续扫描:收敛不是项目,是节奏

一次性收敛做完,三个月后必然回潮。最低可行节奏:

频率 动作 工具/产出
每日 公网高危端口变化 diff masscan/nmap + 基线对比;或云厂商「暴露面」产品
每周 新增 0.0.0.0/0 规则工单抽检 云配置审计 / Config
每月 全量监听端口 vs CMDB 对账 主机 Agent 采集 + 报表
每季 密钥轮换与权限右转 RAM/IAM 权限分析 + 密钥年龄报表
每半年 红队/外部扫描复测 第三方或自建漏洞扫描

告警必须「可行动」:只报「发现 9200 对公网开放」不够,要带 实例 ID、安全组规则 ID、首次发现时间、负责人、工单链接

解决方案

把上面过程固化成上线门禁与存量治理两份清单。

A. 新资产上线门禁 Checklist(未勾完禁止投产)

  • 资产登记:主机/容器集群/中间件实例写入 CMDB,含负责人、环境、是否允许公网
  • 监听面最小:应用只绑必要网卡与端口;管理口不绑 0.0.0.0
  • 安全组默认拒绝:入站白名单精确到源网段 + 端口;无 0.0.0.0/0 高危端口
  • 运维入口:SSH/RDP 仅堡垒机网段;已禁用密码登录与 root 直登
  • 主机防火墙:与安全组同策略双保险(防安全组被误改)
  • 身份最小权限:实例角色 / 进程账户无多余 sudo;无 GTFOBins 高危组合
  • 密钥来源:无硬编码 AK/SK;CI/CD 用 OIDC 或短时令牌;镜像无 .env 密钥层
  • 日志与审计:登录审计、安全组变更审计、高危 API 调用告警已接值班通道
  • 备份与恢复点:数据组件有最近一次成功备份,且恢复演练记录未过期
  • 扫描通过:上线前外部扫描 + 内部监听对账均无新增高危项

B. 存量资产季度收敛 Checklist

  • 导出全网 0.0.0.0/0 入站规则,高危端口清零或改堡垒机源
  • 全量 ss -lntup / 云负载均衡监听 与 CMDB 对账,盲区端口下线或补登记
  • 数据库/缓存/消息队列管理口确认不可从公网与非业务网段直达
  • 长期 AK/SK、robot token、CI PAT 年龄 > 90 天全部轮换
  • 公开仓库与历史 commit 再扫一轮密钥(含 fork 与镜像站)
  • sudoers、本地管理员组、authorized_keys 抽样 100% + 全量自动化基线
  • K8s:清点 NodePort/LoadBalancer/无主 Ingress;NetworkPolicy 是否从 default-allow 误收到不可用(回滚预案必备)
  • 对象存储桶策略与 ACL 扫描,公开读/写清零
  • 临时联调规则自动过期机制是否生效(TTL 标签或云防火墙定时策略)
  • 复测报告归档,遗留项录入风险台账并设到期日

C. 发现「已裸奔」时的 30 分钟止血序

  1. 先断后查:安全组/NSG/防火墙立即收口,保留只读镜像/快照用于取证。
  2. 判定是否失陷:看操作审计与主机外联,区分「仅暴露」与「已被利用」。
  3. 轮换凭据:凡是这台机器/这个组件能摸到的密钥,默认按已泄露轮换。
  4. 数据完整性:核心库做只读快照,对比勒索索引/异常 drop。
  5. 复盘进清单:把本次漏网项回写到门禁 Checklist,避免同类第三次出现。

根因分析

暴露面失控很少是「不会配安全组」这么简单,深层通常是四洞叠加:

  1. 责任缝隙:应用只关心服务起来,网络只关心通,安全只关心扫描器报表——没有人在上线门禁处做最终勾选。
  2. 临时性没有生命周期:联调规则、调试端口、个人 AK,创建时都有正当理由,却没有 TTL 与自动回收。
  3. 控制面单一:只信云安全组,主机防火墙空置;或只信主机 iptables,容器 NodePort 从旁路露出。
  4. 检测滞后于攻击自动化:扫描器与盗钥机器人是分钟级,人工季度巡检是季度级,窗口期天然留给攻击者。

所以 Checklist 的本质不是文档,是 把「默认拒绝 + 最小权限 + 短生命周期 + 持续对账」写进发布流程的强制闸门

预防措施

  1. 发布平台强制校验:CI 流水线集成 gitleaks;CD 创建安全组前校验规则模板,拒绝高危 0.0.0.0/0
  2. 安全组即代码:Terraform/Pulumi 管理,PR 必经两人审批;控制台手工改动由配置审计告警并自动漂移纠正。
  3. 密钥默认短时:云上业务优先实例 RAM 角色 / Pod IRSA / OIDC 联邦,静态 AK 走例外审批且 90 天必轮换。
  4. 暴露面日更大盘:每日公网端口 diff 进值班群,新增项 T+0 认领、T+1 闭环。
  5. 临时规则 TTL:所有「临时放行」必须带过期时间,到期自动删;无 TTL 的变更单直接打回。
  6. 双层防御:云安全组 + 主机防火墙 +(容器)NetworkPolicy,任一层被误改另两层仍可兜住。
  7. 桌面与服务器同一套思想:运维笔记本的 RDP/SMB/WinRM、跳板机、堡垒机会话,一并纳入季度收敛,避免「服务器收了、人的电脑还是跳板」。
  8. 演练:每半年做一次「故意裸奔再收敛」桌面推演,验证清单是否可执行、告警是否会响、职责是否清晰。

总结

服务器被打穿的叙事里,0day 很少,忘关的端口、忘删的规则、忘轮换的密钥 才是主角。把暴露面收敛写成 Checklist,是为了把分散在 ES 裸奔、AK 挖矿、Webshell、sudo 提权等多篇事故里的共性动作,收成上线前能勾、季度能复用、出事能止血的同一套动作。

落地时不必追求一次完美:先上「新资产门禁」十条,再跑一个季度「存量高危 0.0.0.0/0 清零」,同时把密钥扫描嵌进 CI。等这三件事稳定,再叠加日更扫描大盘与安全组即代码。暴露面不会归零,但可以逼近「攻击者自动化打不进、打进了分钟级能发现、发现了有清单可执行」——这比再写一篇漂亮的事后复盘,值钱得多。

使用 Hugo 构建
主题 StackJimmy 设计