1. 1. 06-文件系统取证 (Filesystem Forensics)
    1. 1.1. 1. 文件时间戳分析
      1. 1.1.1. 1.1 Linux 文件时间戳体系
      2. 1.1.2. 1.2 stat 命令完整解读
      3. 1.1.3. 1.3 时间戳篡改(Timestomping)检测
      4. 1.1.4. 1.4 使用 find 按时间搜索文件
    2. 1.2. 2. SUID/SGID 文件审计
      1. 1.2.1. 2.1 SUID/SGID 原理
      2. 1.2.2. 2.2 全面扫描命令
      3. 1.2.3. 2.3 正常 SUID 文件白名单
      4. 1.2.4. 2.4 GTFOBins — 常被利用的 SUID 程序
      5. 1.2.5. 2.5 SUID 后门原理
    3. 1.3. 3. Webshell 猎捕
      1. 1.3.1. 3.1 Webshell 排查策略
      2. 1.3.2. 3.2 时间排查
      3. 1.3.3. 3.3 PHP Webshell 关键字检测
      4. 1.3.4. 3.4 典型 Webshell 样本(识别用)
      5. 1.3.5. 3.5 JSP Webshell 检测
      6. 1.3.6. 3.6 一句话 Webshell 检测正则
      7. 1.3.7. 3.7 YARA 规则检测 Webshell(简介)
    4. 1.4. 4. 隐藏文件与目录排查
      1. 1.4.1. 4.1 点文件/点目录
      2. 1.4.2. 4.2 Unicode 文件名伪装
      3. 1.4.3. 4.3 空格文件名
      4. 1.4.4. 4.4 已删除但进程仍占用的文件
      5. 1.4.5. 4.5 /dev/shm, /tmp, /var/tmp 重点排查
    5. 1.5. 5. 系统文件完整性校验
      1. 1.5.1. 5.1 为什么要校验系统文件
      2. 1.5.2. 5.2 Debian/Ubuntu: debsums
      3. 1.5.3. 5.3 RHEL/CentOS: rpm -Va
      4. 1.5.4. 5.4 关键二进制手动校验
      5. 1.5.5. 5.5 使用静态编译的工具
    6. 1.6. 6. 大文件与异常文件排查
      1. 1.6.1. 6.1 查找大文件
      2. 1.6.2. 6.2 查找最近创建的大文件
      3. 1.6.3. 6.3 查找 immutable 文件
      4. 1.6.4. 6.4 查找世界可写文件
      5. 1.6.5. 6.5 查找世界可写目录(非 sticky bit)
    7. 1.7. 7. 删除文件恢复
      1. 1.7.1. 7.1 从 /proc 恢复运行中的已删除文件
      2. 1.7.2. 7.2 extundelete 恢复 ext 文件系统删除文件
      3. 1.7.3. 7.3 foremost / scalpel 文件雕刻
      4. 1.7.4. 7.4 注意事项
    8. 1.8. 8. 实战案例
      1. 1.8.1. 8.1 完整案例:追踪 Webshell 从上传到利用的全过程
      2. 1.8.2. 8.2 文件系统排查一键脚本
      3. 1.8.3. 8.3 练习:在实验环境中找出植入的 Webshell 和 timestomped 文件
    9. 1.9. 9. 总结与排查流程图

Linux应急响应 - 06 文件系统取证

06-文件系统取证 (Filesystem Forensics)

文件系统是攻击者留下痕迹最多的地方——恶意文件、WebShell、后门程序、篡改的系统文件,都会在文件系统中留下蛛丝马迹。掌握文件系统取证技术,能够有效发现和追踪攻击者的活动。

前置知识01-系统基础与关键目录

关联章节22-SUID后门 | 30-YARA规则


1. 文件时间戳分析

1.1 Linux 文件时间戳体系

Linux 文件系统(ext4)为每个文件维护多个时间戳:

时间戳 名称 含义 触发更新的操作
atime Access Time 最后访问时间 读取文件内容(cat, less, cp 源文件)
mtime Modify Time 最后修改时间 修改文件内容(echo >>, vim 编辑保存)
ctime Change Time 最后状态变更时间 修改文件元数据(chmod, chown, mv, 内容修改也会触发)
crtime/birth Creation Time 文件创建时间 仅在创建时设置(ext4 特有,非所有工具可读)

重要关系

修改内容 → mtime 和 ctime 都更新

修改权限/属主 → 只有 ctime 更新

touch 命令 → 可以修改 atime 和 mtime,但无法修改 ctime

ctime 只有在文件元数据变更时由内核自动更新,用户空间无法直接设置

注意:现代 Linux 默认使用 relatime 挂载选项,atime 不会在每次读取时更新(仅当 atime < mtime 时才更新),这会影响 atime 的取证价值

1.2 stat 命令完整解读

1
2
3
4
5
6
7
8
9
10
11
stat /path/to/file

# 输出示例:
# File: /var/www/html/shell.php
# Size: 45 Blocks: 8 IO Block: 4096 regular file
# Device: 801h/2049d Inode: 1234567 Links: 1
# Access: (0644/-rw-r--r--) Uid: ( 33/www-data) Gid: ( 33/www-data)
# Access: 2026-03-28 15:30:00.000000000 +0800
# Modify: 2026-03-28 03:15:22.000000000 +0800
# Change: 2026-03-28 03:15:22.000000000 +0800
# Birth: 2026-03-28 03:15:22.000000000 +0800

