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报告查看器, 上游代理, 子域名突变, 安全基线, 安全防御评估, 安全防护, 容器安全, 教学环境, 暴力破解, 本地提权, 漏洞修复, 特权升级, 等保合规, 系统加固, 网络安全, 网络安全培训, 请求拦截, 逆向工具, 隐私保护