跳到正文
Hacker News· datosh·· 7 天前AI 评分36

NixOS 中 VEX 的惊人有效性

The Unreasonable Effectiveness of Vex in NixOS

AI 导读

面对今年 LLM 辅助漏洞报告激增,NixOS 展示了用 VEX(Vulnerability Exploitability eXchange)文档自动化漏洞分诊的思路。

正文 · AI 翻译
  • 2026年10月11日,星期日
  • 7分钟阅读

今年,LLM辅助生成的漏洞报告大量涌现,由此我们听到了许多关于热门(开源)项目维护者应对这些报告的艰难处境。大多数项目已经放缓了功能开发,转而专注于漏洞分类处理;另一些项目仍在寻找应对这股洪流本身的方法。

虽然我们倾向于听到许多来自维护者的报告,但对于流程另一侧的人们,我们听到的较少:系统管理员、SRE和AppSec团队同样在应对这股洪流。新的(可能未修补的)CVE每天都在出现。每个CVE都需要评估,受影响的软件需要更新或手动打补丁。

如果你在这个领域待过一段时间,用不了多久就会听说VEX文档和声明。VEX代表Vulnerability Exploitability eXchange(漏洞可利用性交换):一种机器可读的安全公告,告诉你某个特定软件产品是否真的暴露于已知漏洞。

听起来很棒!但我们真的能以自动化方式生成这些声明,来支持可扩展的漏洞分类流程吗?答案一如既往:视情况而定。

基于SBOM的扫描

让我们来看看当今许多项目和组织中使用的安全流程。

给定一个简单的Go程序:

package main

import (
	"fmt"

	"golang.org/x/text/width"
)

func main() {
	fmt.Println(width.Widen.String("hello, world"))
}

使用golang.org/x/text库的v0.38.0:

module maybe-vulnerable

go 1.26.8

require golang.org/x/text v0.38.0

扫描程序漏洞的常见方法是:

  1. 生成软件物料清单(SBOM)
  2. 扫描SBOM中记录的组件以查找已知漏洞

每个使用这种方法的团队都会看到以下漏洞(截至2026年10月11日):

$ syft scan . -o cyclonedx-json > sbom.cdx
$ grype sbom:sbom.cdx
NAME               INSTALLED  FIXED IN  TYPE       VULNERABILITY  SEVERITY  EPSS         RISK
golang.org/x/text  v0.38.0    0.39.0    go-module  GO-2026-5970   High      0.5% (39th)  0.4
golang.org/x/text  v0.38.0    0.41.0    go-module  GO-2026-6629   High      0.3% (24th)  0.3

但如果我们做足功课,就会发现这两个漏洞实际上都不影响我们的程序。

GO-2026-5970只存在于golang.org/x/text/unicode/norm包中,而GO-2026-6629只存在于golang.org/x/text/secure/precis包中。我们的程序都没有使用它们!

我们可以利用这些信息,编写两份声明vulnerable_code_not_present的VEX声明,但这存在两个问题:

  1. 这个手动流程的规模会随着代码库大小和依赖数量的增长而增长。
  2. 每次更改代码时都必须重复完整的分析,因为你可能会开始使用某个易受攻击的函数。

可达性分析

幸运的是,编程语言高度结构化,因此(对机器来说)很容易对其进行推理。

除此之外,Go团队工作非常勤勉,在记录新漏洞时,会使用ecosystem_specific字段记录每个受影响的导入路径和符号。

这正是govulncheck的动力来源。它利用对我们源代码的静态分析和Go漏洞数据库,将报告缩小到仅可能影响应用程序的那些。在我们的示例应用程序上运行它会显示:

$ govulncheck .
=== Symbol Results ===

No vulnerabilities found.

Your code is affected by 0 vulnerabilities.
This scan also found 1 vulnerability in packages you import and 14 vulnerabilities in
modules you require, but your code doesn't appear to call these vulnerabilities.

如果你拥有这种能力,可以想象关闭Dependabot,只在受影响时打补丁。

这在操作系统上就行不通了

让我们看看这在操作系统上会如何运作。我们使用这个Dockerfile:

