DEV Community

Cover image for Why Wazuh Missed React2Shell
Dolan
Dolan

Posted on • Edited on

Why Wazuh Missed React2Shell

Introduction

The security community was recently shaken by React2Shell, a critical unauthenticated Remote Code Execution (RCE) vulnerability. It was initially tracked under two CVEs:

  • CVE-2025-55182 – React

  • CVE-2025-66478 – Next.js

With a CVSS score of 10.0, this vulnerability impacts Next.js 15/16 (App Router) and any framework relying on React 19’s RSC implementation.

Wazuh published a great blog explaining how to detect this vulnerability:

👉 https://wazuh.com/blog/detecting-next-js-cve-2025-66478-rce-vulnerability-with-wazuh/

However, after doing deeper research, I found a critical limitation that applies not only to this CVE, but to most modern application stacks.

The Detection Gap: Why SIEMs Are Blind to Local Projects
Standard vulnerability detectors (including Wazuh’s default modules) excel at finding packages installed via system managers like apt or npm install -g. But modern development happens elsewhere:

Node.js: Most projects install dependencies locally in the project folder.
Python: Virtual environments (venv, uv) isolate packages from the system.
Docker: Packages are buried inside container layers (/var/lib/docker/...).

If your SIEM isn't looking at these local paths, you are flying blind.

So if a vulnerable version of Next.js or React exists inside a project directory, Wazuh will miss it.

I explained this problem earlier here:

👉 https://dev.to/0xdolan/why-you-shouldnt-rely-solely-on-detectors-373g

Wazuh provides several features that help, but none fully solve this problem.

IT Hygiene / Vulnerability Detection

👉 https://documentation.wazuh.com/current/getting-started/use-cases/it-hygiene.html#it-hygiene

👉 https://documentation.wazuh.com/current/user-manual/capabilities/vulnerability-detection/index.html

  • Detects globally installed packages only

Misses:

  • Local Node.js projects
  • Python venvs
  • Docker containers

File Integrity Monitoring (FIM)

👉 https://documentation.wazuh.com/current/user-manual/capabilities/file-integrity/index.html

  • Detects file changes

Does NOT extract:

  • Package names
  • Versions
  • Vulnerability status

Command Monitoring

👉 https://documentation.wazuh.com/current/user-manual/capabilities/command-monitoring/how-it-works.html

  • Can run scripts from the Wazuh manager
  • High risk
  • If manager is compromised → full agent compromise
  • Not recommended for frequent scans

So I needed a safer and more accurate approach.

The Solution: A Custom Inventory Workflow

We can’t rely on static scanners. Instead, we need a proactive script that:

  1. Crawls the filesystem for package.json files.
  2. Extracts version data for specific high-risk libraries (React/Next.js).
  3. Logs these to a JSON file.
  4. Feeds that file into Wazuh for real-time alerting.

Step 1: The Inventory Script

We use a Python script designed to be lightweight. Crucially, it does not exclude /var/lib/docker, allowing us to find vulnerable libraries inside container images.

Save as /usr/local/bin/scan_react2shell.py

#!/usr/bin/env python3

import json
import os
import sys
import time

# --- CONFIGURATION ---
LOG_FILE = "/var/log/react2shell_scan.json"
SEARCH_PATHS = ["/"]

# Excludes (Docker paths are NOT excluded so we can scan them)
EXCLUDE_DIRS = {"/proc", "/sys", "/dev", "/run", "/tmp", "/snap", "/boot"}


