🚨 应急响应 — 日志分析找出攻击时间线

从日志中还原攻击故事

📖 应急响应框架

应急响应(Incident Response, IR)是安全事件发生后的一系列标准化处理流程。NIST SP 800-61定义了四阶段模型,SANS则使用六阶段PICERL模型。

应急响应生命周期 (PICERL): 1. Preparation (准备) ├── IR团队组建 ├── 工具和流程准备 ├── 演练和培训 └── 通信渠道建立 2. Identification (识别) ← 本课重点 ├── 事件检测 ├── 日志收集 ├── 攻击时间线构建 └── 影响评估 3. Containment (遏制) ├── 短期遏制(隔离) ├── 长期遏制(监控) └── 证据保全 4. Eradication (根除) ├── 恶意软件清除 ├── 后门移除 └── 漏洞修复 5. Recovery (恢复) ├── 系统恢复 ├── 验证测试 └── 监控加强 6. Lessons Learned (总结) ├── 时间线文档化 ├── 改进措施 └── 流程更新

🔬 日志分析实战

Linux日志分析

# 关键日志文件
/var/log/auth.log     # 认证日志(Debian/Ubuntu)
/var/log/secure       # 认证日志(RHEL/CentOS)
/var/log/syslog       # 系统日志
/var/log/kern.log     # 内核日志
/var/log/apache2/     # Web服务器日志
/var/log/audit/       # auditd审计日志

# 1. 分析登录行为
# 成功的SSH登录
grep "Accepted" /var/log/auth.log
# Jan 15 03:42:11 server sshd[12345]: Accepted password for root from 185.x.x.x port 54321 ssh2
#                                               ↑ 异常!凌晨3点root远程登录

# 失败的SSH登录(暴力破解指标)
grep "Failed password" /var/log/auth.log | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head
#   1247 185.220.101.x  ← 大量失败尝试!
#    324 103.75.x.x
#     12 10.0.0.15      ← 内网IP也出现!

# 统计每日登录趋势
grep "Accepted" /var/log/auth.log | awk '{print $1,$2}' | sort | uniq -c
#   45 Jan 15
#    2 Jan 16           ← 登录次数骤降,可能被劫持

# 2. 分析sudo使用
grep "sudo" /var/log/auth.log
# Jan 15 03:45:22 server sudo: user1 : TTY=pts/0 ; PWD=/home/user1 ; USER=root ; COMMAND=/bin/bash
#                                                                      ↑ 恶意: 普通用户获取root shell

# 3. 分析用户切换
grep "su:" /var/log/auth.log
# Jan 15 03:46:01 server su: (to root) user1 on pts/0

# 4. 新用户创建
grep "useradd\|adduser" /var/log/auth.log
# Jan 15 03:50:11 server useradd[1234]: new user: name=backdoor, UID=1002

# 5. auditd日志深度分析
# 搜索特定事件
ausearch -m USER_LOGIN -sv no  # 登录失败
ausearch -m USER_ACCT           # 账户活动
ausearch -k ssh_config          # SSH配置变更

# 时间范围搜索
ausearch -ts 01/15/2024 03:00:00 -te 01/15/2024 05:00:00

Web日志分析

# Apache/Nginx访问日志分析
# 格式: IP - - [时间] "方法 URL 协议" 状态码 大小

# 1. 统计访问量TOP IP
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20

# 2. 搜索SQL注入尝试
grep -iE "(union.*select|or.*1=1|--|;--|drop.*table|char\(|0x)" /var/log/nginx/access.log

# 3. 搜索目录遍历
grep -E "\.\./\.\./" /var/log/nginx/access.log

# 4. 搜索Web Shell访问
grep -iE "(cmd=|exec=|shell=|c99|r57|webshell|b374k|WSO)" /var/log/nginx/access.log

# 5. 异常HTTP方法
grep -E "\"(PUT|DELETE|TRACE|OPTIONS|CONNECT)" /var/log/nginx/access.log

# 6. 大量404扫描
awk '$9 == 404 {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head

# 7. 攻击时间线构建
# 提取特定IP的所有活动
grep "185.220.101.x" /var/log/nginx/access.log | awk '{print $4,$7,$9}' | sort
# [15/Jan/2024:03:40:01] /login.php 200
# [15/Jan/2024:03:40:05] /login.php 401  ← 登录失败
# [15/Jan/2024:03:40:08] /login.php 401
# [15/Jan/2024:03:42:11] /login.php 200  ← 登录成功!
# [15/Jan/2024:03:42:15] /admin/dashboard 200  ← 访问管理后台
# [15/Jan/2024:03:45:22] /admin/users.php 200  ← 查看用户列表
# [15/Jan/2024:03:50:01] /admin/backup.tar.gz 200 ← 下载数据!

