NVD has published CVE-2026-129171, a critical CVSS 9.8 unauthenticated SQL injection vulnerability in NginxPulse — a monitoring and analytics component commonly deployed alongside nginx reverse proxies and load balancers. The vulnerability is remotely exploitable over the network with no authentication required, placing it in the highest-risk category for immediate remediation.
Any organization running NginxPulse versions 1.5.0 through 1.6.25 is exposed. Attackers can exploit this flaw to extract database contents via error-based SQL injection, and — critically — to read or write arbitrary files on the underlying server by abusing PostgreSQL superuser functions. This is not a data-leak-only scenario. File write capability via a database superuser is a well-worn path to remote code execution, webshell deployment, and full host compromise.
If NginxPulse is internet-facing — and in many deployments it is, either directly or behind an nginx reverse proxy that forwards API traffic — you should treat this as a drop-everything patching event.
Technical Analysis
Affected Products and Versions
| Attribute | Detail |
|---|---|
| Product | NginxPulse (nginx monitoring/analytics API) |
| Affected versions | 1.5.0 up to (but not including) 1.6.26 |
| Fixed version | 1.6.26 and later |
| CVE | CVE-2026-129171 |
| CVSS v3.1 Score | 9.8 (Critical) |
| Attack Vector | Network (AV:N) |
| Authentication Required | None |
| Database backend | PostgreSQL |
How the Vulnerability Works
The flaw resides in the /api/stats/:type endpoint of the NginxPulse API. Two parameters — id and locationType — are passed directly into a SQL query without sufficient sanitization or parameterization. Specifically, the vulnerable code path uses these parameters to construct a quoted table-name identifier in the SQL statement.
An attacker can break out of the quoted identifier context by injecting a closing quote followed by arbitrary SQL. The attack chain looks like this:
- Injection point: The attacker sends a crafted GET or POST request to
/api/stats/:typewith a maliciousidorlocationTypevalue containing a closing double-quote (") followed by injected SQL. - Error-based extraction: The application returns verbose database error messages (or behaves observably differently based on query success/failure), enabling error-based or boolean-based blind SQL injection to enumerate database contents — table names, column names, credentials, session tokens, configuration secrets.
- File read/write via PostgreSQL superuser: If the database connection runs under a PostgreSQL superuser account (common in poorly hardened deployments), the attacker can leverage built-in functions such as
pg_read_file(),pg_write_file(),pg_ls_dir(), andlo_import/lo_exportto read arbitrary files from the server filesystem (/etc/passwd, application configs, TLS private keys, SSH keys) or write files (webshells, cron jobs, authorized_keys entries). - Post-exploitation: File write access to the web root, application directory, or system paths enables webshell deployment, persistence, and lateral movement.
The fact that this requires zero authentication and is reachable via a standard API endpoint makes it trivially exploitable at scale by automated scanners and botnets.
Exploitation Status
As of this writing, CVE-2026-129171 has been published by NVD with a full technical description. Given the CVSS 9.8 severity, the unauthenticated network attack vector, and the well-documented exploitation technique, weaponized PoC development should be assumed imminent if not already underway. Organizations should operate under the assumption that internet-facing NginxPulse instances are being actively scanned.
Check CISA's Known Exploited Vulnerabilities (KEV) catalog for updates — if added, federal agencies will face a mandatory remediation deadline, and all organizations should treat it as confirmation of active exploitation.
Detection & Response
The following detection content targets the specific observable behaviors associated with exploitation of CVE-2026-129171: anomalous requests to the /api/stats/ endpoint containing SQL metacharacters, and PostgreSQL process behavior consistent with file read/write operations.
SIGMA Rules
---
title: NginxPulse SQL Injection Attempt via API Stats Endpoint
id: 3a7f2c91-8b4d-4e5a-bc12-9d1e6f0a2345
status: experimental
description: Detects HTTP requests to the NginxPulse /api/stats/ endpoint containing SQL injection metacharacters in the id or locationType parameters, consistent with CVE-2026-129171 exploitation.
references:
- https://nvd.nist.gov/vuln/detail/CVE-2026-129171
author: Security Arsenal
date: 2026/04/06
tags:
- attack.initial_access
- attack.t1190
logsource:
category: webserver
product: nginx
detection:
selection_uri:
cs-uri|contains: '/api/stats/'
selection_sqli:
cs-uri-query|contains:
- '%22'
- '%27'
- 'UNION'
- 'union'
- 'SELECT'
- 'select'
- 'pg_read_file'
- 'pg_write_file'
- 'pg_ls_dir'
- 'lo_import'
- 'lo_export'
- 'information_schema'
- 'pg_catalog'
- '--'
- '%2D%2D'
- ';--'
- '%3B'
condition: selection_uri and selection_sqli
falsepositives:
- Legitimate API requests with unusual but benign query strings
level: high
---
title: PostgreSQL Suspicious File Operation Functions
id: 8c1e4b72-3d5f-4a6b-9c01-2e7f8a1b3456
status: experimental
description: Detects PostgreSQL log entries indicating usage of superuser file read/write functions (pg_read_file, pg_write_file, pg_ls_dir, lo_import, lo_export) which may indicate post-exploitation via SQL injection such as CVE-2026-129171.
references:
- https://nvd.nist.gov/vuln/detail/CVE-2026-129171
- https://attack.mitre.org/techniques/T1505/003/
author: Security Arsenal
date: 2026/04/06
tags:
- attack.persistence
- attack.t1505.003
- attack.collection
- attack.t1005
logsource:
product: postgresql
service: postgresql
detection:
selection:
message|contains:
- 'pg_read_file'
- 'pg_write_file'
- 'pg_ls_dir'
- 'lo_import'
- 'lo_export'
- 'COPY ... TO PROGRAM'
- 'COPY ... FROM PROGRAM'
condition: selection
falsepositives:
- Database administrators performing legitimate maintenance operations
- Backup scripts using COPY TO/FROM
level: high
---
title: Webshell Write in NginxPulse or Web Root Directory
id: 5f2a8d13-6c7e-4b8d-ae34-1f9c0d2e4567
status: experimental
description: Detects file creation events with common webshell extensions in NginxPulse application directories or web root paths, which may indicate post-exploitation file write via SQL injection (CVE-2026-129171).
references:
- https://nvd.nist.gov/vuln/detail/CVE-2026-129171
- https://attack.mitre.org/techniques/T1505/003/
author: Security Arsenal
date: 2026/04/06
tags:
- attack.persistence
- attack.t1505.003
logsource:
category: file_event
product: linux
detection:
selection_path:
TargetFilename|contains:
- '/usr/share/nginx'
- '/var/www'
- '/opt/nginxpulse'
- '/etc/nginx'
selection_ext:
TargetFilename|endswith:
- '.php'
- '.jsp'
- '.jspx'
- '.asp'
- '.aspx'
- '.py'
- '.cgi'
- '.war'
condition: selection_path and selection_ext
falsepositives:
- Legitimate application deployments or updates
level: high
KQL — Microsoft Sentinel / Defender
The following KQL queries are designed for environments ingesting nginx access logs via CEF/Syslog into Sentinel, and for hunting PostgreSQL process activity on endpoints.
// Hunt 1: NginxPulse SQLi attempts in nginx access logs ingested via Syslog/CEF
CommonSecurityLog
| where TimeGenerated > ago(7d)
| where RequestURL contains "/api/stats/"
| where RequestURL has_any ("pg_read_file", "pg_write_file", "pg_ls_dir", "lo_import", "lo_export", "information_schema", "UNION", "SELECT", "%22", "%27", "--")
| project TimeGenerated, SourceIP, RequestURL, RequestMethod, DeviceAction, DestinationIP, DestinationPort
| order by TimeGenerated desc
// Hunt 2: Syslog-ingested nginx logs with SQLi patterns targeting NginxPulse
Syslog
| where TimeGenerated > ago(7d)
| where SyslogMessage contains "/api/stats/"
| where SyslogMessage has_any ("pg_read_file", "pg_write_file", "UNION", "SELECT", "information_schema", "%22", "--")
| project TimeGenerated, Computer, SyslogMessage
| order by TimeGenerated desc
// Hunt 3: Endpoint hunt — PostgreSQL process spawning unexpected child processes (post-exploitation)
DeviceProcessEvents
| where TimeGenerated > ago(7d)
| where InitiatingProcessFileName =~ "postgres" or InitiatingProcessFileName =~ "postmaster"
| where FileName !in~ ("postgres", "postmaster", "pg_ctl", "psql", "pg_dump", "pg_restore", "vacuumdb")
| project TimeGenerated, DeviceName, InitiatingProcessFileName, FileName, ProcessCommandLine, AccountName
| order by TimeGenerated desc
Velociraptor VQL
For DFIR teams running Velociraptor across Linux server fleets, this artifact hunts for evidence of SQL injection exploitation and post-exploitation artifacts on hosts running NginxPulse.
-- Hunt for NginxPulse exploitation artifacts: suspicious processes spawned by PostgreSQL
-- and recently written script files in web/application directories
SELECT Pid, Name, CommandLine, Exe, Username, CreateTime
FROM pslist()
WHERE Name =~ 'postgres'
AND CommandLine =~ '(pg_read_file|pg_write_file|pg_ls_dir|lo_import|lo_export|COPY.*PROGRAM)'
-- Separately, hunt for recently created script files in web and NginxPulse directories
SELECT FullPath, Size, Mtime, Ctime
FROM glob(globs=[
'/var/www/**/*.php',
'/var/www/**/*.py',
'/var/www/**/*.cgi',
'/usr/share/nginx/**/*.php',
'/opt/nginxpulse/**/*.sh',
'/opt/nginxpulse/**/*.py'
])
WHERE Mtime > now() - 604800
ORDER BY Mtime DESC
Remediation Script
The following Bash script identifies running NginxPulse instances, checks the installed version against the vulnerable range, and applies the patch. Run this on every server hosting NginxPulse.
#!/bin/bash
# CVE-2026-129171 — NginxPulse Unauthenticated SQL Injection
# Remediation and verification script
# Run as root or with sudo
set -euo pipefail
echo "=============================================="
echo "CVE-2026-129171 NginxPulse Remediation Script"
echo "=============================================="
# --- Step 1: Check if NginxPulse is installed ---
echo ""
echo "[*] Checking for NginxPulse installation..."
NginxPulse_FOUND=false
# Check common installation paths
for dir in /opt/nginxpulse /usr/local/nginxpulse /srv/nginxpulse; do
if [ -d "$dir" ]; then
NginxPulse_FOUND=true
INSTALL_DIR="$dir"
echo "[+] Found NginxPulse at: $INSTALL_DIR"
fi
done
# Check for running process
if pgrep -f "nginxpulse" > /dev/null 2>&1; then
NginxPulse_FOUND=true
echo "[+] NginxPulse process is running"
pgrep -af "nginxpulse"
fi
if [ "$NginxPulse_FOUND" = false ]; then
echo "[-] NginxPulse not found on this system. No action needed."
exit 0
fi
# --- Step 2: Determine installed version ---
echo ""
echo "[*] Determining installed NginxPulse version..."
# Try multiple methods to get version
VERSION=""
if command -v nginxpulse &> /dev/null; then
VERSION=$(nginxpulse --version 2>/dev/null | grep -oP '\d+\.\d+\.\d+' | head -1)
elif [ -f "${INSTALL_DIR:-/opt/nginxpulse}/VERSION" ]; then
VERSION=$(cat "${INSTALL_DIR}/VERSION" | grep -oP '\d+\.\d+\.\d+' | head -1)
elif [ -f "${INSTALL_DIR:-/opt/nginxpulse}/package.json" ]; then
VERSION=$(grep -oP '"version"\s*:\s*"\K[^"]+' "${INSTALL_DIR}/package.json" | head -1)
fi
if [ -z "$VERSION" ]; then
echo "[!] Could not determine version automatically. Check manually."
echo "[!] If version >= 1.5.0 and < 1.6.26, this system is VULNERABLE."
exit 1
fi
echo "[+] Detected version: $VERSION"
# --- Step 3: Check if vulnerable ---
# Vulnerable: >= 1.5.0 AND < 1.6.26
version_gte() {
printf '%s\n%s\n' "$2" "$1" | sort -V -C
}
version_lt() {
! version_gte "$1" "$2"
}
if version_gte "$VERSION" "1.5.0" && version_lt "$VERSION" "1.6.26"; then
echo ""
echo "[!!!] VULNERABLE — Version $VERSION is affected by CVE-2026-129171"
echo "[!!!] Immediate action required."
echo ""
# --- Step 4: Attempt upgrade ---
echo "[*] Attempting to upgrade NginxPulse to 1.6.26 or later..."
# Try package manager first
if command -v apt-get &> /dev/null; then
echo "[*] Using apt..."
apt-get update -qq
apt-get install -y --only-upgrade nginxpulse 2>/dev/null || echo "[!] apt upgrade failed — check for manual update"
elif command -v yum &> /dev/null; then
echo "[*] Using yum..."
yum update -y nginxpulse 2>/dev/null || echo "[!] yum upgrade failed — check for manual update"
elif command -v dnf &> /dev/null; then
echo "[*] Using dnf..."
dnf update -y nginxpulse 2>/dev/null || echo "[!] dnf upgrade failed — check for manual update"
fi
# Check for Docker deployment
if command -v docker &> /dev/null; then
NginxPulse_CONTAINERS=$(docker ps --format '{{.Names}} {{.Image}}' 2>/dev/null | grep -i nginxpulse || true)
if [ -n "$NginxPulse_CONTAINERS" ]; then
echo ""
echo "[+] Found NginxPulse Docker containers:"
echo "$NginxPulse_CONTAINERS"
echo "[*] Pull latest image and recreate containers:"
echo " docker pull nginxpulse/nginxpulse:latest"
echo " docker-compose up -d --force-recreate"
fi
fi
echo ""
echo "[*] Post-upgrade: verify the new version is >= 1.6.26"
echo "[*] Restart the NginxPulse service after upgrading:"
echo " systemctl restart nginxpulse"
else
echo "[+] Version $VERSION is NOT in the vulnerable range (1.5.0 – 1.6.25)."
echo "[+] No patching required for CVE-2026-129171."
fi
# --- Step 5: Check for signs of compromise ---
echo ""
echo "[*] Checking for indicators of compromise..."
# Check nginx access logs for SQLi patterns against /api/stats/
LOG_PATHS=("/var/log/nginx/access.log" "/var/log/nginx/access.log.1" "/var/log/nginx/*/access.log")
for logpath in "${LOG_PATHS[@]}"; do
for logfile in $logpath; do
if [ -f "$logfile" ]; then
HITS=$(grep -c '/api/stats/' "$logfile" 2>/dev/null || echo 0)
if [ "$HITS" -gt 0 ]; then
echo "[+] Found $HITS requests to /api/stats/ in $logfile"
SUSPICIOUS=$(grep '/api/stats/' "$logfile" | grep -ciE '(union|select|pg_read_file|pg_write_file|pg_ls_dir|lo_import|lo_export|information_schema|%22|%27|--)' 2>/dev/null || echo 0)
if [ "$SUSPICIOUS" -gt 0 ]; then
echo "[!!!] WARNING: $SUSPICIOUS suspicious SQLi-like requests found in $logfile"
echo "[!!!] Investigate immediately — possible exploitation attempts"
grep '/api/stats/' "$logfile" | grep -iE '(union|select|pg_read_file|pg_write_file|pg_ls_dir|lo_import|lo_export|information_schema|%22|%27|--)' | tail -20
fi
fi
fi
done
done
# Check PostgreSQL logs for file operation functions
PG_LOG_DIRS=("/var/log/postgresql" "/var/lib/pgsql/data/log")
for pgdir in "${PG_LOG_DIRS[@]}"; do
if [ -d "$pgdir" ]; then
PG_HITS=$(grep -rl 'pg_read_file\|pg_write_file\|pg_ls_dir\|lo_import\|lo_export' "$pgdir" 2>/dev/null || true)
if [ -n "$PG_HITS" ]; then
echo "[!!!] WARNING: PostgreSQL logs contain file operation function calls:"
echo "$PG_HITS"
echo "[!!!] Investigate for post-exploitation activity"
fi
fi
done
echo ""
echo "=============================================="
echo "Remediation check complete."
echo "If IOCs were found, escalate to your IR team."
echo "=============================================="
Remediation
Immediate Actions (within 24 hours)
-
Upgrade NginxPulse to version 1.6.26 or later. This is the only complete fix. The vendor advisory and patched packages should be available through your standard distribution channel or directly from the NginxPulse project. Refer to the NVD entry for CVE-2026-129171 for links to vendor advisories as they are published.
-
If you cannot patch immediately, restrict network access to the
/api/stats/endpoint at the reverse proxy or firewall level. The following nginx configuration snippet blocks external access to the vulnerable endpoint:
# Add to your nginx server block to block /api/stats/ from untrusted sources
# This is a TEMPORARY workaround — patching is the only complete fix
# Option 1: Deny all external access to /api/stats/
location /api/stats/ {
allow 10.0.0.0/8; # Internal RFC1918 ranges only
allow 172.16.0.0/12;
allow 192.168.0.0/16;
deny all;
}
# Option 2: Return 403 for any request with SQL metacharacters in query string
location /api/stats/ {
if ($args ~* "(union|select|pg_read_file|pg_write_file|pg_ls_dir|lo_import|lo_export|information_schema|%22|%27|--)") {
return 403;
}
proxy_pass http://backend;
}
- Audit PostgreSQL database user privileges. The NginxPulse application should connect to PostgreSQL using a least-privilege account — not a superuser. If the database connection uses a superuser role, the file read/write impact of this vulnerability is fully realized. Revoke superuser privileges and grant only the minimum required permissions:
# Connect to PostgreSQL and audit the application's database role
sudo -u postgres psql -c "\du"
# Revoke superuser from the application account (replace 'nginxpulse_user' with actual role)
sudo -u postgres psql -c "ALTER ROLE nginxpulse_user NOSUPERUSER;"
# Verify the role no longer has superuser
sudo -u postgres psql -c "SELECT rolname, rolsuper FROM pg_roles WHERE rolname = 'nginxpulse_user';"
- Rotate credentials and secrets that may have been exposed via database extraction — database passwords, API keys, session secrets, TLS private keys stored in the database.
Medium-Term Actions (within 7 days)
-
Hunt for historical exploitation. Use the SIEM queries and scripts provided above to review at least 30 days of nginx access logs and PostgreSQL logs for exploitation indicators.
-
Verify no webshells or persistence mechanisms were deployed. Check web-accessible directories for unexpected script files, review cron jobs, systemd timers, and SSH authorized_keys for unauthorized additions.
-
Add the vulnerable endpoint to your WAF ruleset. If you operate a web application firewall, deploy a virtual patch rule blocking SQL injection patterns against
/api/stats/as defense-in-depth even after upgrading. -
Monitor CISA KEV. If CVE-2026-129171 is added to the CISA Known Exploited Vulnerabilities catalog, federal civilian agencies will have a binding remediation deadline. All organizations should treat KEV inclusion as confirmation that active exploitation is occurring.
The Bottom Line
This is a textbook worst-case web vulnerability: unauthenticated, network-reachable, critical severity, with a path from data exfiltration to remote code execution. The /api/stats/ endpoint is exactly the kind of API surface that gets exposed to the internet without anyone realizing it — embedded in monitoring dashboards, proxied through load balancers, or left accessible after a proof-of-concept deployment that was never properly locked down.
If you run NginxPulse, treat this with the same urgency you would give an internet-facing RCE. Patch now, hunt for compromise, and lock down your database service accounts.
Related Resources
Security Arsenal Penetration Testing Services AlertMonitor Platform Book a SOC Assessment vulnerability-management Intel Hub
Is your security operations ready?
Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.