def scan_system():
    print(f"[*] Starting Inventory Scan on: {SEARCH_PATHS}")

    # Initialize Log File
    if not os.path.exists(LOG_FILE):
        try:
            with open(LOG_FILE, "w") as f:
                pass
            print(f"[*] Created new log file at {LOG_FILE}")
        except IOError as e:
            print(f"[!] Error creating log file: {e}")
            return

    count_inspected = 0

    for root_dir in SEARCH_PATHS:
        for dirpath, dirnames, filenames in os.walk(root_dir, followlinks=True):
            # Skip excluded directories
            if any(exclude in dirpath for exclude in EXCLUDE_DIRS):
                dirnames[:] = []
                continue

            if "package.json" in filenames:
                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 pkg_name:
                    full_path = os.path.join(dirpath, "package.json")
                    try:
                        with open(
                            full_path, "r", encoding="utf-8", errors="ignore"
                        ) as f:
                            data = json.load(f)
                            ver = data.get("version", "0.0.0")

                            print(f"[+] Found {pkg_name} v{ver} at {full_path}")
                            count_inspected += 1

                            # --- DOCKER TAGGING LOGIC ---
                            display_path = full_path
                            if (
                                "/var/lib/docker" in full_path
                                or "/var/lib/containerd" in full_path
                            ):
                                display_path = f"[DOCKER] {full_path}"

                            log_entry = {
                                "timestamp": time.strftime("%Y-%m-%dT%H:%M:%S"),
                                "scan_type": "react_inventory",
                                "package": pkg_name,
                                "version": ver,
                                "path": display_path,
                            }

                            with open(LOG_FILE, "a") as lf:
                                lf.write(json.dumps(log_entry) + "\n")

                    except Exception as e:
                        pass

    print(f"[*] Scan complete. Total Packages Logged: {count_inspected}")


if __name__ == "__main__":
    scan_system()

Enter fullscreen mode Exit fullscreen mode

Note: This script scans for package.json and specifically flags packages like react and next while identifying if they are inside a Docker path.

Sample Output:

{"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"}

Enter fullscreen mode Exit fullscreen mode

Step 2: Wazuh Integration

Once the script generates /var/log/react2shell_scan.json, we need Wazuh to read it.

1. Configure the Agent:

Add this to the Wazuh agent or via centralized configuration (ossec.conf):

<localfile>
  <log_format>json</log_format>
  <location>/var/log/react2shell_scan.json</location>
</localfile>
Enter fullscreen mode Exit fullscreen mode

Pushing the Configuration Centrally

To avoid manually editing the ossec.conf file on every server, we utilize Wazuh’s Centralized Configuration feature. By modifying the agent.conf file on the Manager, we can instruct all agents to start "watching" our custom inventory log.

On the Wazuh Manager:

Edit the shared configuration file for the default group:
vim /var/ossec/etc/shared/default/agent.conf

Add the following block inside the tags:

<agent_config>
  <!-- 
    This tells every agent to monitor the output of our inventory script.
    Wazuh will collect these JSON logs and send them to the manager for analysis.
  -->
  <localfile>
    <log_format>json</log_format>
    <location>/var/log/react2shell_scan.json</location>
  </localfile>
</agent_config>
Enter fullscreen mode Exit fullscreen mode

How it works:

  1. Synchronization: Once you save this file on the Manager, it automatically calculates a new MD5 checksum.
  2. Propagation: The next time your agents check in (heartbeat), they will detect the configuration change, download the updated agent.conf file, and restart their log collector engine.
  3. Visibility: From this point forward, any time your Python script (run via cron) updates /var/log/react2shell_scan.json, the data is instantly streamed to the Wazuh Manager to be checked against your custom React2Shell rules.

2. Create the Detection Rules:

In your Wazuh Manager (/var/ossec/etc/rules/local_rules.xml), add rules to flag specific vulnerable versions based on the Vercel and React security bulletins.

<group name="react_inventory,cve_2025_55182,cve_2025_66478,vulnerability,">
  <!-- INVENTORY: Log EVERY React/Next package found-->
  <rule id="100500" level="3"> 
    <decoded_as>json</decoded_as> 
    <field name="scan_type">react_inventory</field> 
    <description>Inventory: Found $(package) version $(version) at $(path).</description> 
  </rule>

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


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

Step 3: Deployment via Ansible

How do you get this onto 100 servers? Don't use Wazuh's "Command Monitoring" for script execution. Giving a SIEM manager the right to run arbitrary scripts on all agents is a major security risk if the manager is compromised.

Instead, use Ansible. Create a playbook to:

  1. Copy the Python script.
  2. Set up a Cron job (e.g., daily at 2 AM).
  3. Ensure the log file permissions are correct.

Notifications / Messenger Integration

To receive real-time alerts in your favorite messenger app, you can use these community integrations:

Wazuh-Telegram-Slack-Teams

Conclusion

By shifting from "System Inventory" to "Application Inventory," we caught a CVSS 10.0 vulnerability that otherwise would have stayed hidden in a local node_modules folder.

Top comments (0)