为什么 Wazuh 错过了 React2Shell

发布: (2025年12月28日 GMT+8 16:36)
9 分钟阅读
原文: Dev.to

Source: Dev.to

请提供您希望翻译的完整文本内容,我将为您翻译成简体中文并保留原有的格式、Markdown 语法以及代码块和链接。谢谢!

介绍

安全社区最近被 React2Shell 震动,这是一种严重的未认证远程代码执行(RCE)漏洞。它最初被记录在两个 CVE 中:

  • CVE-2025-55182 – React
  • CVE-2025-66478 – Next.js

该漏洞的 CVSS 评分为 10.0,影响 Next.js 15/16(App Router) 以及任何依赖 React 19 的 RSC 实现 的框架。

Wazuh 发布了一篇很棒的博客,解释了如何检测此漏洞:

👉

然而,经过更深入的研究,我发现了一个关键限制,这不仅适用于此 CVE,也适用于大多数现代应用栈。

检测盲区:为什么 SIEM 对本地项目视而不见

标准的漏洞检测器(包括 Wazuh 的默认模块)擅长查找通过系统管理器(如 aptnpm install -g)安装的包。现代开发却在别处进行:

技术依赖项的典型位置
Node.js每个项目内部的本地 node_modules 文件夹
Python虚拟环境(venvuv)将包与系统隔离
Docker包埋在容器层中(/var/lib/docker/...

如果你的 SIEM 没有检查这些本地路径,你就像在盲目飞行。因此,项目目录中的 Next.js 或 React 的易受攻击版本会被 Wazuh 漏掉。

我之前在这里解释过这个问题:

👉

Wazuh 提供了若干功能来帮助,但没有一个能完全解决此问题。

IT 卫生 / 漏洞检测

  • 文档:
  • 文档:

它的作用: 仅检测全局安装的包。

它的盲点:

  • 本地 Node.js 项目
  • Python 虚拟环境
  • Docker 容器

文件完整性监控 (FIM)

  • 文档:

它的作用: 检测文件变更。

它不提取的内容:

  • 包名
  • 版本
  • 漏洞状态

命令监控

  • 文档:

优点: 可以从 Wazuh 管理器运行脚本。

缺点:

  • 风险高——如果管理器被攻破,所有代理都会受影响。
  • 不建议用于频繁扫描。

因此,我需要一种 更安全、更精准的方法

解决方案:自定义清单工作流

仅靠静态扫描器是不够的。我们需要一个主动脚本来:

  1. 在文件系统中爬取 package.json 文件。
  2. 提取特定高风险库(reactnext)的版本信息。
  3. 将发现记录到 JSON 文件中。
  4. 将该文件导入 Wazuh,实现实时告警。

步骤 1:清单脚本

下面是一个轻量级的 Python 脚本,不会排除 /var/lib/docker,从而能够在容器镜像内部找到易受攻击的库。

将其保存为 /usr/local/bin/scan_react2shell.py 并赋予可执行权限(chmod +x /usr/local/bin/scan_react2shell.py)。

#!/usr/bin/env python3

import json
import os

# --- CONFIGURATION -------------------------------------------------
LOG_FILE      = "/var/log/react2shell_scan.json"
SEARCH_PATHS = ["/"]                     # Roots to scan
EXCLUDE_DIRS = {"/proc", "/sys", "/dev", "/run", "/tmp", "/snap", "/boot"}

# ------------------------------------------------------------------
def scan_system():
    print(f"[*] Starting inventory scan on: {SEARCH_PATHS}")

    # Ensure log file exists
    if not os.path.exists(LOG_FILE):
        try:
            open(LOG_FILE, "w").close()
            print(f"[*] Created new log file at {LOG_FILE}")
        except IOError as e:
            print(f"[!] Could not create log file: {e}")
            return

    inspected = 0

    for root_dir in SEARCH_PATHS:
        for dirpath, dirnames, filenames in os.walk(root_dir, followlinks=True):
            # Skip excluded directories
            if any(excl in dirpath for excl in EXCLUDE_DIRS):
                dirnames[:] = []          # Prevent walk into sub‑dirs
                continue

            if "package.json" not in filenames:
                continue

            # Identify if the package is a top‑level react/next module
            parent_dir      = os.path.basename(dirpath)
            grandparent_dir = os.path.basename(os.path.dirname(dirpath))

            pkg_name = None
            if grandparent_dir == "node_modules":
                if parent_dir == "react":
                    pkg_name = "react"
                elif parent_dir == "next":
                    pkg_name = "next"

            if not pkg_name:
                continue

            pkg_path = os.path.join(dirpath, "package.json")
            try:
                with open(pkg_path, "r", encoding="utf-8", errors="ignore") as f:
                    data = json.load(f)
                version = data.get("version", "0.0.0")
                print(f"[+] Found {pkg_name} v{version} at {pkg_path}")
                inspected += 1

                # Tag Docker locations for easier downstream processing
                display_path = pkg_path
                if "/var/lib/docker" in pkg_path or "/var/lib/containerd" in pkg_path:
                    display_path = f"[DOCKER] {pkg_path}"

                # Append entry to log file
                entry = {
                    "package": pkg_name,
                    "version": version,
                    "path": display_path,
                    "timestamp": int(time.time())
                }
                with open(LOG_FILE, "a", encoding="utf-8") as log_f:
                    log_f.write(json.dumps(entry) + "\n")

            except (IOError, json.JSONDecodeError) as e:
                print(f"[!] Error reading {pkg_path}: {e}")

    print(f"[*] Scan complete – inspected {inspected} vulnerable packages.")
    return

if __name__ == "__main__":
    scan_system()

脚本功能说明

  • 从根目录 / 开始遍历整个文件系统。
  • 跳过已知的系统目录,这些目录不可能包含项目代码。
  • node_modules/reactnode_modules/next 中查找 package.json 文件。
  • 提取 version 字段,并将一行 JSON 写入 /var/log/react2shell_scan.json
  • 对位于 Docker 存储路径(/var/lib/docker/var/lib/containerd)下的发现进行标记,以便后续处理。

rd`).

您可以使用 cron 或 systemd 定时器来调度此脚本,并配置 Wazuh 读取生成的日志文件以触发告警。

后续步骤

  1. 创建 Wazuh localfile 规则,监控 /var/log/react2shell_scan.json 并解析其中的 JSON 条目。
  2. 定义告警规则,当版本匹配已知的漏洞范围时触发(例如 React 和 Next.js 的受影响版本区间)。

注意: 此脚本会扫描 package.json 文件,标记 reactnext 等包,并检测它们是否位于 Docker 路径中。

示例输出

{"timestamp":"2025-12-11T13:15:07","scan_type":"react_inventory","package":"react","version":"19.2.1","path":"[DOCKER]/var/lib/docker/.../node_modules/react/package.json"}
{"timestamp":"2025-12-11T13:15:08","scan_type":"react_inventory","package":"next","version":"15.4.5","path":"/home/user/app/node_modules/next/package.json"}

Step 2 – Wazuh 集成

一旦脚本生成了 /var/log/react2shell_scan.json,请配置 Wazuh 以读取该文件。

1. 配置 Agent(localfile)

在 Wazuh Agent 配置文件 (ossec.conf) 通过集中配置,添加以下块:

<localfile>
  <log_format>json</log_format>
  <location>/var/log/react2shell_scan.json</location>
</localfile>

2. 集中推送配置

为避免在每台服务器上手动编辑 ossec.conf,请使用 Wazuh 的 集中配置 功能。

在 Wazuh Manager 上,编辑默认组的共享配置:

vim /var/ossec/etc/shared/default/agent.conf

将块插入 <agent_config> 标签内:

<agent_config>
  <localfile>
    <log_format>json</log_format>
    <location>/var/log/react2shell_scan.json</location>
  </localfile>
</agent_config>

工作原理

  • 同步化: 在 Manager 上保存文件会生成新的 MD5 校验和。
  • 传播: 下一次心跳时,Agent 检测到变化,下载更新后的 agent.conf 并重启日志收集器。
  • 可见性: 每当 Python 脚本(通过 cron 运行)更新 /var/log/react2shell_scan.json,数据就会被流式传输到 Wazuh Manager 进行规则评估。

3. 创建检测规则

将以下规则添加到 Manager 的本地规则文件 (/var/ossec/etc/rules/local_rules.xml):

<rule id="100500" level="12">
  <if_sid>100500</if_sid>
  <field name="type">json</field>
  <field name="program_name">react_inventory</field>
  <description>Inventory: Found $(package) version $(version) at $(path).</description>
</rule>

<rule id="100501" level="12">
  <if_sid>100500</if_sid>
  <field name="package">react</field>
  <field name="version">^19\.0\.0$|^19\.1\.[0-1]$|^19\.2\.0$</field>
  <cve>CVE-2025-55182</cve>
  <reference>https://nvd.nist.gov/vuln/detail/CVE-2025-55182</reference>
  <description>Vulnerable React2Shell detected: React $(version) at $(path)</description>
  <mitre> T1190 </mitre>
</rule>

<rule id="100502" level="12">
  <if_sid>100500</if_sid>
  <field name="package">next</field>
  <field name="version">^15\.0\.[0-4]$|^15\.1\.[0-8]$|^15\.4\.[0-7]$|^16\.0\.[0-6]$</field>
  <cve>CVE-2025-66478</cve>
  <reference>https://nvd.nist.gov/vuln/detail/CVE-2025-66478</reference>
  <description>Vulnerable React2Shell detected: Next.js $(version) at $(path)</description>
  <mitre> T1190 </mitre>
</rule>

第3步 – 使用 Ansible 部署

将脚本、cron 任务和权限部署到多台服务器 而不 使用 Wazuh 的 “Command Monitoring”。

---
- name: Deploy React2Shell inventory scanner
  hosts: all
  become: true

  vars:
    script_src: files/react2shell_scan.py
    script_dest: /usr/local/bin/react2shell_scan.py
    log_file: /var/log/react2shell_scan.json
    cron_job: "0 2 * * * /usr/bin/python3 {{ script_dest }}"

  tasks:
    - name: Copy Python scanner script
      copy:
        src: "{{ script_src }}"
        dest: "{{ script_dest }}"
        mode: '0755'

    - name: Ensure log file exists with correct permissions
      file:
        path: "{{ log_file }}"
        state: touch
        owner: wazuh
        group: wazuh
        mode: '0640'

    - name: Install daily cron job
      cron:
        name: "React2Shell inventory scan"
        minute: "0"
        hour: "2"
        job: "{{ cron_job }}"
        user: root

运行 playbook:

ansible-playbook -i inventory.yml deploy-react2shell.yml

通知 / 消息集成

在您偏好的聊天平台上通过社区集成接收实时警报。

Wazuh‑Telegram‑Slack‑Teams

  • Slack:
  • Telegram:

Microsoft Teams 集成

结论

通过将 “System Inventory” 转换为 “Application Inventory”, 我们捕获了一个 CVSS 10.0 漏洞,否则它会隐藏在本地 node_modules 文件夹中。

Back to Blog

相关文章

阅读更多 »