Why Wazuh Missed React2Shell
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– ReactCVE-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:
| Technology | Typical location of dependencies |
|---|---|
| Node.js | Local node_modules folder inside each project |
| 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. 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:
- Crawls the filesystem for
package.jsonfiles. - Extracts version data for specific high‑risk libraries (
react,next). - Logs the findings to a JSON file.
- 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.jsonfiles insidenode_modules/reactornode_modules/next. - Extracts the
versionfield and writes a JSON line to/var/log/react2shell_scan.json. - Tags any findings that reside under Docker’s storage paths (
/var/lib/dockeror/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
- Create a Wazuh localfile rule that watches
/var/log/react2shell_scan.jsonand parses the JSON entries. - 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.

- Slack:
- Telegram:
Microsoft Teams Integration
- Microsoft Teams: https://github.com/0xdolan/wazuh-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.