Go 主机安全面试:Linux 文件系统元数据与 inode 篡改检测
主机安全里,很多高价值告警都和文件系统有关:WebShell 落地、配置被改、日志被清空、后门脚本被替换、启动项被投放。面试官通常会追问:为什么只看路径不够?inode、mtime、ctime、hard link 这些字段怎么用?Go Agent 怎么在不拖慢主机的前提下把文件变化讲成一条可解释证据链?
岗位场景
text
Web RCE / 提权 / 账户接管
-> 写入、替换、删除、重命名关键文件
-> 修改权限、属主、时间戳、链接关系
-> 关联进程、用户、目录画像和文件 hash
-> 还原攻击链并压低误报这类题考的不是“会不会看文件变化”,而是你能不能把路径、inode、进程和时间线合到一起,判断文件是被正常发布还是被入侵改写。
高频面试题
1. 为什么主机安全不能只靠文件路径做检测?
简答:路径会变,inode 才更接近文件实体。只看路径容易被重命名、硬链接、符号链接和挂载切换绕过。
关键知识点:
rename会改变路径,不一定改变文件内容。hard link让同一 inode 对应多个路径。symlink指向目标路径,表面看到的文件不一定是真正被访问的对象。- 容器、
overlayfs、bind mount 会让“路径看起来一样”的文件,背后实体并不一样。
Go 落地思路:
- 事件模型同时保存
path、old_path、dev、inode、mode、uid、gid。 - 规则判断不要只匹配字符串,优先看文件身份和操作类型。
- 展示告警时先说“哪个实体被改”,再说“在哪个路径上看到”。
2. inode、dentry、文件描述符分别代表什么?
简答:inode 表示文件元数据实体,dentry 是目录项缓存,文件描述符是进程打开文件后的句柄。
关键知识点:
- inode 里有权限、属主、大小、时间戳等核心元数据。
- dentry 更像“路径名到 inode 的映射”,不是文件本体。
- 文件描述符是进程级资源,能说明“谁在操作这个文件”。
- 只看
stat不够,必要时还要结合lstat、readlink、/proc/<pid>/fd。
Go 落地思路:
- 采集层把对象拆成“路径视角”和“实体视角”两份信息。
- 检测层优先按
dev + inode聚合,再补路径。 - 复盘时如果多个路径指向同一 inode,要能一眼看出是硬链接还是重命名。
3. 哪些文件操作最值得做安全检测?
简答:优先看能改变“内容、权限、归属、可执行性、可见性和链接关系”的操作。
重点操作:
| 操作 | 风险点 |
|---|---|
openat + O_TRUNC | 文件被直接清空或重写 |
write / pwrite | 内容被落地或覆盖 |
renameat2 | 原文件被替换、路径被切换 |
unlinkat | 文件被删除但可能还被进程持有 |
chmod / fchmod | 可执行位、SUID、权限漂移 |
chown / fchown | 属主变化,可能是提权前奏 |
linkat / symlinkat | 伪装入口或隐藏真实目标 |
truncate / ftruncate | 日志、配置、脚本被清零 |
Go 落地思路:
- 先把高风险操作单独建模,不要把所有文件事件都当同一种。
- 对高频目录做分级,例如 Web 目录、启动项目录、审计日志目录、
/etc。 - 采集时保留原始操作码,标准化时再映射成
FileCreate、FileModify、FileDelete、FilePermChange。
4. 怎么判断文件变化是攻击还是正常发布?
简答:看执行主体、时间窗口、目录画像、文件形态和相邻行为。单看“文件变了”不够。
关键知识点:
- 正常发布通常是批量、成组、可回滚的。
- 攻击写入更常是单点、临时目录、异常父进程和可疑后续执行。
logrotate、包管理、CI/CD、镜像启动都会改文件,但模式比较稳定。- 只要有“写入后立即执行、写入后外联、写入后提权”,风险就会上升。
Go 落地思路:
- 给目录加画像,例如
webroot、config、log、startup、tmp。 - 告警逻辑把“变更量”“变更主体”“变更时间”一起打分。
- 发布窗口内批量改动可以降级,但关键系统路径不要直接白名单化。
go
func suspiciousRewrite(oldSize, newSize int64, critical bool) bool {
if !critical || oldSize <= 0 {
return false
}
return newSize == 0 || newSize < oldSize/5
}5. Go 侧应该怎么设计文件事件模型?
简答:模型要稳定、能聚合、能回放,还要能表达“对象是谁、被谁、在何时、以什么方式改了”。
建议字段:
go
type FileObject struct {
HostID string
Path string
OldPath string
DevMajor uint32
DevMinor uint32
Inode uint64
Op string
Size int64
Mode uint32
UID uint32
GID uint32
MTime int64
CTime int64
PID int
PPID int
ProcName string
Cmdline string
}关键知识点:
- 字段不要一开始铺太满,但
dev + inode + path + op + time不能少。 - 原始证据和标准化字段都要保留,方便复盘和规则回放。
- 事件 ID 要稳定,否则去重和补偿会乱。
Go 落地思路:
- 采集、标准化、检测、上报分层。
- 大字段如
cmdline、raw、hash尽量异步补全。 - 队列满了要可观测丢弃,不要默默阻塞主链路。
6. 文件 hash 应该在采集时算吗?
简答:不建议全量同步算。小文件、低频目录可以算,大文件和高频写入要异步。
关键知识点:
- 同步 hash 会拖慢采集链路,尤其是大文件或网络盘。
- 文件可能在你读之前就被删掉或覆盖。
- hash 的价值在于去重、样本复核和跨主机关联,不是替代文件元数据。
Go 落地思路:
- 采集 goroutine 只入队基础事件,hash worker 单独限流。
- 设定最大 hash 大小和超时,超限就降级。
- 读失败不要丢事件,记
hash_status=failed。
7. 怎么把文件变化和进程行为关联起来?
简答:用
pid + start_time + time_window关联写入者,再补目录画像和后续访问行为。
关键知识点:
- 只有文件变化,没有进程上下文,很难判断是不是攻击。
PID会复用,所以必须带启动时间或更稳定的进程身份。- 先写后执行、先改权限再落地脚本、先删日志再退出,都是常见链路。
Go 落地思路:
- 进程缓存和文件缓存都要有 TTL。
- 文件事件先找最近的进程画像,找不到也不要丢原始事件。
- 服务端可以做更长窗口的链路还原,Agent 侧只做轻量关联。
8. 容器和 overlayfs 场景下,文件检测为什么更难?
简答:因为看到的路径不一定对应宿主机真实文件,层叠文件系统、挂载命名空间和临时层会让路径语义变弱。
关键知识点:
- 容器内
/etc/passwd可能映射到不同层,宿主机路径不等于容器路径。 overlayfs上同一路径的上层和下层含义不同。bind mount、只读挂载和临时卷会影响是否能落地、能否持久化。- 线上排障时如果不标记命名空间信息,很容易串案。
Go 落地思路:
- 事件里保留 mount namespace、container ID、mount point。
- 规则按主机和容器分别建画像,别直接混用。
- 容器内高频变更目录应默认低权重,关键系统路径仍然高权重。
学习要点
- Linux 文件系统基础:inode、dentry、fd、
rename、unlink、truncate、chmod、chown。 - 检测方法:路径画像、inode 关联、目录基线、进程上下文、hash 异步补全。
- 采集手段:
inotify、fanotify、audit、eBPF、周期扫描。 - 工程重点:分层、限流、TTL 缓存、去重、回放、降噪。
- 容器场景:命名空间、挂载点、
overlayfs、只读层和持久化层。
小练习
- 设计一个
FileIdentity,要求能区分同路径不同实体、同实体不同路径和重命名场景。 - 写一条规则,识别“Web 目录文件被
truncate后又被同一进程重新写入”的行为。 - 如果客户说“发布时误报很多”,你会先看哪 4 个字段或指标?
- 试着解释:为什么
chmod +s、chown root、rename这三类变化在提权和持久化里都值得盯。