📊 攻击时间线构建

#!/usr/bin/env python3
"""
攻击时间线构建器
从多个日志源聚合事件,生成时间线
"""
import re
from datetime import datetime
from collections import defaultdict

class TimelineBuilder:
    def __init__(self):
        self.events = []
    
    def parse_auth_log(self, filepath):
        """解析Linux auth.log"""
        patterns = {
            'login_success': r'Accepted (\w+) for (\w+) from ([\d.]+)',
            'login_fail': r'Failed (\w+) for (?:invalid user )?(\w+) from ([\d.]+)',
            'sudo': r'sudo:\s+(\w+)\s+:.*COMMAND=(.+)',
            'user_add': r'new user: name=(\w+)',
        }
        
        with open(filepath) as f:
            for line in f:
                # 提取时间戳
                ts_match = re.match(r'(\w+\s+\d+\s+\d+:\d+:\d+)', line)
                if not ts_match:
                    continue
                timestamp = datetime.strptime(
                    ts_match.group(1), '%b %d %H:%M:%S'
                ).replace(year=datetime.now().year)
                
                for event_type, pattern in patterns.items():
                    match = re.search(pattern, line)
                    if match:
                        event = {
                            'timestamp': timestamp,
                            'type': event_type,
                            'details': match.groups(),
                            'raw': line.strip()
                        }
                        self.events.append(event)
    
    def parse_web_log(self, filepath):
        """解析Web访问日志"""
        pattern = r'([\d.]+) - - \[(.+?)\] "(\w+) (.+?) .+?" (\d+) (\d+)'
        
        with open(filepath) as f:
            for line in f:
                match = re.match(pattern, line)
                if match:
                    ip, ts_str, method, url, status, size = match.groups()
                    timestamp = datetime.strptime(
                        ts_str.split()[0], '%d/%b/%Y:%H:%M:%S'
                    )
                    event = {
                        'timestamp': timestamp,
                        'type': 'web_request',
                        'details': (ip, method, url, int(status)),
                        'raw': line.strip()
                    }
                    self.events.append(event)
    
    def build_timeline(self):
        """构建排序的时间线"""
        self.events.sort(key=lambda e: e['timestamp'])
        return self.events
    
    def detect_anomalies(self):
        """检测异常事件"""
        anomalies = []
        ip_login_attempts = defaultdict(int)
        
        for event in self.events:
            if event['type'] == 'login_fail':
                ip = event['details'][2]
                ip_login_attempts[ip] += 1
                if ip_login_attempts[ip] > 10:
                    anomalies.append({
                        'timestamp': event['timestamp'],
                        'type': 'brute_force',
                        'ip': ip,
                        'attempts': ip_login_attempts[ip]
                    })
            
            if event['type'] == 'login_success':
                ts = event['timestamp']
                if ts.hour < 6 or ts.hour > 22:
                    anomalies.append({
                        'timestamp': ts,
                        'type': 'off_hours_login',
                        'user': event['details'][1],
                        'ip': event['details'][2]
                    })
        
        return anomalies

# 使用示例
tl = TimelineBuilder()
tl.parse_auth_log('/var/log/auth.log')
tl.parse_web_log('/var/log/nginx/access.log')
timeline = tl.build_timeline()

print("=== 攻击时间线 ===")
for event in timeline:
    print(f"[{event['timestamp']}] {event['type']}: {event['details']}")

print("\n=== 异常检测 ===")
for anomaly in tl.detect_anomalies():
    print(f"[{anomaly['timestamp']}] {anomaly['type']}: {anomaly}")

🔍 Windows事件日志分析

# 关键Windows安全事件ID
# 4624 - 成功登录
# 4625 - 登录失败
# 4634 - 注销
# 4648 - 显式凭据登录(RunAs)
# 4672 - 特权登录(管理员)
# 4720 - 创建用户
# 4728 - 添加到全局组
# 4732 - 添加到本地组
# 4740 - 账户锁定
# 4768 - Kerberos TGT请求
# 4769 - Kerberos TGS请求
# 4776 - NTLM认证
# 7045 - 新服务安装
# 1102 - 审计日志清除(极其可疑!)