各字段排查意义

Size:文件大小,WebShell 通常很小(几十字节到几KB)

Inode:文件节点号,用于 debugfs 深度分析

Access (权限):异常权限(如 777)需要关注

Uid/Gid:文件所有者,www-data 的可疑文件需要重点排查

Access time:最后访问时间

Modify time:最后内容修改时间

Change time:最后元数据变更时间

Birth time:文件创建时间(不是所有系统都显示)

1.3 时间戳篡改(Timestomping)检测

攻击者为什么要篡改时间戳:让恶意文件看起来是”很久以前”创建的,避免被 find -mtime -7 这类命令发现

常见篡改手法

1
2
3
4
5
6
7
# 攻击者使用 touch 修改时间戳
touch -t 202301010000 /var/www/html/shell.php
# 将文件的 mtime/atime 改为 2023年1月1日

# 更高级的:参考另一个文件的时间戳
touch -r /var/www/html/index.php /var/www/html/shell.php
# 将 shell.php 的时间改为和 index.php 一样

检测方法1:mtime 与 ctime 不一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# touch 只能修改 mtime 和 atime,不能修改 ctime
# 如果 mtime 远早于 ctime,说明 mtime 被篡改过

# 检测 mtime 比 ctime 早超过 1 天的文件
find /var/www/ -type f -exec sh -c '
for f; do
mtime=$(stat -c %Y "$f")
ctime=$(stat -c %Z "$f")
diff=$((ctime - mtime))
if [ $diff -gt 86400 ]; then
echo "可疑时间戳! mtime比ctime早$(($diff/86400))天: $f"
stat "$f" | grep -E "Modify|Change"
fi
done
' sh {} +

检测方法2:使用 crtime(ext4 创建时间)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# ext4 文件系统保存了真实的创建时间(crtime/birth time)
# 即使攻击者用 touch 修改了 mtime,crtime 不会改变

# 获取文件的 inode 号
ls -i /var/www/html/shell.php
# 1234567 /var/www/html/shell.php

# 使用 debugfs 读取 crtime(需要 root 和知道分区设备名)
# 先确定文件所在分区
df /var/www/html/shell.php
# /dev/sda1

debugfs -R "stat <1234567>" /dev/sda1 2>/dev/null | grep crtime
# crtime: 0x6604a0b2:00000000 -- Thu Mar 28 03:15:22 2026

检测方法3:批量检测 timestomping

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/bin/bash
# 批量检测时间戳篡改
TARGET_DIR="${1:-/var/www}"

echo "=== 时间戳篡改检测 ==="
echo "扫描目录: $TARGET_DIR"
echo ""

find "$TARGET_DIR" -type f 2>/dev/null | while read f; do
mtime=$(stat -c %Y "$f" 2>/dev/null)
ctime=$(stat -c %Z "$f" 2>/dev/null)

if [ -n "$mtime" ] && [ -n "$ctime" ]; then
diff=$((ctime - mtime))
# mtime 比 ctime 早超过 1 小时
if [ $diff -gt 3600 ]; then
mdate=$(stat -c %y "$f" | cut -d. -f1)
cdate=$(stat -c %z "$f" | cut -d. -f1)
echo "[!] $f"
echo " mtime: $mdate"
echo " ctime: $cdate"
echo " 差异: $(($diff/3600)) 小时"
echo ""
fi
fi
done

1.4 使用 find 按时间搜索文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 查找最近 7 天内修改的文件
find /var/www/ -type f -mtime -7

# 查找最近 24 小时内修改的文件
find /var/www/ -type f -mmin -1440

# 查找指定时间范围内修改的文件(需要创建参考文件)
touch -t 202603280300 /tmp/start_time
touch -t 202603280400 /tmp/end_time
find /var/www/ -type f -newer /tmp/start_time ! -newer /tmp/end_time

# 查找 ctime 在最近 7 天内变更的文件
find /var/www/ -type f -ctime -7

# 组合:最近修改的 PHP 文件
find /var/www/ -name "*.php" -mtime -7 -ls

2. SUID/SGID 文件审计

2.1 SUID/SGID 原理

**SUID (Set User ID)**:当执行该文件时,进程的有效 UID 变为文件所有者的 UID(通常是 root)

**SGID (Set Group ID)**:类似 SUID,但针对组权限

安全风险:如果攻击者能让一个文件设置 SUID 位且所有者为 root,则任何用户执行该文件都能获得 root 权限

2.2 全面扫描命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查找所有 SUID 文件
find / -perm -4000 -type f -ls 2>/dev/null

# 查找所有 SGID 文件
find / -perm -2000 -type f -ls 2>/dev/null

# 查找 SUID 或 SGID 文件
find / -perm /6000 -type f -ls 2>/dev/null

# 更高效:排除已知安全的目录
find / -perm -4000 -type f \
! -path "/proc/*" \
! -path "/sys/*" \
! -path "/snap/*" \
-ls 2>/dev/null

2.3 正常 SUID 文件白名单

Ubuntu/Debian 默认 SUID 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/sudo
/usr/bin/su
/usr/bin/mount
/usr/bin/umount
/usr/bin/pkexec
/usr/bin/fusermount
/usr/bin/traceroute6.iputils(可能存在)
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/eject/dmcrypt-get-device
/usr/lib/snapd/snap-confine(如果安装了 snap)

