Why Wazuh Missed React2Shell

Published: (December 28, 2025 at 03:36 AM EST)
6 min read
Source: Dev.to

Source: Dev.to

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:

👉

However, after 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. Modern development, however, happens elsewhere:

TechnologyTypical location of dependencies
Node.jsLocal node_modules folder inside each project
PythonVirtual environments (venv, uv) isolate packages from the system
DockerPackages are buried inside container layers (/var/lib/docker/...)

If your SIEM isn’t looking at these local paths, you are flying blind. Consequently, a vulnerable version of Next.js or React inside a project directory will be missed by Wazuh.

I explained this problem earlier here:

👉

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

IT Hygiene / Vulnerability Detection

  • Documentation:
  • Documentation:

What it does: Detects globally installed packages only.

What it misses:

  • Local Node.js projects
  • Python virtual environments
  • Docker containers

File Integrity Monitoring (FIM)

  • Documentation:

What it does: Detects file changes.

What it does NOT extract:

  • Package names
  • Versions
  • Vulnerability status

Command Monitoring

  • Documentation:

Pros: Can run scripts from the Wazuh manager.

Cons:

  • High risk – if the manager is compromised, all agents are compromised.
  • Not recommended for frequent scans.

So I needed a safer and more accurate approach.

The Solution: A Custom Inventory Workflow

Static scanners alone aren’t enough. 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).
  3. Logs the findings to a JSON file.
  4. Feeds that file into Wazuh for real‑time alerting.

Step 1: The Inventory Script

Below is a lightweight Python script that does not exclude /var/lib/docker, allowing us to find vulnerable libraries inside container images.

Save it as /usr/local/bin/scan_react2shell.py and make it executable (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()

What the script does

  • Walks the entire filesystem (starting at /).
  • Skips well‑known system directories that cannot contain project code.
  • Looks for package.json files inside node_modules/react or node_modules/next.
  • Extracts the version field and writes a JSON line to /var/log/react2shell_scan.json.
  • Tags any findings that reside under Docker’s storage paths (/var/lib/docker or /var/lib/containerd).

You can schedule this script with cron or a systemd timer and configure Wazuh to ingest the generated log file for alerting.

Next Steps

  1. Create a Wazuh localfile rule that watches /var/log/react2shell_scan.json and parses the JSON entries.
  2. Define an alert rule that triggers when the version matches a known vulnerable range (e.g., the ranges for React and Next.js).

Note: This script scans for package.json files and flags packages such as react and next, also detecting if they reside 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"}

Step 2 – Wazuh Integration

Once the script generates /var/log/react2shell_scan.json, configure Wazuh to ingest it.

1. Configure the Agent (localfile)

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

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

2. Push the Configuration Centrally

To avoid editing ossec.conf on every server, use Wazuh’s Centralized Configuration feature.

On the Wazuh Manager, edit the shared configuration for the default group:

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

Insert the block inside the <agent_config> tags:

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

How it works

  • Synchronization: Saving the file on the Manager generates a new MD5 checksum.
  • Propagation: On the next heartbeat, agents detect the change, download the updated agent.conf, and restart their log collector.
  • Visibility: Every time the Python script (run via cron) updates /var/log/react2shell_scan.json, the data is streamed to the Wazuh Manager for rule evaluation.

3. Create Detection Rules

Add the following rules to the manager’s local rules file (/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>

Step 3 – Deployment via Ansible

Deploy the script, cron job, and permissions to many servers without using Wazuh’s “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

Run the playbook:

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

Notifications / Messenger Integration

Receive real‑time alerts in your preferred chat platform using community integrations.

Wazuh‑Telegram‑Slack‑Teams

  • Slack:
  • Telegram:

Microsoft Teams Integration

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.

Back to Blog

Related posts

Read more »