FROM debian:trixie@sha256:913f6706df59a68922d1dd08f78c2476560a8d367897200a6005b00e5f67c2d5
RUN apt-get update \
 && apt-get install -y --no-install-recommends openssh-server \
 && mkdir -p /run/sshd \
 && rm -rf /var/lib/apt/lists/*

然后像之前一样扫描:

$ docker build -t opensshpam .
$ syft scan docker:opensshpam -o cyclonedx-json > sbom.cdx
$ grype sbom:sbom.cdx
✔ Scanned for vulnerabilities     [284 vulnerability matches]
  ├── by severity: 3 critical, 65 high, 80 medium, 39 low, 97 negligible
  └── by status:   0 fixed, 284 not-fixed, 0 ignored
NAME             INSTALLED            FIXED_IN   TYPE  VULNERABILITY   SEVERITY    EPSS          RISK
[...]
openssh-server   1:10.0p1-7+deb13u4              deb   CVE-2007-2768   Negligible  8.6% (94th)   0.4
[...]

在这个演示中,我们将专注于单个漏洞CVE-2007-2768的分类流程,其余283个留给读者作为练习。🫠

简而言之,如果 sshd 通过 PAM 使用 OPIE 一次性密码,攻击者就能从登录提示中判断某个用户名是否存在。这个问题自 2007 年以来在上游一直未修复。

手动检查我们的 sshd 配置,可以看到 PAM 已启用,但键盘交互式认证被禁用,而这是 sshd 将登录提示交给 PAM 的唯一方式。

$ docker run --rm opensshpam sh -c 'sshd -T | grep -E "^(usepam|kbdinteractiveauthentication) "'
usepam yes
kbdinteractiveauthentication no

此外,我们可以看到没有配置名为 opie 的模块。

$ docker run --rm opensshpam grep -E '^(@include|auth)' /etc/pam.d/sshd /etc/pam.d/common-auth
/etc/pam.d/sshd:@include common-auth
/etc/pam.d/sshd:@include common-account
/etc/pam.d/sshd:@include common-session
/etc/pam.d/sshd:@include common-password
/etc/pam.d/common-auth:auth     [success=1 default=ignore]      pam_unix.so nullok
/etc/pam.d/common-auth:auth     requisite                       pam_deny.so
/etc/pam.d/common-auth:auth     required                        pam_permit.so
$ docker run --rm opensshpam sh -c 'find / -xdev -name "pam_opie*" 2>/dev/null; dpkg -l | grep -i opie'
# nothing is printed

每次配置变更都要对另外 283 个漏洞做这件事,根本不可行!

NixOS 的非凡效力

那么,NixOS 有什么不同?让我们看看这个系统,它非常接近我们之前那个基于 Docker 的操作系统示例:

{
  fileSystems."/" = {
    device = "/dev/sda1";
    fsType = "ext4";
  };
  boot.loader.grub.device = "nodev";
  system.stateVersion = "26.11";

  services.openssh.enable = true;
  services.openssh.settings.KbdInteractiveAuthentication = false;
}

使用以下 flake:

{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

  outputs =
    { nixpkgs, ... }:
    {
      nixosConfigurations.sshd = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [
          ./configuration.nix
        ];
      };
    };
}

将 syft 替换为 sbomnix,我们就可以运行与之前相同的过程:

$ sbomnix .#nixosConfigurations.sshd.config.system.build.toplevel
[...]
INFO     Wrote: sbom.cdx.json
$ grype sbom:sbom.cdx.json
✔ Scanned for vulnerabilities     [272 vulnerability matches]
  ├── by severity: 14 critical, 84 high, 152 medium, 22 low, 0 negligible
  └── by status:   147 fixed, 125 not-fixed, 0 ignored
NAME        INSTALLED    FIXED_IN   TYPE  VULNERABILITY   SEVERITY    EPSS          RISK
[...]
openssh     10.5p1                  nix   CVE-2007-2768   Medium      8.6% (94th)   4.0
[...]

终于,我们来到了收获的时刻。正如我们之前了解到的,CVE-2007-2768 只在某些条件成立时才会影响我们的系统,而 NixOS 让我们能够将这些条件代码化!

不仅如此,我们还可以有条件地生成 VEX 源,仅在条件仍然成立时保留 VEX 声明:

{
  config,
  lib,
  pkgs,
  ...
}:
let
  notAffected =
    !(pkgs ? opie)
    && config.services.openssh.settings.KbdInteractiveAuthentication == false
    && !(lib.any (rule: rule.enable && lib.hasInfix "opie" rule.modulePath) (
      lib.attrValues config.security.pam.services.sshd.rules.auth
    ));
in
{
  system.build.vex = pkgs.writeText "vex.json" (
    builtins.toJSON {
      "@context" = "https://openvex.dev/ns/v0.2.0";
      "@id" = "https://kammel.dev/vex/sshd";
      author = "Fabian Kammel";
      timestamp = "2026-10-11T00:00:00Z";
      version = 1;
      statements = lib.optional notAffected {
        vulnerability.name = "CVE-2007-2768";
        products = [ { "@id" = "pkg:nix/openssh"; } ];
        status = "not_affected";
        justification = "vulnerable_code_not_present";
      };
    }
  );
}

将其集成到我们的流程中:

$ VEX=$(nix build --no-link --print-out-paths .#nixosConfigurations.sshd.config.system.build.vex)
$ grype --vex $VEX sbom:sbom.cdx.json
 ✔ Scanned for vulnerabilities     [271 vulnerability matches]
   ├── by severity: 14 critical, 84 high, 152 medium, 22 low, 0 negligible
   └── by status:   147 fixed, 125 not-fixed, 1 ignored

这个已分诊的 CVE 现在被成功忽略了。

nixos-vex

如你所见,构建这些条件需要大量工作,但回报是巨大的。一旦某个 CVE 被分诊并代码化,重新检查的成本几乎为零(只需一点点 CI 开销)。

我一直在为我的 NixOS 机器做这类分诊工作,并将其开源为 nixos-vex。

查看示例 桌面 和 服务器 配置的演示,它们还展示了 nixos-vex 内置的一些其他功能。

我还不建议将其用作高可信度的数据源!

我的目标是分享这一流程,邀请反馈,并希望将来能将其回馈给 Nix 生态系统。

来源:Hacker News · blog.kammel.dev