Go 主机安全面试:WebShell 落地与文件篡改检测
WebShell 检测经常把文件系统、Web 目录、进程行为和告警降噪串起来考。面试官不只关心你知道 eval、assert 这些关键字,还会追问 Agent 怎么采、怎么关联、怎么降低误报,以及 Go 侧如何控制资源。
岗位场景
text
攻击入口:Web 漏洞 / 弱口令 / 文件上传
-> Web 目录写入脚本文件
-> Web 进程解释执行
-> 读取敏感文件 / 执行系统命令
-> 外连、提权或持久化
-> HIDS/EDR 还原证据链并告警主机安全研发要做的是把“文件变化”和“进程行为”放到同一个时间线里,而不是只靠文件内容关键字打一个孤立告警。
1. WebShell 落地检测为什么不能只扫文件内容?
简答:内容扫描能发现一部分样本,但容易被混淆绕过,也解释不了文件是谁写的、何时执行的。
关键知识点:
- WebShell 可能用编码、拼接、反射、动态加载绕过关键字。
- 同一个危险函数在测试脚本、框架缓存里也可能合法出现。
- 文件内容只能说明“像不像”,进程和时间关系才能说明“怎么来的”。
- 检测证据最好包括写入进程、文件路径、文件 hash、后续执行行为。
Go 落地思路:
- 文件内容扫描只做候选筛选。
- 采集文件创建、修改、重命名事件,补充进程上下文。
- 告警输出保留命中片段、文件元数据和关联进程证据。
2. Web 目录文件变化至少要采哪些字段?
简答:要能回答“谁在什么时间把什么文件写到了哪里,文件后来有没有被访问或执行”。
建议字段:
| 字段 | 作用 |
|---|---|
path / old_path | 识别创建、覆盖、重命名 |
op | 区分 create、write、chmod、rename、delete |
uid / gid | 判断写入身份 |
pid / ppid | 关联写入进程和父进程 |
sha256 | 去重、样本分析和复核 |
mtime / ctime | 还原文件变化时间 |
Go 落地思路:
- 文件事件里先记录路径和操作类型,hash 可以异步计算。
- 对大文件设置 hash 大小上限,避免拖垮 Agent。
- 采集失败要记录原因,例如权限不足、文件已删除、路径不可读。
3. Linux 上文件事件可以从哪里采集?
简答:
inotify简单,audit/eBPF 证据更强,生产上通常组合使用。
| 来源 | 适合用途 | 主要问题 |
|---|---|---|
| inotify | 监控固定 Web 目录变化 | 事件没有完整进程上下文 |
| audit | 文件审计、进程关联 | 配置复杂,规则量影响性能 |
| eBPF | 低开销采集 open/write/rename | 内核版本和权限兼容成本 |
| 周期扫描 | hash 基线和兜底校验 | 实时性差,IO 成本要控 |
Go 落地思路:
- 小范围目录变化可以先用 inotify 类能力,复杂场景再上 audit/eBPF。
- 采集层只负责把事件拿准,检测逻辑放到 Go 规则或服务端。
- 对降级状态要上报,例如“当前只有周期扫描,没有进程上下文”。
4. 如何判断一次文件写入可能是 WebShell?
简答:看路径、后缀、内容特征、写入进程和后续访问行为的组合。
常见组合:
text
Web 目录新增 .php/.jsp/.asp 文件
+ 写入进程来自 web 服务或异常脚本
+ 内容命中危险函数或混淆特征
+ 文件随后被 web 进程读取或触发命令执行
=> 高风险 WebShell 落地关键知识点:
.php、.jsp、.aspx、.jspx、.phtml都是高关注后缀。- 上传目录、临时目录、静态资源目录出现脚本文件更可疑。
- 只看后缀会误报,框架发布、插件安装也会写入脚本。
Go 实现要点:
go
func LooksLikeWebShell(path, content string) bool {
p := strings.ToLower(path)
c := strings.ToLower(content)
return strings.HasSuffix(p, ".php") &&
(strings.Contains(c, "eval(") || strings.Contains(c, "system("))
}这类函数只适合做第一层筛选,真正告警还要叠加写入进程、目录画像和访问行为。
5. 如何降低文件篡改规则的误报?
简答:给 Web 目录建立变化画像,发布窗口和可信路径降级,异常写入再升级。
误报来源:
- 正常上线发布写入大量文件。
- CMS 插件安装、模板更新。
- 日志、缓存、上传文件持续变化。
- 安全扫描器投放测试样本。
降噪策略:
| 场景 | 处理方式 |
|---|---|
| 发布窗口内批量变更 | 聚合成一条低风险变更 |
| 可信 CI/CD 用户写入 | 降级并保留审计 |
| 上传目录出现脚本文件 | 升级风险 |
| Web 用户写入可执行脚本 | 升级风险 |
Go 落地思路:
- 去重 key 可以用
host_id + path + sha256 + rule_id。 - 白名单要有范围:路径、用户、进程、时间窗口。
- 告警里记录降噪原因,方便客户复盘。
6. 文件 hash 应该同步算还是异步算?
简答:小文件可以同步,大文件和高频变化要异步,否则采集链路会被 IO 卡住。
关键知识点:
- WebShell 通常文件较小,但不能假设所有文件都小。
- 同步 hash 会阻塞事件处理 goroutine。
- 文件可能刚收到事件就被删除或覆盖,要处理读取失败。
Go 落地思路:
- 事件采集 goroutine 只入队基础事件。
- hash worker 有并发上限和文件大小上限。
- 读取失败不丢事件,标记
hash_status=failed。
go
const MaxHashBytes = 2 << 20
func ShouldHash(size int64) bool {
return size > 0 && size <= MaxHashBytes
}7. 怎么把文件事件和进程事件关联起来?
简答:用
pid + start_time + time_window关联写入进程,再用路径和后续命令执行补证据。
关联链路:
text
file_write: /var/www/html/upload/a.php by pid=3021
within 10s
process_exec: php-fpm -> sh -c whoami
same path or same web vhost
=> WebShell 写入后被触发执行Go 落地思路:
- 进程上下文缓存要带启动时间,避免 PID 复用串案。
- 文件事件先关联最近进程,关联不到也要保留原始事件。
- 服务端可以做更长窗口的攻击链还原,Agent 不要无限缓存。
8. 客户反馈“WebShell 误报”时怎么定位?
简答:先复核证据链,再判断是发布行为、规则过宽、白名单缺失还是采集字段不准。
排查顺序:
- 看规则版本、命中文件、hash、命中片段。
- 看写入进程、用户、父进程和是否处于发布窗口。
- 看文件是否被 Web 进程访问,是否触发命令执行或外连。
- 对比同目录历史变化频率和客户发布系统路径。
- 如果确认误报,补上下文条件或白名单,不要直接删除规则。
Go 研发要点:
- 告警结构保存
rule_id、rule_version、evidence。 - 规则要能用历史事件回放,验证修改后是否漏报。
- 日志区分采集失败、hash 失败、规则异常和上报失败。
学习要点
- Linux 文件系统基础:inode、权限、mtime、ctime、rename 原子性。
- WebShell 常见落地路径、脚本后缀和危险函数。
- 文件事件采集来源:inotify、audit、eBPF、周期扫描。
- Go Agent 资源控制:队列上限、worker 并发、hash 大小限制。
- 检测工程:证据链、误报降噪、规则回放、攻击链还原。
小练习
- 设计一个
FileEvent结构体,字段要能支持 WebShell 落地溯源。 - 给
LooksLikeWebShell补 3 个测试用例:普通 PHP、evalWebShell、非 PHP 文本。 - 设计一个去重 key,要求同一文件同一 hash 只告警一次。
- 如果发布系统会批量写入 PHP 文件,你会用哪些字段把它和 WebShell 区分开?
