| name | performing-linux-log-forensics-investigation |
| description | 对 Linux 系统日志(包括 syslog、auth.log、systemd journal、kern.log 和应用程序日志)进行取证调查,以重建用户活动、检测未授权访问并在被入侵的 Linux 系统上建立事件时间线。 |
| domain | cybersecurity |
| subdomain | digital-forensics |
| tags | ["linux-forensics","syslog","auth-log","systemd-journal","journalctl","linux-logs","ssh-forensics","cron","audit-log","log-analysis"] |
| version | 1.0 |
| author | mahipal |
| license | Apache-2.0 |
执行 Linux 日志取证调查
概述
Linux 系统维护着大量日志,是取证调查的主要证据来源。与 Windows 事件日志不同,Linux 日志通常是存储在 /var/log/ 中的纯文本文件和由 systemd-journald 管理的二进制 journal 文件。关键取证日志包括:auth.log(认证事件、sudo 使用、SSH 会话)、syslog(系统级消息)、kern.log(内核事件)以及特定应用程序日志。Linux 审计框架(auditd)提供了与 Windows 安全事件日志相当的详细安全事件记录。对这些日志的取证分析使调查人员能够重建用户会话、识别未授权访问、检测权限提升、追踪横向移动并建立完整的事件时间线。
关键日志文件及位置
| 日志文件 | 路径 | 内容 |
|---|
| auth.log / secure | /var/log/auth.log(Debian)或 /var/log/secure(RHEL) | 认证、sudo、SSH、PAM |
| syslog / messages | /var/log/syslog(Debian)或 /var/log/messages(RHEL) | 通用系统消息 |
| kern.log | /var/log/kern.log | 内核消息、USB 事件、驱动加载 |
| lastlog | /var/log/lastlog | 每用户最后登录记录(二进制) |
| wtmp | /var/log/wtmp | 登录/注销记录(二进制,用 last 读取) |
| btmp | /var/log/btmp | 登录失败记录(二进制,用 lastb 读取) |
| faillog | /var/log/faillog | 登录失败计数器(二进制) |
| cron.log | /var/log/cron 或 /var/log/syslog | 计划任务执行记录 |
| audit.log | /var/log/audit/audit.log | Linux 审计框架事件 |
| journal | /var/log/journal/ 或 /run/log/journal/ | systemd 二进制 journal |
| dpkg.log | /var/log/dpkg.log | 软件包安装/删除记录(Debian) |
| yum.log | /var/log/yum.log | 软件包安装/删除记录(RHEL) |
分析技术
认证日志分析
grep "Accepted" /var/log/auth.log
grep "Failed password" /var/log/auth.log
grep "Failed password" /var/log/auth.log | grep -oP '\d+\.\d+\.\d+\.\d+' | sort -u
grep "sudo:" /var/log/auth.log | grep "COMMAND"
grep "Failed password" /var/log/auth.log | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -20
grep "useradd\|adduser" /var/log/auth.log
grep "Accepted publickey" /var/log/auth.log
Systemd Journal 分析
journalctl --output=json --no-pager > journal_export.json
journalctl --since "2025-02-01" --until "2025-02-15" --output=json > timerange.json
journalctl -u sshd --output=json > sshd_journal.json
journalctl -k --output=json > kernel_journal.json
journalctl -p err --output=json > errors.json
journalctl -b 0 --output=json > current_boot.json
journalctl --list-boots
Linux 审计框架分析
ausearch -m USER_AUTH --start today
ausearch -f /etc/shadow
ausearch -m EXECVE --start "02/01/2025" --end "02/28/2025"
aureport --login --start "02/01/2025"
aureport --auth --failed
ausearch -ua 1001
ausearch -ua username
Cron 任务调查
cat /etc/crontab
ls -la /var/spool/cron/crontabs/
grep "CRON" /var/log/syslog
ls -la /var/spool/at/
atq
Python 取证日志解析器
import re
import json
import sys
import os
from datetime import datetime
from collections import defaultdict
class LinuxLogForensicAnalyzer:
"""分析 Linux 系统日志进行取证调查。"""
def __init__(self, log_dir: str, output_dir: str):
self.log_dir = log_dir
self.output_dir = output_dir
os.makedirs(output_dir, exist_ok=True)
def parse_auth_log(self, auth_log_path: str) -> dict:
"""解析 auth.log 中的认证事件。"""
events = {
"successful_logins": [],
"failed_logins": [],
"sudo_commands": [],
"account_changes": [],
"ssh_sessions": []
}
ssh_accepted = re.compile(
r'(\w+\s+\d+\s+[\d:]+)\s+(\S+)\s+sshd\[\d+\]:\s+Accepted\s+(\S+)\s+for\s+(\S+)\s+from\s+([\d.]+)'
)
ssh_failed = re.compile(
r'(\w+\s+\d+\s+[\d:]+)\s+(\S+)\s+sshd\[\d+\]:\s+Failed\s+password\s+for\s+(\S*)\s+from\s+([\d.]+)'
)
sudo_cmd = re.compile(
r'(\w+\s+\d+\s+[\d:]+)\s+(\S+)\s+sudo:\s+(\S+)\s+:.*COMMAND=(.*)'
)
useradd = re.compile(
r'(\w+\s+\d+\s+[\d:]+)\s+(\S+)\s+useradd\[\d+\]:\s+new user: name=(\S+)'
)
with open(auth_log_path, , errors=) f:
line f:
m = ssh_accepted.search(line)
m:
events[].append({
: m.group(), : m.group(),
: m.group(), : m.group(), : m.group()
})
m = ssh_failed.search(line)
m:
events[].append({
: m.group(), : m.group(),
: m.group(), : m.group()
})
m = sudo_cmd.search(line)
m:
events[].append({
: m.group(), : m.group(),
: m.group(), : m.group().strip()
})
m = useradd.search(line)
m:
events[].append({
: m.group(), : m.group(),
: m.group()
})
events
() -> :
ip_failures = defaultdict()
event auth_events.get(, []):
ip_failures[event[]] +=
brute_force = []
ip, count ip_failures.items():
count >= threshold:
brute_force.append({: ip, : count})
(brute_force, key= x: x[], reverse=)
() -> :
auth_events = .parse_auth_log(auth_log_path)
brute_force = .detect_brute_force(auth_events)
report = {
: datetime.now().isoformat(),
: auth_log_path,
: {
: (auth_events[]),
: (auth_events[]),
: (auth_events[]),
: (auth_events[]),
: (brute_force)
},
: brute_force,
: auth_events
}
report_path = os.path.join(.output_dir, )
(report_path, ) f:
json.dump(report, f, indent=)
()
()
()
()
report_path
():
(sys.argv) < :
()
sys.exit()
analyzer = LinuxLogForensicAnalyzer(os.path.dirname(sys.argv[]), sys.argv[])
analyzer.generate_report(sys.argv[])
__name__ == :
main()
参考资料