devstuff/harden-docker-seccomp
GitHub: devstuff/harden-docker-seccomp
针对 CVE-2026-31431 内核本地提权漏洞,通过定制 seccomp 配置阻断 AF_ALG 套接字来加固 Docker 与 Kubernetes 容器环境的应急缓解工具。
Stars: 1 | Forks: 1
# harden-docker-seccomp
幂等脚本,用于在主机上阻止所有 Docker 容器创建 `AF_ALG` 套接字,
作为 **CVE-2026-31431**("Copy Fail")的缓解措施。
## 背景
CVE-2026-31431 是 Linux 内核 `authencesn` 加密模板中的一个本地提权漏洞,
存在于 2017 年至上游修复发布(主线提交 `a664bf3d603d`)之间构建的内核中。
非特权用户可以将 `AF_ALG` 套接字操作与 `splice()` 结合使用,
对任何可读文件的页缓存执行受控的 4 字节写入,从而将目标指向 setuid 二进制文件以获取 root shell。
一个 732 字节的 Python 概念验证程序可以可靠地利用此漏洞,无需竞态条件或针对特定发行版的偏移量,
适用于发布受影响内核的所有主要 Linux 发行版。
该漏洞利用的强制性第一步是打开 `AF_ALG` 套接字
(`socket(AF_ALG, SOCK_SEQPACKET, 0))。通过 seccomp 阻止该系统调用
即使在未修补的内核上也能防止被利用。Docker 的内置默认
seccomp 配置文件不阻止 `AF_ALG`,并且 `RuntimeDefault` 也不
足够——经过测试的集群表明,在 PSS Restricted 下准入的 Pod 仍然
可以打开 `AF_ALG` 套接字。
请参阅 上的原始研究员公告以及
上的 CERT-EU 公告,
以获取完整的技术细节和各发行版的补丁可用性信息。
### 本工具的范围
此脚本涵盖 Docker Engine 全局守护进程配置。对于
Kubernetes,请参阅下文的 [Kubernetes 章节](#kubernetes)。对于裸机或
VM 工作负载(非容器化),请改为禁用 `algif_aead` 内核模块:
```
echo "install algif_aead /bin/false" > /etc/modprobe.d/disable-algif.conf
rmmod algif_aead 2>/dev/null || true
```
该方法对 dm-crypt/LUKS、kTLS、IPsec/XFRM、OpenSSL、
GnuTLS、NSS 或 SSH 没有影响。
## 工作原理
1. **提取 Docker 活跃的内置 seccomp 配置文件**,通过检查
短暂存在容器的 `HostConfig.SecurityOpt`。这避免了
对远程 URL 的任何依赖,并保证基础配置文件与
实际安装的 Docker 版本相匹配。仅当容器检查
未返回任何结果时,才会使用来自
[`moby/profiles`](https://github.com/moby/profiles) 的 GitHub 获取作为备用方案。
2. **修补配置文件**,通过从 Docker 的白名单条目中移除 `socket` 并
使用参数过滤器重新添加它,该过滤器使用 `SCMP_CMP_NE`
允许除 `AF_ALG`(值为 `38`)之外的所有地址族。所有其他 Docker 默认的
seccomp 行为均予以保留。
3. **原子化写入修补后的配置文件**至
`/etc/seccomp/docker-block-af-alg.json`(临时文件 + 重命名)。如果
磁盘上的内容已完全相同则跳过。
4. **更新 `/etc/docker/daemon.json`**,将 `"seccomp-profile"` 设置为
修补后的配置文件路径。原始文件在首次修改时
备份至 `daemon.json.bak`。如果已正确配置则跳过。
5. **重新加载 `dockerd`**,通过 `systemctl reload docker`(SIGHUP —— 无需重启)。如果两个文件均未更改则跳过。
6. **验证阻止是否生效**,通过在容器内运行探测,
无论上述步骤中是否进行了任何更改。
该脚本是幂等的:多次运行会产生相同的结果,
并且仅在实际发生更改时才重新加载 Docker。
## 要求
- Python 3.12+
- 在主机上运行的 Docker Engine(非 Docker Desktop)
- `PATH` 中包含 `docker` CLI
- `PATH` 中包含 `curl`(仅作为备用)
- `systemctl`(systemd 主机)
- 需要 Root/sudo 权限以写入 `/etc/seccomp` 和 `/etc/docker`,并执行
`systemctl reload docker`
## 用法
```
# 应用缓解措施并验证(正常使用)
sudo python3 harden-docker-seccomp.py
# 显示将会发生什么更改,而不写入任何内容或重新加载 Docker
sudo python3 harden-docker-seccomp.py --dry-run
# 仅重新运行容器验证(无配置更改)
python3 harden-docker-seccomp.py --verify-only
# 详细输出
sudo python3 harden-docker-seccomp.py --verbose
```
### 预期输出(首次运行)
```
INFO Written: /etc/seccomp/docker-block-af-alg.json
INFO Updated: /etc/docker/daemon.json
INFO Reloading Docker daemon (SIGHUP)…
INFO Docker daemon reloaded.
INFO Verifying AF_ALG is blocked inside a container…
OK: AF_ALG blocked — [Errno 1] Operation not permitted
INFO All done. CVE-2026-31431 (Copy Fail) mitigation is active.
```
### 预期输出(后续运行)
```
INFO Profile unchanged: /etc/seccomp/docker-block-af-alg.json
INFO daemon.json already configured correctly.
INFO No changes — Docker daemon reload not required.
INFO Verifying AF_ALG is blocked inside a container…
OK: AF_ALG blocked — [Errno 1] Operation not permitted
INFO All done. CVE-2026-31431 (Copy Fail) mitigation is active.
```
## 退出代码
| 代码 | 含义 |
| ---- | ------- |
| `0` | 成功 —— 缓解措施已激活 |
| `1` | 脚本未以 root 身份运行(修补时)或发生不可恢复的错误 |
| `2` | 验证失败 —— `AF_ALG` 未被阻止 |
## Kubernetes
Kubernetes Pod 共享主机内核,因此在受影响的节点上,
任何 Pod 都可以访问相同的 `AF_ALG` 套接字原语。`RuntimeDefault` seccomp 并不
足够——经过测试的集群表明,在 PSS Restricted 下准入的 Pod 仍然
可以打开 `AF_ALG` 套接字。必须使用带有显式拒绝规则的 `Localhost` 配置文件。
缓解措施需要两件事:每个节点文件系统上存在配置文件 JSON,
以及每个 Pod 规约引用它。以下章节涵盖了这两点,
包括如何在不修改单个 Pod 规约的情况下全局注入配置文件。
### 步骤 1 —— 将配置文件分发到每个节点
kubelet 相对于其 seccomp 根目录解析 `Localhost` seccomp 配置文件,
该根目录默认为 `/var/lib/kubelet/seccomp`。在可能调度工作负载的每个节点上,
该路径必须存在配置文件。
应用此仓库中的 ConfigMap 和 DaemonSet:
```
kubectl apply -f templates/configmap-seccomp-profile.yaml
kubectl apply -f templates/daemonset-distribute-profile.yaml
```
DaemonSet 运行一个 init 容器,将配置文件从
ConfigMap 复制到节点的 kubelet seccomp 根目录,
然后停泊一个最小的 pause 容器,以便 Pod 保持可见以进行健康监控。
它容忍所有污点,因此也会在控制平面节点上运行。
验证文件是否存在于节点上:
```
kubectl -n kube-system exec -it \
$(kubectl -n kube-system get pod -l app.kubernetes.io/name=distribute-af-alg-seccomp \
-o jsonpath='{.items[0].metadata.name}') -- \
cat /var/lib/kubelet/seccomp/block-af-alg.json
```
### 步骤 2 —— 将配置文件注入到每个 Pod(无需更改 Pod 规约)
与其修改单个 Pod 规约或 Helm 图表,不如使用变更式
准入 Webhook 在准入时自动注入 `seccompProfile`。
提供了两种选项:Kyverno 和 OPA Gatekeeper。
这两种方法仅在 Pod 尚未声明配置文件时才注入,
因此具有显式配置文件的 Pod 将保持不变。
#### 选项 A —— Kyverno
此模板使用 `MutatingPolicy` API (`policies.kyverno.io/v1`),
该 API 在 Kyverno 1.17 中达到了 GA 阶段。旧的 `ClusterPolicy` API
(`kyverno.io/v1`) 在 Kyverno 1.17(2026 年 1 月)中已被弃用,并
计划在 1.20(2026 年 10 月)中移除;请勿将其用于新策略。
`matchConditions` CEL 表达式会在变更前检查 `seccompProfile` 是否不存在,
因此已声明配置文件的 Pod 将保持不变。
```
# 安装 Kyverno(如果尚未安装)
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
helm upgrade --install kyverno kyverno/kyverno -n kyverno --create-namespace
# 应用策略
kubectl apply -f templates/kyverno-mutate-seccomp.yaml
```
验证新 Pod 是否接收了注入的配置文件:
```
kubectl run test --image=busybox --restart=Never -- sleep 30
kubectl get pod test -o jsonpath='{.spec.securityContext.seccompProfile}'
# 预期:{"localhostProfile":"block-af-alg.json","type":"Localhost"}
kubectl delete pod test
```
参见 [`templates/kyverno-mutate-seccomp.yaml`](templates/kyverno-mutate-seccomp.yaml)。
#### 选项 B —— OPA Gatekeeper
Gatekeeper 的 `Assign` 变更 CRD 使用 `pathTests` 条件,仅在
`spec.securityContext.seccompProfile` 尚不存在时注入配置文件。
变更功能自 Gatekeeper 3.10+ 起已稳定;不需要特性标志。
```
# 安装 Gatekeeper(如果尚未安装)
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
helm install -n gatekeeper-system gatekeeper gatekeeper/gatekeeper \
--create-namespace
# 应用 mutation
kubectl apply -f templates/gatekeeper-assign-seccomp.yaml
```
验证:
```
kubectl run test --image=busybox --restart=Never -- sleep 30
kubectl get pod test -o jsonpath='{.spec.securityContext.seccompProfile}'
# 预期:{"localhostProfile":"block-af-alg.json","type":"Localhost"}
kubectl delete pod test
```
参见 [`templates/gatekeeper-assign-seccomp.yaml`](templates/gatekeeper-assign-seccomp.yaml)。
### 本工具未涵盖的内容
具有 `hostPID: true`、`hostNetwork: true` 或
`securityContext.privileged: true` 的 Pod 拥有更高的访问权限,仅靠 seccomp 无法
完全控制。请单独审计这些工作负载,并在可能的情况下移除权限。
### 模板设计说明
**ConfigMap 作为事实来源。** 配置文件 JSON 存储在
`configmap-seccomp-profile.yaml` 中,而不是嵌入在 DaemonSet 中或
在多个文件中重复。DaemonSet 挂载它并将其复制到节点。
更新配置文件意味着编辑一个 ConfigMap 并重启 DaemonSet
Pod —— 无需更改其他文件。
**DaemonSet 使用 `system-node-critical` 优先级。** 这确保了
分发 Pod 不会在其所保护的工作负载被调度之前被驱逐,
否则会导致节点缺少配置文件文件,并使 Pod 陷入
`CreateContainerError` 状态。
**Gatekeeper 排除了 `kube-system` 和 `gatekeeper-system`。** 向可能早于 DaemonSet
安装的系统 Pod 注入 `Localhost` 配置文件,如果节点上
尚不存在该文件,可能会导致配置文件引用损坏。Kyverno 策略不需要此排除项,
因为 Kyverno 能更优雅地处理 Webhook 排序,但如果需要,
也可以在那里添加 `exclude` 规则。
**条件注入,而非覆盖。** Kyverno `MutatingPolicy` 的 CEL `matchCondition` 和
Gatekeeper 的 `MustNotExist` 路径测试都意味着
准入策略仅在 Pod 没有现有 `seccompProfile` 时才生效。
已声明自己配置文件的工作负载 —— 包括那些通过自定义白名单
合法需要 `AF_ALG` 的工作负载 —— 将保持不变。
## 参考资料
- —— 原始研究员公告 (Xint)
- —— CERT-EU 公告
- —— CVE 记录
- —— Debian 安全追踪
- —— Docker seccomp 文档
- —— 规范的 Docker 默认 seccomp 配置文件
- —— Kyverno 策略文档
- —— Gatekeeper 变更文档
## 是的,大部分工作是由 Claude 完成的,这是聊天记录
标签:0day漏洞, AF_ALG, CERT-EU, CVE-2026-31431, DevSecOps, Docker, K8s模板, Linux内核, PoC, seccomp, SELinux, setuid, splice系统调用, Web截图, Web报告查看器, 上游代理, 子域名突变, 安全基线, 安全防御评估, 安全防护, 容器安全, 教学环境, 暴力破解, 本地提权, 漏洞修复, 特权升级, 等保合规, 系统加固, 网络安全, 网络安全培训, 请求拦截, 逆向工具, 隐私保护