按易失性顺序收集,然后封存
目标
把按 RFC 3227 易失性顺序收集证据的计划固化成文件,并编写严格遵循该顺序的收集器。把收集时间、收集者、命令和 sha256 写进清单,封存清单后,再编写检查封存是否被破坏的验证工具,并在含有客户个人信息的项目上划出界线。
为什么重要
进入现场后,手会先伸向日志。在下载日志的这 10 分钟里,进程列表会变,连接会关闭,临时文件会被删除。日志明天还在原地,而那些东西只存在于此刻,一旦消失,就再也回答不了“当时那个进程在运行吗”这个问题。 RFC 3227 的第 2.1 节写道要从易失性高的向易失性低的推进,并给出了七组示例顺序。这个顺序给出的不只是优先级,还有判断的依据:被问到为什么先取那个时,可以用等级而不是凭感觉来回答。 光有收集还不够。几周之后,会有人问“这个文件确实是那天从那台服务器上取出来的吗”。要为每个项目记录何时、谁、用什么收集的,以及内容指纹,并且把清单本身也封存起来。因为只写文件哈希的话,改掉清单就万事大吉了。 反过来,想全部收集的话,就会把客户个人信息整个带走。要划出界线,但不要悄悄去掉,而要把“已去掉”这一事实和原因留在记录里。 评分器不会相信你写下的文字。它用自己的计划和自己的范围文件真实运行你的收集器,核对顺序、时间和哈希,还会悄悄修改生成的证据包中的一个文件,看你的验证工具能否指出它的名字。
步骤
- 创建并运行 /root/evidence/gen_scene.py,生成 /root/evidence/host/。其中包含 2 个配置文件、2 个日志、1 份中央日志副本、6 个临时文件,以及生产数据库(customer 120 行、charge 360 行)。
- 在 /root/evidence/plan.json 中,按易失性从高到低依次写入八个项目。每个项目写明 id、volatility_class、command 和 why。
- 创建 /root/evidence/collect.py,让它严格按计划的顺序收集并留下清单。
- 让它为每个项目写入 sha256 和字节数,并在清单中写入收集者。为此增加
--collector。 - 封存清单本身,留下 MANIFEST.sha256。
- 创建 /root/evidence/verify.py,检查封存是否被破坏,并打印出被破坏文件的名称。
- 在 /root/evidence/scope.json 中写明决定不收集的项目和原因,并为此增加
--scope,使被排除的项目仍然留在记录中。 - 在 /root/evidence/bundle/ 中生成真正的证据包并验证,然后在 /root/evidence/evidence_report.md 中分四节书写。
参考
- 需要收集的八个项目如下。
| id | 是什么 | 从哪里读取 |
|---|---|---|
proc_table |
当前正在运行的进程列表 | 此 Pod 的进程表 |
net_state |
路由表和已打开的连接 | /proc/net/route、/proc/net/tcp |
kernel_stats |
内核统计和内存 | /proc/stat、/proc/meminfo |
tmp_files |
临时文件系统中留下的内容 | host/tmp |
app_logs |
应用日志 | host/var/log |
app_db |
生产数据库 | host/var/lib/app.db |
remote_logs |
从中央日志服务器取得的副本 | host/remote |
host_config |
配置和物理配置 | host/etc |
- 运行契约:
python3 /root/evidence/collect.py --plan <계획> --out <묶음 디렉터리> [--collector <이름>] [--scope <범위 파일>](占位符依次为计划文件、证据包目录、名称、范围文件)把一行摘要输出到标准输出,并以退出码 0 结束。如果无法读取计划文件或范围文件,则以 3 结束。 - 验证契约:
python3 /root/evidence/verify.py --bundle <묶음 디렉터리>(占位符为证据包目录)在证据包完好时以 0 结束,被破坏时以非 0 结束,并在标准输出中打印含有被破坏文件名称的行。 - 计划文件:
{"items": [{"id": …, "volatility_class": 1..7, "command": "셸 명령", "why": "왜 이 자리인가"}]}(占位符依次为 shell 命令与排序理由)。项目的顺序就是收集的顺序。收集器不做排序。 - 清单:在
<묶음>/manifest.json(占位符为证据包目录)中写入{"created_at", "collector", "items": [...]}。每个项目写入 id、order(从 1 开始)、volatility_class、command、collected_at 和 status;已收集的项目还要写入 file、exit_code、sha256 和 bytes,被排除的项目则写入 reason。 - 收集到的文件名为
<id>.txt,内容原样保存命令的标准输出。 - 封存:在
<묶음>/MANIFEST.sha256(占位符为证据包目录)中写入一行<manifest.json 의 sha256> manifest.json(占位符为 manifest.json 的 sha256)。 - 范围文件:
{"policy": …, "excluded": [{"id": …, "reason": …}]}。被排除的项目不生成文件,而是在清单中以 statusexcluded和 reason 留下记录。 - 时间用 RFC 3339 的 UTC 格式书写。必须逐项分别记录,顺序才能得到证明。
- 常见错误:把计划按等级重新排序(判断就藏进了代码里);整个证据包只写一个时间;只写文件哈希而不封存清单;悄悄去掉被排除的项目。
- 这八个项目的构成,以及把
app_db作为排除对象,是本实验的假设。RFC 3227 只规定各组的顺序,并没有规定应当去掉哪些项目。 - 参考文档:RFC 3227 的第 2.1 节写了易失性顺序,第 2.2 节写了应当避免的事。NIST SP 800-86 更广泛地讨论了同一主题,Python hashlib 文档和 RFC 3339 则是本实验使用的工具。
掌握现场
创建并运行 /root/evidence/gen_scene.py,生成 /root/evidence/host/。其中包含 etc 2 个文件、var/log 2 个文件、remote 1 个文件、tmp 6 个文件,以及 var/lib/app.db(customer 120 行、charge 360 行)。
在实验 Pod 里不可能触碰真正的客户服务器,所以只准备磁盘这一侧。易失性高的内容不需要模拟,因为这个 Pod 的 /proc 就是真的。创建完成后,用 tree 浏览一遍。
按易失性顺序制定计划
在 /root/evidence/plan.json 中,按易失性从高到低依次写入八个项目。每个项目写入 id、RFC 3227 的 volatility_class(1..7)、实际要运行的 command,以及说明为什么排在这个位置的 why。
直接把 RFC 3227 第 2.1 节的七组作为等级使用。进程表、内核统计和路由表属于同一组,接下来是临时文件系统,再接下来是磁盘,再接下来是远程日志数据,最后是物理配置。command 必须真正运行并输出内容。
按计划收集
创建 /root/evidence/collect.py,让它严格按计划的顺序收集,并留下 <묶음>/manifest.json(占位符为证据包目录)。每个项目写入 id、order、volatility_class、command、collected_at、file、exit_code 和 status。
不要把计划按等级重新排序,一旦排序,判断就藏进了代码里。命令用 bash 运行,标准输出原样保存到 <id>.txt。时间必须逐项分别记录,顺序才能得到证明。
记录何时、谁、用什么
增加 --collector <이름>(占位符为名称),并让它为每个已收集的项目写入 sha256 和 bytes,在清单中写入 collector。
几周之后,会有人问“这个文件确实是那天从那台服务器上取出来的吗”。那时能回答的,就是收集时间、收集者、实际执行的命令,以及内容指纹。sha256 要读取整个文件来计算。
清单也要封存
让收集器在清单写完后计算该文件的 sha256,以 <해시> manifest.json 的形式写成一行,保存到 <묶음>/MANIFEST.sha256(占位符依次为哈希值与证据包目录)。
只把文件哈希写进清单的话,改掉清单就万事大吉了。封存无法防止伪造,但能发现在不知不觉中发生的变化:用编辑器打开后保存了,或者复制过程中被截断了,这种情况实际上常见得多。
指出被破坏的文件名称
创建 /root/evidence/verify.py。用 --bundle <묶음>(占位符为证据包目录)把封存和文件哈希进行比较,完好时以 0 结束,被破坏时以非 0 结束,并在标准输出中打印含有被破坏文件名称的行。
只打印“封存已被破坏”的验证工具,会让人一直守在那里。必须指出是什么被破坏了,才会有下一步行动。清单被改动和收集到的文件被改动这两种情况都要能发现,而被排除的项目没有文件才是正常的。
把决定不收集的内容留在记录里
在 /root/evidence/scope.json 中写入 policy 和 excluded,排除 app_db,并给 collect.py 增加 --scope <파일>(占位符为文件名)。被排除的项目不生成文件,而是在清单中以 status excluded 和 reason 留下记录。
支付服务器的生产数据库里有姓名、邮箱和电话号码。一旦把它转储下来带走,我们就不再是调查者,而成了新的风险。重要的是不要悄悄去掉:只有“已去掉”这件事留在记录里,之后才知道该申请什么。
生成证据包并出具报告
在 /root/evidence/bundle/ 中生成真正的证据包(同时给出计划和范围),用 verify.py 验证后,在 /root/evidence/evidence_report.md 中分 ## 무엇을 어떤 차례로 모았나 ## 무엇을 모으지 않았나 ## 봉인과 검증 ## 남은 위험 四节书写(韩文标题,依次意为“按什么顺序收集了什么”“没有收集什么”“封存与验证”“剩余风险”)。
报告不要手写,而是从清单生成。项目名称和等级、被排除的项目及原因、封存值和验证命令都要包含在内,下一个人才能原样重新验证。