CentOS/RHEL 默认 SUID 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/sudo
/usr/bin/su
/usr/bin/mount
/usr/bin/umount
/usr/bin/pkexec
/usr/bin/crontab
/usr/sbin/unix_chkpwd
/usr/sbin/pam_timestamp_check
/usr/lib/polkit-1/polkit-agent-helper-1
/usr/libexec/dbus-1/dbus-daemon-launch-helper

基线对比脚本

1
2
3
4
5
6
7
# 生成 SUID 基线(在安全状态下执行)
find / -perm -4000 -type f 2>/dev/null | sort > /root/suid_baseline.txt

# 排查时对比
find / -perm -4000 -type f 2>/dev/null | sort > /tmp/suid_current.txt
diff /root/suid_baseline.txt /tmp/suid_current.txt
# ">" 开头的行 = 新增的 SUID 文件(需要调查)

2.4 GTFOBins — 常被利用的 SUID 程序

GTFOBins (https://gtfobins.github.io/) 收录了可以被滥用的 Linux 二进制程序

常见可被 SUID 利用的程序

程序 SUID 提权方法 危险等级
find find . -exec /bin/bash -p \; 极高
vim / vi :!/bin/bash:set shell=/bin/bash :shell 极高
python / python3 python -c 'import os; os.execl("/bin/bash","bash","-p")' 极高
nmap 旧版 nmap --interactive 然后 !sh
less / more !/bin/bash
awk awk 'BEGIN {system("/bin/bash")}'
perl perl -e 'exec "/bin/bash";'
cp 覆盖 /etc/passwd 或 /etc/shadow
mv 替换系统文件
env env /bin/bash -p
bash bash -p 极高

排查命令

1
2
3
4
5
6
7
8
9
10
11
# 检查这些危险程序是否有 SUID 位
DANGEROUS="find vim vi python python3 perl ruby nmap less more awk env bash sh dash node php lua"
for prog in $DANGEROUS; do
path=$(which $prog 2>/dev/null)
if [ -n "$path" ]; then
perms=$(stat -c %a "$path" 2>/dev/null)
if [ $((perms & 4000)) -ne 0 ]; then
echo "[!] 危险 SUID: $path (权限: $perms)"
fi
fi
done

2.5 SUID 后门原理

攻击者创建 SUID 后门的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 方法1:复制 bash 并设置 SUID
cp /bin/bash /tmp/.hidden_bash
chmod u+s /tmp/.hidden_bash
# 任何用户执行 /tmp/.hidden_bash -p 即可获得 root shell

# 方法2:编写自定义 SUID 程序
cat > /tmp/.suid_backdoor.c << 'EOF'
#include <unistd.h>
int main() {
setuid(0);
setgid(0);
execl("/bin/bash", "bash", "-p", NULL);
return 0;
}
EOF
gcc -o /tmp/.suid_backdoor /tmp/.suid_backdoor.c
chmod u+s /tmp/.suid_backdoor
rm /tmp/.suid_backdoor.c

# 方法3:对现有的系统工具增加 SUID
chmod u+s /usr/bin/find
# 然后通过 find -exec 获取 root shell

排查重点

1
2
3
4
5
6
7
8
# 非标准路径下的 SUID 文件
find / -perm -4000 -type f 2>/dev/null | grep -v "^/usr/\|^/bin/\|^/sbin/"

# /tmp, /var/tmp, /dev/shm 下的 SUID 文件
find /tmp /var/tmp /dev/shm -perm -4000 -type f -ls 2>/dev/null

# 隐藏目录中的 SUID 文件
find / -path "*/.*" -perm -4000 -type f -ls 2>/dev/null

更多详情22-SUID后门

3. Webshell 猎捕

3.1 Webshell 排查策略

1
2
3
4
5
1. 时间排查:查找最近修改/创建的 Web 文件
2. 关键字排查:搜索已知的 Webshell 特征字符串
3. 文件属性排查:大小、权限、所有者异常
4. 统计异常排查:文件熵值、代码行数异常
5. 工具扫描:YARA 规则、专业 Webshell 检测工具

3.2 时间排查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Web 根目录(根据实际情况调整)
WEBROOT="/var/www"

# 查找最近 7 天修改的 Web 文件
find $WEBROOT -name "*.php" -mtime -7 -ls
find $WEBROOT -name "*.jsp" -mtime -7 -ls
find $WEBROOT -name "*.asp" -mtime -7 -ls
find $WEBROOT -name "*.aspx" -mtime -7 -ls

# 查找最近 24 小时修改的所有文件
find $WEBROOT -type f -mmin -1440 -ls

# 按修改时间排序(最新的在前)
find $WEBROOT -name "*.php" -type f -printf '%T@ %Tc %p\n' | sort -rn | head -20

3.3 PHP Webshell 关键字检测

常见 PHP Webshell 关键字

关键字 说明 危险等级
eval( 执行 PHP 代码
system( 执行系统命令
exec( 执行系统命令
shell_exec( 执行系统命令
passthru( 执行系统命令
popen( 打开进程管道
proc_open( 打开进程
base64_decode( Base64 解码(混淆)
gzinflate( Gzip 解压(混淆)
str_rot13( ROT13 编码(混淆)
assert( 类似 eval
preg_replace + e 修饰符 正则执行代码
$_POST[ / $_GET[ / $_REQUEST[ + eval 用户输入直接执行 极高
call_user_func( 动态函数调用
create_function( 动态创建函数

排查命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
WEBROOT="/var/www"

# 搜索常见 Webshell 关键字
grep -rn --include="*.php" \
-e "eval(" \
-e "system(" \
-e "exec(" \
-e "shell_exec(" \
-e "passthru(" \
-e "popen(" \
-e "proc_open(" \
-e "base64_decode(" \
-e "assert(" \
"$WEBROOT" 2>/dev/null

# 更精确:查找一句话木马模式
grep -rn --include="*.php" -P \
"(eval|assert|system|exec)\s*\(\s*(\\\$_(POST|GET|REQUEST|COOKIE)|base64_decode)" \
"$WEBROOT" 2>/dev/null

# 查找超短 PHP 文件(一句话木马通常很短)
find "$WEBROOT" -name "*.php" -size -1k -exec wc -l {} \; | awk '$1<5 {print}'

3.4 典型 Webshell 样本(识别用)

一句话木马

1
2
3
4
<?php eval($_POST['cmd']); ?>
<?php @eval($_POST['pass']); ?>
<?php system($_GET['c']); ?>
<?php assert($_REQUEST['x']); ?>

混淆的一句话

1
2
3
4
5
<?php $a="ev"."al"; $a($_POST['p']); ?>
<?php eval(base64_decode("c3lzdGVtKCRfR0VUWydjJ10pOw==")); ?>
<?php $f=create_function('',$_POST['c']); $f(); ?>
<?php preg_replace("/.*/e",$_POST['c'],''); ?>
<?php ${"\x47\x4c\x4fB\x41\x4c\x53"}["\x76\x61\x72"]($_POST['p']); ?>

大马(功能完整的 Webshell)特征

文件较大(几十KB到几百KB)

包含文件管理、命令执行、数据库连接、反弹 shell 等多种功能

通常有密码保护($password = 'xxx';

可能包含 Base64 编码的大段数据

3.5 JSP Webshell 检测

1
2
3
4
5
6
7
8
9
10
11
12
13
# JSP Webshell 关键字
grep -rn --include="*.jsp" --include="*.jspx" \
-e "Runtime.getRuntime().exec" \
-e "ProcessBuilder" \
-e "getParameter" \
-e "ClassLoader" \
-e "defineClass" \
-e "URLClassLoader" \
"$WEBROOT" 2>/dev/null

# 典型 JSP 一句话:
# <%Runtime.getRuntime().exec(request.getParameter("cmd"));%>
# <%=Runtime.getRuntime().exec(request.getParameter("cmd"))%>

3.6 一句话 Webshell 检测正则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 综合正则检测(PHP)
grep -rPn --include="*.php" \
'(?:eval|assert|system|exec|passthru|shell_exec|popen|proc_open)\s*\(\s*(?:\$_(?:POST|GET|REQUEST|COOKIE|SERVER)|base64_decode|gzinflate|str_rot13|gzuncompress)' \
"$WEBROOT" 2>/dev/null

# 检测动态函数调用
grep -rPn --include="*.php" \
'(?:call_user_func|call_user_func_array|create_function|array_map|array_filter|usort)\s*\([^)]*\$_(?:POST|GET|REQUEST)' \
"$WEBROOT" 2>/dev/null

# 检测变量函数调用($func = "system"; $func("cmd");)
grep -rPn --include="*.php" \
'\$\w+\s*\(\s*\$_(?:POST|GET|REQUEST)' \
"$WEBROOT" 2>/dev/null

3.7 YARA 规则检测 Webshell(简介)

YARA 是一种基于规则的恶意文件识别工具,可以编写灵活的规则检测 Webshell

1
2
3
4
5
6
7
8
# 安装 YARA
# Ubuntu/Debian
apt install yara
# CentOS
yum install yara

# 基础使用
yara webshell_rules.yar /var/www/

简单的 YARA Webshell 规则示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
rule PHP_Webshell_Generic {
meta:
description = "检测常见 PHP Webshell"
strings:
$eval = "eval(" ascii nocase
$post = "$_POST" ascii
$get = "$_GET" ascii
$system = "system(" ascii nocase
$exec = "exec(" ascii nocase
$b64 = "base64_decode(" ascii nocase
condition:
($eval and ($post or $get)) or
($system and ($post or $get)) or
($exec and ($post or $get)) or
($eval and $b64)
}

更多 YARA 规则详见30-YARA规则

4. 隐藏文件与目录排查

4.1 点文件/点目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查找所有隐藏文件(以 . 开头)
# 排除常见的合法隐藏文件
find / -name ".*" -not -path "/proc/*" -not -path "/sys/*" \
-not -name ".bashrc" -not -name ".bash_history" -not -name ".profile" \
-not -name ".bash_logout" -not -name ".cache" -not -name ".config" \
-not -name ".local" -not -name ".ssh" -not -name ".gnupg" \
-not -name ".." -not -name "." \
-ls 2>/dev/null | head -50

# 重点排查路径:
find /tmp -name ".*" -ls 2>/dev/null
find /var/tmp -name ".*" -ls 2>/dev/null
find /dev/shm -name ".*" -ls 2>/dev/null
find /var/www -name ".*" -ls 2>/dev/null

4.2 Unicode 文件名伪装

原理:攻击者使用 Unicode 特殊字符创建看似正常但实际不同的文件名

常见手法

全角字符:index.php vs index.php

零宽字符:index​.php(名字中有不可见字符)

右至左覆盖字符(RLO):gpj.sysinfo 显示为 ofnisys.jpg

1
2
3
4
5
6
7
8
# 检测非 ASCII 字符的文件名
find /var/www -type f | grep -P '[^\x00-\x7F]'

# 检测包含不可打印字符的文件名
find /var/www -type f -print0 | xargs -0 ls -la | grep -P '[^\x20-\x7E]'

# 使用 ls 的 -b 选项显示特殊字符
ls -lab /var/www/html/

4.3 空格文件名

1
2
3
4
5
6
7
8
# 查找文件名中包含空格的文件
find / -name "* *" -not -path "/proc/*" -not -path "/sys/*" -ls 2>/dev/null

# 查找文件名是纯空格的文件
find / -name " " -o -name " " -o -name " " 2>/dev/null

# 查找文件名包含全角空格的文件
find / -name "* *" -not -path "/proc/*" -ls 2>/dev/null

4.4 已删除但进程仍占用的文件

原理:Linux 中删除文件只是解除目录项链接,如果有进程仍在使用该文件,文件数据不会被真正释放

1
2
3
4
5
6
7
8
9
10
# 查找所有已删除但仍被进程打开的文件
lsof +L1

# 只看可执行文件
lsof +L1 | grep -E "REG.*\(deleted\)"

# 输出示例:
# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
# miner 1234 root txt REG 8,1 1234567 0 5678 /tmp/.cache/xmrig (deleted)
# agent 5678 root txt REG 8,1 987654 0 9012 /dev/shm/.update/beacon (deleted)

处理方法

1
2
3
4
5
# 从 /proc 恢复已删除的文件
cp /proc/1234/exe /tmp/evidence/recovered_miner

# 获取文件的 MD5/SHA256
sha256sum /proc/1234/exe

4.5 /dev/shm, /tmp, /var/tmp 重点排查

为什么这些目录需要重点排查

/tmp:所有用户可写,系统重启后清空

/var/tmp:所有用户可写,重启后不清空

/dev/shm:共享内存文件系统(tmpfs),在内存中,速度快,重启后消失

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 全面排查这三个目录
for dir in /tmp /var/tmp /dev/shm; do
echo "=== $dir ==="
echo "文件总数: $(find $dir -type f 2>/dev/null | wc -l)"
echo "隐藏文件:"
find $dir -name ".*" -type f -ls 2>/dev/null
echo "可执行文件:"
find $dir -type f -executable -ls 2>/dev/null
echo "SUID 文件:"
find $dir -perm -4000 -type f -ls 2>/dev/null
echo "最近 24 小时修改的文件:"
find $dir -type f -mmin -1440 -ls 2>/dev/null
echo ""
done

5. 系统文件完整性校验

5.1 为什么要校验系统文件

攻击者可能替换系统关键二进制文件(如 ps, ss, netstat, ls, lsof),使其过滤掉恶意进程/网络连接的信息。这是 rootkit 的经典手法。

5.2 Debian/Ubuntu: debsums

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 安装 debsums
apt install debsums

# 检查所有包文件的 MD5 完整性
debsums -c
# -c 只显示被修改的文件

# 检查特定包
debsums -c coreutils # ls, cp, mv 等
debsums -c procps # ps, top 等
debsums -c iproute2 # ss, ip 等
debsums -c net-tools # netstat, ifconfig 等
debsums -c lsof # lsof

# 输出示例(被修改的文件):
# /usr/bin/ps FAILED
# /usr/bin/netstat FAILED

注意:debsums 使用 MD5,不如 SHA-256 安全,但对于快速检测文件是否被替换已经足够

5.3 RHEL/CentOS: rpm -Va

1
2
3
4
5
6
7
8
9
# 验证所有已安装包的文件
rpm -Va

# 只验证特定包
rpm -V coreutils
rpm -V procps-ng
rpm -V iproute
rpm -V net-tools
rpm -V lsof

输出格式详解

1
S.5....T.  /usr/bin/ps
标记 位置 含义
S 1 文件大小变化
M 2 文件权限或类型变化
5 3 MD5 校验值变化
D 4 设备号变化
L 5 符号链接路径变化
U 6 所有者变化
G 7 所属组变化
T 8 修改时间变化
P 9 Capabilities 变化
. 任意 该项未变化

示例分析

1
2
3
4
5
6
S.5....T.  /usr/bin/ps
# S = 大小变了, 5 = MD5 变了, T = 时间变了
# 这个 ps 非常可能被替换了!

......G.. /etc/group
# G = 组变了(可能是正常的管理操作)

5.4 关键二进制手动校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 需要关注的关键命令
CRITICAL_BINS=(
/usr/bin/ps
/usr/bin/top
/usr/bin/ls
/usr/bin/ss
/usr/bin/netstat
/usr/bin/lsof
/usr/bin/find
/usr/bin/who
/usr/bin/w
/usr/bin/last
/usr/bin/lastlog
/usr/sbin/sshd
/usr/bin/login
/usr/bin/su
/usr/bin/sudo
/usr/bin/curl
/usr/bin/wget
)

echo "=== 关键二进制文件 SHA256 校验 ==="
for bin in "${CRITICAL_BINS[@]}"; do
if [ -f "$bin" ]; then
hash=$(sha256sum "$bin" | awk '{print $1}')
size=$(stat -c %s "$bin")
mtime=$(stat -c %y "$bin" | cut -d. -f1)
printf "%-25s %s size:%s mtime:%s\n" "$bin" "$hash" "$size" "$mtime"
fi
done

与已知 good hash 对比

可以从相同版本的干净系统获取基线 hash

也可以重新安装对应包获取原始文件:

1
2
3
4
5
# Ubuntu/Debian: 重新安装包并对比
apt install --reinstall procps

# CentOS/RHEL: 重新安装包
yum reinstall procps-ng

5.5 使用静态编译的工具

当怀疑系统工具被替换时,应该使用自己带的静态编译工具进行排查

1
2
3
4
5
6
7
8
9
10
11
12
# 从安全的 USB/网络位置复制静态编译的工具
# 常用的静态编译工具包(需要提前准备):
# - busybox (包含大部分常用命令的静态编译版)
# - static-ps, static-ss, static-ls 等

# 使用 busybox(静态编译)
./busybox ps aux
./busybox ls -la /tmp
./busybox netstat -antlp

# 如果没有准备好的工具,可以紧急编译
# (前提是 gcc 没被替换......)

6. 大文件与异常文件排查

6.1 查找大文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 查找大于 100MB 的文件
find / -size +100M -type f \
-not -path "/proc/*" \
-not -path "/sys/*" \
-not -path "/snap/*" \
-ls 2>/dev/null

# 查找大于 1GB 的文件
find / -size +1G -type f \
-not -path "/proc/*" \
-not -path "/sys/*" \
-ls 2>/dev/null

# 按大小排序显示最大的 20 个文件
find / -type f \
-not -path "/proc/*" \
-not -path "/sys/*" \
-printf '%s %p\n' 2>/dev/null | sort -rn | head -20

排查要点

异常位置的大文件(如 /tmp, /var/tmp, /dev/shm 下的大文件)

压缩包(.tar.gz, .zip)可能是攻击者打包的数据窃取文件

日志文件异常增大可能表示正在进行暴力破解

6.2 查找最近创建的大文件

1
2
3
4
5
6
7
8
9
# 最近 7 天创建的大于 10MB 的文件
find / -type f -size +10M -mtime -7 \
-not -path "/proc/*" \
-not -path "/sys/*" \
-ls 2>/dev/null

# 检查是否有压缩包(可能是数据打包外传)
find / -type f \( -name "*.tar*" -o -name "*.zip" -o -name "*.rar" -o -name "*.7z" \) \
-mtime -7 -ls 2>/dev/null

6.3 查找 immutable 文件

原理:攻击者使用 chattr +i 设置 immutable 属性,使文件无法被删除或修改(即使是 root 也不行,除非先去掉 i 属性)

1
2
3
4
5
6
7
8
9
10
# 扫描所有带 immutable 属性的文件
lsattr -R / 2>/dev/null | grep -E "^....i"

# 重点扫描
lsattr -R /tmp /var/tmp /var/www /etc 2>/dev/null | grep -E "^....i"

# 如果发现异常的 immutable 文件
# 去除 immutable 属性后才能删除
chattr -i /path/to/malicious_file
rm /path/to/malicious_file

为什么攻击者要用 immutable

防止管理员删除恶意文件

防止安全工具覆盖/修改恶意文件

保护 crontab 后门不被清除

6.4 查找世界可写文件

1
2
3
4
5
6
7
8
9
10
11
# 查找世界可写的文件(排除 /tmp 等本身就是可写的目录)
find / -perm -o+w -type f \
-not -path "/proc/*" \
-not -path "/sys/*" \
-not -path "/tmp/*" \
-not -path "/var/tmp/*" \
-not -path "/dev/*" \
-ls 2>/dev/null

# 特别关注 /etc, /usr, /bin 等目录下的世界可写文件
find /etc /usr /bin /sbin -perm -o+w -type f -ls 2>/dev/null

6.5 查找世界可写目录(非 sticky bit)

1
2
3
4
5
# 世界可写但没有 sticky bit 的目录(任何人都可以删除其中的文件)
find / -perm -o+w -type d ! -perm -1000 \
-not -path "/proc/*" \
-not -path "/sys/*" \
-ls 2>/dev/null

7. 删除文件恢复

7.1 从 /proc 恢复运行中的已删除文件

最简单且最可靠的方法(前提:进程仍在运行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查找已删除但仍被打开的文件
lsof +L1

# 从 /proc 恢复
# 方法1:通过 /proc/PID/exe(可执行文件)
cp /proc/PID/exe /tmp/evidence/recovered_binary

# 方法2:通过 /proc/PID/fd/N(打开的文件描述符)
# 先找到对应的 fd 编号
ls -la /proc/PID/fd/ | grep deleted
# 3 -> /var/log/secret.log (deleted)
cp /proc/PID/fd/3 /tmp/evidence/recovered_log

# 验证恢复的文件
file /tmp/evidence/recovered_binary
sha256sum /tmp/evidence/recovered_binary
strings /tmp/evidence/recovered_binary | head -20

7.2 extundelete 恢复 ext 文件系统删除文件

前提:文件已被删除且进程不再运行,文件系统为 ext3/ext4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 安装
apt install extundelete # Ubuntu/Debian
yum install extundelete # CentOS

# 重要:先卸载或以只读方式重新挂载文件系统
# (避免新数据覆盖已删除的文件)
mount -o remount,ro /dev/sda1

# 恢复指定时间之后删除的文件
extundelete /dev/sda1 --after $(date -d "2026-03-28" +%s) --restore-all

# 恢复指定目录下的文件
extundelete /dev/sda1 --restore-directory var/www/html

# 恢复指定 inode 的文件
extundelete /dev/sda1 --restore-inode 1234567

# 恢复的文件默认保存在 RECOVERED_FILES/ 目录

7.3 foremost / scalpel 文件雕刻

文件雕刻(File Carving):通过文件头/尾签名从磁盘原始数据中恢复文件,不依赖文件系统元数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 安装 foremost
apt install foremost # Ubuntu/Debian
yum install foremost # CentOS

# 从磁盘镜像或分区恢复文件
foremost -i /dev/sda1 -o /tmp/recovered/

# 指定要恢复的文件类型
foremost -t exe,elf,pdf,zip -i /dev/sda1 -o /tmp/recovered/

# 使用 scalpel(foremost 的改进版)
apt install scalpel
# 编辑配置文件指定要恢复的类型
# /etc/scalpel/scalpel.conf
scalpel /dev/sda1 -o /tmp/recovered/

使用场景

文件系统严重损坏,extundelete 无法使用

需要恢复特定类型的文件(如 ELF 可执行文件)

取证分析中需要尽可能多地恢复证据

7.4 注意事项

文件恢复的黄金法则

尽快操作——被删除的数据块可能随时被新数据覆盖

绝对不要在目标磁盘上进行恢复操作(恢复的文件要写到其他磁盘)

最好先做磁盘的位对位镜像(dd),然后在镜像上操作

1
2
3
4
5
6
# 制作磁盘镜像
dd if=/dev/sda1 of=/mnt/external/sda1.img bs=4M status=progress

# 在镜像上进行恢复操作
extundelete /mnt/external/sda1.img --restore-all
foremost -i /mnt/external/sda1.img -o /mnt/external/recovered/

8. 实战案例

8.1 完整案例:追踪 Webshell 从上传到利用的全过程

场景:WAF 告警发现有 Webshell 上传行为,需要进行文件系统层面的取证

Step 1:查找最近修改的 PHP 文件

1
2
3
find /var/www -name "*.php" -mtime -3 -ls
# -rw-r--r-- 1 www-data www-data 45 Mar 28 03:20 /var/www/html/uploads/../config.php
# -rw-r--r-- 1 www-data www-data 234 Mar 28 03:25 /var/www/html/static/.cache.php

Step 2:分析可疑文件内容

1
2
3
4
5
6
cat /var/www/html/uploads/../config.php
# <?php @eval($_POST['x']);?>
# 确认是一句话木马

cat /var/www/html/static/.cache.php
# 发现是经过混淆的大马

Step 3:检查时间戳

1
2
3
4
5
6
7
stat /var/www/html/uploads/../config.php
# Access: 2026-03-28 15:30:00 (最后被访问 → 攻击者最后使用时间)
# Modify: 2026-03-28 03:20:11 (上传时间)
# Change: 2026-03-28 03:20:11
# Birth: 2026-03-28 03:20:11

# 时间一致,没有被 timestomp

Step 4:在 Web 日志中定位上传请求

1
2
3
4
5
grep "config.php\|\.cache.php" /var/log/nginx/access.log
# 192.168.1.50 - - [28/Mar/2026:03:20:10] "POST /upload.php HTTP/1.1" 200 45
# 192.168.1.50 - - [28/Mar/2026:03:20:30] "POST /uploads/../config.php HTTP/1.1" 200 12
# 192.168.1.50 - - [28/Mar/2026:03:25:00] "POST /uploads/../config.php HTTP/1.1" 200 234
# → 上传了 webshell 后立即使用

Step 5:追踪攻击者后续活动

1
2
3
4
5
6
# 查看 config.php 被访问的时间段的所有请求
grep "192.168.1.50" /var/log/nginx/access.log | grep "28/Mar/2026:03:2"
# 发现攻击者还访问了 /etc/passwd, 执行了 whoami 等命令

# 检查是否有文件被修改
find /var/www -user www-data -mtime -3 -newer /var/www/html/uploads/../config.php -ls

Step 6:检查是否有进一步渗透

1
2
3
4
5
6
# SUID 后门检查
find / -perm -4000 -newer /var/www/html/uploads/../config.php -ls 2>/dev/null

# 新文件检查
find / -newer /var/www/html/uploads/../config.php -not -path "/proc/*" \
-not -path "/sys/*" -type f -ls 2>/dev/null | head -20

时间线总结

1
2
3
4
03:20:10 - 攻击者通过 upload.php 上传了 config.php(一句话木马)
03:20:30 - 攻击者开始使用 webshell 执行命令
03:25:00 - 攻击者上传了更功能丰富的大马 .cache.php
03:25~ - 攻击者开始进一步渗透...

8.2 文件系统排查一键脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#!/bin/bash
# 文件系统安全排查脚本
# 使用方法: sudo bash filesystem_audit.sh [webroot]

WEBROOT="${1:-/var/www}"

echo "=========================================="
echo " 文件系统安全排查"
echo " 执行时间: $(date)"
echo " Web根目录: $WEBROOT"
echo "=========================================="

echo -e "\n[1] SUID 文件排查(非标准路径):"
find / -perm -4000 -type f \
-not -path "/usr/*" -not -path "/bin/*" -not -path "/sbin/*" \
-not -path "/proc/*" -not -path "/sys/*" -not -path "/snap/*" \
-ls 2>/dev/null

echo -e "\n[2] 最近 7 天修改的 Web 文件:"
find "$WEBROOT" -type f \( -name "*.php" -o -name "*.jsp" -o -name "*.asp" -o -name "*.aspx" \) \
-mtime -7 -ls 2>/dev/null

echo -e "\n[3] Webshell 关键字检测:"
grep -rn --include="*.php" -l \
-e "eval(\$_" -e "assert(\$_" -e "system(\$_" -e "exec(\$_" \
-e "shell_exec(\$_" -e "passthru(\$_" \
"$WEBROOT" 2>/dev/null

echo -e "\n[4] /tmp /var/tmp /dev/shm 可疑文件:"
for dir in /tmp /var/tmp /dev/shm; do
echo "--- $dir ---"
find $dir -type f \( -name ".*" -o -executable -o -perm -4000 \) -ls 2>/dev/null
done

echo -e "\n[5] 已删除但仍被打开的文件:"
lsof +L1 2>/dev/null | grep -E "REG.*deleted" | head -20

echo -e "\n[6] immutable 文件检测:"
lsattr -R /tmp /var/tmp /etc /var/www 2>/dev/null | grep -E "^....i"

echo -e "\n[7] 时间戳篡改检测(mtime 远早于 ctime):"
find "$WEBROOT" -type f -name "*.php" 2>/dev/null | while read f; do
mtime=$(stat -c %Y "$f" 2>/dev/null)
ctime=$(stat -c %Z "$f" 2>/dev/null)
if [ -n "$mtime" ] && [ -n "$ctime" ]; then
diff=$((ctime - mtime))
if [ $diff -gt 86400 ]; then
echo " [!] $f (mtime比ctime早$(($diff/86400))天)"
fi
fi
done

echo -e "\n[8] 系统二进制完整性检查:"
if command -v debsums &>/dev/null; then
echo " 使用 debsums 检查..."
debsums -c 2>/dev/null | head -20
elif command -v rpm &>/dev/null; then
echo " 使用 rpm -Va 检查关键包..."
for pkg in coreutils procps-ng iproute net-tools lsof; do
result=$(rpm -V $pkg 2>/dev/null)
[ -n "$result" ] && echo " $pkg: $result"
done
fi

echo -e "\n[9] 世界可写的敏感目录文件:"
find /etc /usr /bin /sbin -perm -o+w -type f -ls 2>/dev/null

echo -e "\n[10] 大于 100MB 的可疑文件(排除日志和数据库):"
find / -size +100M -type f \
-not -path "/proc/*" -not -path "/sys/*" -not -path "/snap/*" \
-not -name "*.log" -not -name "*.sql" -not -name "*.gz" \
-not -path "/var/lib/mysql/*" -not -path "/var/lib/docker/*" \
-ls 2>/dev/null

echo -e "\n=========================================="
echo " 排查完成"
echo "=========================================="

8.3 练习:在实验环境中找出植入的 Webshell 和 timestomped 文件

练习目标

找出所有 Webshell 文件(至少 3 个:一句话、混淆型、大马)

找出被 timestomp 的恶意文件

还原攻击者的完整活动时间线

实验环境

1
2
# 使用 Docker 搭建练习环境
docker run -it --name ir-lab-06 ir-practice:filesystem-forensics /bin/bash

练习提示

Webshell1:在 uploads 目录下,使用了 .. 路径穿越

Webshell2:在 static 目录下,隐藏文件(点开头)

Webshell3:文件名使用了 Unicode 伪装

timestomped 文件:使用 mtime vs ctime 对比检测

验证清单

[ ] 找到所有 Webshell 并记录路径和类型

[ ] 确定每个 Webshell 的真实上传时间

[ ] 发现 timestomped 文件并确定真实创建时间

[ ] 检查是否有 SUID 后门被植入

[ ] 验证系统二进制文件完整性

[ ] 建立完整的攻击时间线

9. 总结与排查流程图

文件系统取证标准流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1. 时间线建立(确定入侵时间窗口)

2. 最近修改文件排查(find -mtime/-ctime)

3. Webshell 关键字搜索

4. SUID/SGID 文件审计

5. 隐藏文件/异常文件排查

6. 系统文件完整性校验

7. 时间戳篡改检测

8. 已删除文件恢复

9. 证据保全与汇总

关键命令速查

排查项目 命令
最近修改文件 find / -mtime -7 -type f -ls
SUID 文件 find / -perm -4000 -type f -ls
Webshell 检测 grep -rn "eval(\$_" /var/www/
隐藏文件 find / -name ".*" -ls
系统完整性(Debian) debsums -c
系统完整性(RHEL) rpm -Va
已删除文件 lsof +L1
immutable 文件 lsattr -R / | grep "i"
时间戳详情 stat filename
文件恢复(运行中) cp /proc/PID/exe /tmp/recovered

下一步学习:深入了解 SUID 后门请参考 22-SUID后门,YARA 规则编写请参考 30-YARA规则


上一章 目录 下一章
05-进程与网络分析 Linux应急响应 07-计划任务审计