Unauthenticated gRPC API Allows Ban/Unban and Notify-Script Execution
- CVE
- CVE-2026-48692
- CVSS
- 8.1 (High)
- CWE
- CWE-306 (Missing Authentication for Critical Function)
- Affected
- FastNetMon Community Edition <= 1.2.9
- Component
- src/fastnetmon.cpp line 477 (server init); src/api.cpp (all RPC methods)
- Attack Vector
- Local (network-adjacent if bind address is changed to 0.0.0.0)
- Discovered by
- Lorikeet Security
FastNetMon ships with a gRPC management API. It runs on port 50052 and exposes RPC methods for triggering and clearing bans (ExecuteBan / ExecuteUnBan), listing the current ban list (GetBanlist), and reading traffic counters (GetTotalTrafficCounters). These are the security-critical control-plane operations of the mitigation system: ExecuteBan announces BGP blackhole routes that send specified traffic to a discard interface; ExecuteUnBan withdraws those routes; both can trigger the configured notify script via popen().
The server is initialized with grpc::InsecureServerCredentials(). The source code at line 476 of src/fastnetmon.cpp contains a comment that, as documentation of intent, is extraordinarily clear:
// Listen on the given address without any authentication mechanism.
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
None of the RPC methods in src/api.cpp add their own credential checks. There is no TLS termination, no API token, no client certificate, no role-based authorization separating read-only counter queries from destructive ban/unban operations.
Disclosure status: Lorikeet Security notified FastNetMon LTD on April 25, 2026. CVE-2026-48692 was assigned by MITRE. No vendor response or fix as of May 23, 2026.
What an attacker can do with the API
The attack surface, assuming the attacker can connect to the gRPC port:
- Blackhole arbitrary IPs (
ExecuteBan). The attacker submits a target IP and FastNetMon instructs its BGP speaker to announce a discard route for that IP. The IP becomes unreachable on the protected network. This is a denial-of-service primitive against any destination the attacker chooses. If the FastNetMon instance announces to upstream providers, the blackhole propagates beyond the local network. - Disable active mitigations (
ExecuteUnBan). The attacker enumerates the current ban list viaGetBanlistand selectively removes bans, undoing legitimate DDoS mitigations. If FastNetMon is actively mitigating an ongoing attack, the attacker can disable that mitigation by unbanning the attacking IPs. - Trigger script execution (notify pipeline). Both
ExecuteBanandExecuteUnBaninvoke the configured notify script viapopen(). The script receives the attack IP and other metadata as arguments. The attacker influences these arguments. Combined with the command-injection vulnerabilities in the bundled Juniper and MikroTik notify scripts, this chains into arbitrary command execution. - Enumerate monitored networks (
GetTotalTrafficCounters). The attacker learns which subnets FastNetMon is monitoring and the current per-subnet traffic rates. This is reconnaissance — useful for planning targeted attacks against specific high-traffic prefixes or for confirming that FastNetMon does not have visibility into a particular subnet before launching an attack on it.
Reachability
The default bind address is 127.0.0.1:50052, which restricts access to processes running on the same host. The CVSS attack vector is "Local" under that default, because the attacker needs to be on the FastNetMon host to reach the API.
However, the bind address is a configurable option (fastnetmon_api_host in fastnetmon.conf). Operators who want to manage FastNetMon remotely — via Ansible, a custom dashboard, or a centralized orchestration system — change this to a management-network IP or to 0.0.0.0. The moment that bind address moves off localhost, the attack vector becomes network-adjacent or fully remote, and the same CVE becomes effectively CVSS 9.8 / Critical.
The "Local" rating in the CVSS score reflects the default configuration, but the rating is brittle. Any operator who has changed the bind address (and many have, for legitimate management reasons) is exposed at a much higher risk level than the score suggests.
Local-only is still serious
Even under the default localhost bind, "local" does not mean safe. Any of the following give an attacker the ability to invoke the API:
- A web application on the same host (a monitoring dashboard, a control panel) that has a server-side request forgery (SSRF) vulnerability.
- Any other process that the attacker has compromised, including unprivileged ones — the gRPC API does not check who is on the other end of the socket.
- An attacker who has gained shell access through any other means — even as a low-privilege user — can hit the API directly.
- A privilege escalation chain that starts from some other CVE in this disclosure series (e.g., RCE via CVE-2026-48686) and then uses the API for persistence.
Local-no-auth APIs are a routine source of "low-privilege user to root" escalations because they typically expose privileged operations that the daemon runs as root, without any check on who is asking.
The vulnerable code
// src/fastnetmon.cpp, around line 477
std::string server_address("127.0.0.1:50052");
ServerBuilder builder;
// Listen on the given address without any authentication mechanism.
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
builder.RegisterService(&api_service);
std::unique_ptr<Server> server(builder.BuildAndStart());
The comment is unambiguous about the design intent. There is no TODO that says "add auth later." There is no #ifdef wrapping the insecure credentials in an "INSECURE_DEV_MODE" guard. The API is, by design, listening without authentication. That design needs to change.
How a fix should look
// 1. Use mutual TLS with client certificate validation.
auto cert_chain = read_file("/etc/fastnetmon/server.crt");
auto private_key = read_file("/etc/fastnetmon/server.key");
auto ca_cert = read_file("/etc/fastnetmon/ca.crt");
grpc::SslServerCredentialsOptions ssl_options(
GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY);
ssl_options.pem_root_certs = ca_cert;
ssl_options.pem_key_cert_pairs.push_back({private_key, cert_chain});
builder.AddListeningPort(
server_address,
grpc::SslServerCredentials(ssl_options));
// 2. Add an authentication interceptor that maps client cert subject to a role.
builder.RegisterService(&api_service);
// 3. In each RPC handler, check the role against the operation:
Status ExecuteBan(ServerContext* ctx, const BanRequest* req, BanResponse* resp) override {
if (!require_role(ctx, Role::OPERATOR)) {
return Status(StatusCode::PERMISSION_DENIED, "ExecuteBan requires OPERATOR role");
}
// ... existing logic ...
}
Status GetTotalTrafficCounters(ServerContext* ctx,
const Empty* req,
CountersResponse* resp) override {
if (!require_role(ctx, Role::READ_ONLY)) {
return Status(StatusCode::PERMISSION_DENIED, "Read role required");
}
// ... existing logic ...
}
Three layers: TLS for transport security, client certificates for caller identity, role-based access control to separate destructive operations (ban/unban) from monitoring queries (counters/banlist). The gRPC library supports all three natively; the change is a few hundred lines plus configuration.
For deployments that want a lighter solution: a static API token in an Authorization metadata field, validated by a custom server interceptor, would close the unauthenticated-access gap at a fraction of the implementation cost. It's not as good as mTLS — tokens leak more easily — but it's vastly better than no auth at all.
Compensating controls
- Verify the bind address is
127.0.0.1. Checkfastnetmon.confforfastnetmon_api_hostand confirm it is not0.0.0.0or a management-network IP. If you need remote management, do it through SSH tunnels or a VPN, not by binding the gRPC port to a public interface. - Firewall localhost on shared hosts. If multiple services run on the FastNetMon host, use namespace isolation (containers, systemd's
PrivateNetwork) to prevent other services from reaching127.0.0.1:50052. This is harder than firewall rules but more correct —iptables -I INPUT -i lorules are notoriously tricky. - Restrict who has shell access to the FastNetMon host. Local-only auth is fine if only authorized administrators have local access. Treat the FastNetMon host as a privileged management box, not a general-purpose server.
- Audit your notify script. Since
ExecuteBaninvokes the notify pipeline, an attacker who can reach the gRPC API can trigger that pipeline. Make sure your notify script is hardened (the bundled Juniper and MikroTik scripts are not — see CVE-2026-48687 and CVE-2026-48695). - Log every gRPC call. FastNetMon does not log RPC invocations by default. Adding logging at the API boundary doesn't prevent the bug, but it gives you detective visibility into anything unusual.
The pattern: "internal" APIs are not safe APIs
This bug is part of a recurring pattern in infrastructure software: APIs designed for "internal" use are routinely shipped with no authentication, because the assumption is that whoever can reach them is already authorized. The assumption is wrong for three reasons:
- Local processes are not all trustworthy. A shared host runs many processes, some of which the operator did not write, and some of which may be compromised or buggy. Local APIs are routinely abused through other-process channels (SSRF in web apps on the same host, command injection in unrelated services, malicious unprivileged users).
- "Internal" boundaries move. The same API that started life as localhost-only gets exposed to a management network when an operator needs to manage it remotely. Then to a VPN. Then accidentally to the internet when someone misconfigures a firewall. The auth gap doesn't close itself when the boundary expands.
- Defense in depth matters. Even if every other security control is in place, an unauthenticated API is one missing layer in the defense. The cost of mTLS in a control-plane API is small. The benefit is structural: even if everything else is compromised, the attacker still has to forge a certificate to invoke privileged operations.
For projects that build infrastructure software: treat every control-plane API as a public API. Add authentication from day one. Don't ship with InsecureServerCredentials as the default, even for development; ship with a generated self-signed cert and clear instructions for how to roll out real certs in production. The cost of doing this right at the start is much smaller than the cost of changing it after a CVE.
Disclosure timeline
| Date | Event |
|---|---|
| 2026-04-25 | Vulnerability identified during Lorikeet Security source code audit of FastNetMon Community Edition 1.2.9 |
| 2026-04-25 | CVE ID requested from MITRE |
| 2026-04-25 | Vendor (Pavel Odintsov / FastNetMon LTD) notified at the contact published in SECURITY.md |
| 2026-05-22 | CVE-2026-48692 assigned by MITRE |
| TBD | Vendor response |
| TBD | Fix release |
| 2026-05-23 | Lorikeet Security publishes responsible disclosure report |
Full Responsible Disclosure Report (PDF)
Complete writeup of all 16 FastNetMon Community Edition vulnerabilities Lorikeet Security identified, including vulnerable-code excerpts, impact analysis, and remediation guidance for each CVE.
Auditing the network infrastructure your business depends on
Lorikeet Security finds the parser bugs, protocol confusion, and unauthenticated-control-plane issues that move your DDoS detector, your BGP speaker, and your edge devices from "running" to "exploitable." Source code review, fuzz harness development, and adversarial protocol testing.