从日志中还原攻击故事
应急响应(Incident Response, IR)是安全事件发生后的一系列标准化处理流程。NIST SP 800-61定义了四阶段模型,SANS则使用六阶段PICERL模型。
# 关键日志文件
/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
# 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安全事件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 | 集中日志管理 | 企业级日志聚合分析 |
| Splunk | SIEM | 安全事件关联分析 |
| jq | JSON处理 | 快速过滤JSON日志 |
| lnav | 日志查看 | 多格式日志实时查看 |
| Sigma | 规则框架 | 通用检测规则 |
| Chainsaw | 快速分析 | Windows EVTX快速搜索 |
| Hayabusa | Windows日志 | 快速事件响应分析 |
# 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