# PowerShell查询
# 查找所有成功登录
Get-WinEvent -FilterHashtable @{LogName='Security';ID=4624} |
  Select-Object TimeCreated,
    @{N='User';E={$_.Properties[5].Value}},
    @{N='SourceIP';E={$_.Properties[18].Value}},
    @{N='LogonType';E={$_.Properties[8].Value}} |
  Where-Object {$_.LogonType -eq 10}  # 远程交互式登录

# 查找异常服务安装
Get-WinEvent -FilterHashtable @{LogName='System';ID=7045} |
  Select-Object TimeCreated,
    @{N='Service';E={$_.Properties[0].Value}},
    @{N='Path';E={$_.Properties[1].Value}}

# 查找日志清除
Get-WinEvent -FilterHashtable @{LogName='Security';ID=1102}
# 如果发现日志被清除 → 攻击者试图掩盖踪迹!

# 使用Sigma规则检测
# https://github.com/SigmaHQ/sigma
# 自动化日志分析

🛠️ 日志分析工具

工具类型适用场景
ELK Stack集中日志管理企业级日志聚合分析
SplunkSIEM安全事件关联分析
jqJSON处理快速过滤JSON日志
lnav日志查看多格式日志实时查看
Sigma规则框架通用检测规则
Chainsaw快速分析Windows EVTX快速搜索
HayabusaWindows日志快速事件响应分析
# jq - JSON日志分析利器
# 分析CloudTrail日志
cat cloudtrail.json | jq '.Records[] | 
  select(.eventName == "ConsoleLogin") | 
  {time: .eventTime, user: .userIdentity.arn, ip: .sourceIPAddress}'

# 统计API调用频率
cat cloudtrail.json | jq -r '.Records[].eventName' | sort | uniq -c | sort -rn | head

# lnav - 日志导航
lnav /var/log/               # 自动检测格式
# 在lnav中:
# :filter-in sshd            # 过滤SSH日志
# :highlight Failed          # 高亮失败登录

# Chainsaw - Windows EVTX快速搜索
chainsaw search "4624" evtx_files/ --regex
chainsaw hunt evtx_files/ --rules sigma/

🛡️ 日志管理最佳实践

# 1. 集中式日志管理
# 使用rsyslog远程日志
cat > /etc/rsyslog.d/remote.conf << 'EOF'
*.* @@log-server:514     # TCP发送到日志服务器
EOF

# 2. 日志完整性保护
# 使用TLS加密传输
# 使用签名验证日志未被篡改

# 3. 日志保留策略
# 安全日志: 至少1年
# 认证日志: 至少90天
# Web日志: 至少30天
# 合规要求: PCI DSS要求1年

# 4. auditd关键规则
cat > /etc/audit/rules.d/audit.rules << 'EOF'
# 认证事件
-w /var/log/auth.log -p wa -k auth
-w /etc/ssh/sshd_config -p wa -k ssh_config

# 用户/组变更
-w /etc/passwd -p wa -k passwd_changes
-w /etc/group -p wa -k group_changes
-w /etc/shadow -p wa -k shadow_changes

# 特权命令
-a always,exit -F arch=b64 -S execve -F uid=0 -k root_cmds

# 网络配置变更
-w /etc/hosts -p wa -k hosts_changes
-a always,exit -F arch=b64 -S sethostname -k hostname_changes

# 不可变规则(防止auditd自身被禁用)
-e 2
EOF

service auditd restart
日志分析找出攻击时间线 — 掌握应急响应的核心分析技能!你能分析Linux/Windows/Web日志,构建攻击时间线,检测暴力破解、异常登录、持久化等行为。
命令已验证:grep / awk / ausearch / jq / lnav / chainsaw — 所有命令在Ubuntu 22.04环境测试通过
思考题:
  1. 为什么事件ID 1102(审计日志清除)是高优先级告警?攻击者为什么需要清除日志?
  2. 如何判断一次登录是合法还是恶意?需要结合哪些上下文?
  3. 集中式日志管理如何防御攻击者篡改本地日志?
  4. 日志量过大时如何优化分析效率?

📚 延伸阅读

参考资料:NIST SP 800-61r2 | SANS PICERL Model | Sigma Project | MITRE ATT&CK | ELK Documentation | auditd Man Page