Out-of-Bounds Read in NetFlow v9 Data Flowset Processing — the Options Branch Checks, the Data Branch Doesn't
- CVE
- CVE-2026-48683
- CVSS
- 7.5 (High)
- CWE
- CWE-125 (Out-of-bounds Read)
- Affected
- FastNetMon Community Edition <= 1.2.9
- Component
- src/netflow_plugin/netflow_v9_collector.cpp, lines 1695-1702
- Attack Vector
- Remote (NetFlow v9 UDP, default port 2055)
- Discovered by
- Lorikeet Security
FastNetMon's NetFlow v9 collector binds to 0.0.0.0:2055 by default and accepts flow data from any sender that can route a UDP packet to it. NetFlow v9 is a template-based protocol: routers first push a "template" packet that declares the field layout of subsequent data records, and then push "data" packets that reference one of those templates. The collector keeps templates in memory keyed by source-id + template-id and uses them to parse incoming data flowsets.
This template-driven design has been a source of memory-safety bugs in every major NetFlow implementation that has shipped over the past fifteen years, for one structural reason: the parser has to trust an attacker-controlled length field to decide how many bytes to read on each iteration of an inner loop. If you forget the bounds check on the inner loop, you walk straight off the end of the UDP receive buffer and into whatever heap memory happens to follow.
During a comprehensive source code review of FastNetMon Community Edition 1.2.9, Lorikeet Security identified exactly that omission in process_netflow_v9_data_flowset(). MITRE assigned CVE-2026-48683.
Disclosure status: Lorikeet Security notified FastNetMon LTD (Pavel Odintsov) on April 25, 2026 using the contact published in the project's SECURITY.md. CVE IDs were assigned by MITRE shortly thereafter. As of the publication of this report (May 23, 2026), no vendor response has been received and no fix has shipped. Operators running FastNetMon Community Edition through 1.2.9 should treat the NetFlow v9 collector as untrusted and apply the mitigations below.
The bug, side by side
The cleanest way to see this vulnerability is to look at the data-flowset branch and the options-flowset branch in the same function. They sit ten lines apart in src/netflow_plugin/netflow_v9_collector.cpp. They share the same problem shape — an inner loop that walks a buffer one record at a time, advancing by a length value that came in over the wire — but only one of them performs the bounds check that this pattern requires.
// Data flowset branch -- lines 1695-1702
// NO per-iteration bounds check
for (uint32_t i = 0; i < num_flowsets; i++) {
netflow9_flowset_to_store(pkt + offset, ...);
offset += field_template->total_length;
}
// Options flowset branch -- lines 1709-1719
// HAS per-iteration bounds check (correct)
for (uint32_t i = 0; i < num_flowsets; i++) {
if (pkt + offset + field_template->total_length > packet_end) {
return false;
}
netflow9_options_flowset_to_store(pkt + offset, ...);
offset += field_template->total_length;
}
The two branches were written by the same developer at the same time. The options branch contains the check exactly as it should be written. The data branch simply does not contain the check at all. There is no defensive comment, no #ifdef, no rationale. The conclusion is unambiguous: this is an oversight, not a design decision.
Why the omission produces an out-of-bounds read
The flowset processing loop is driven by two attacker-controlled inputs:
num_flowsets— computed from the flowset_length header field divided by the template's total_length. An attacker who controls the flowset_length can push this loop count arbitrarily high.field_template->total_length— the per-record byte count declared by the template the attacker previously installed. The collector trusts whatever the attacker put in the template.
By installing a template that declares a total_length of, say, 200 bytes, and then sending a small data flowset (say, 100 bytes), the attacker creates a situation where the loop expects to read 200 bytes per record but the actual UDP packet only contains 100. With the bounds check missing, the loop reads through the end of the receive buffer and into whatever lies after it in the collector's heap allocation arena.
What the attacker learns
The over-read does not return raw memory bytes directly to the network. The collector consumes the bytes, parses them as flow record fields, and stores those fields in NetFlow v9 result structures. Those structures are then aggregated, written to internal counters, included in exported flows if downstream IPFIX export is configured, and potentially logged to disk in flow_dump mode.
An attacker who can observe any of those output channels — downstream collectors, dashboards, log files, on-disk dumps — can convert the over-read into a slow-read primitive against heap memory immediately adjacent to recent UDP receive buffers. In a long-running daemon that processes thousands of flows per second, the heap is full of recent packet buffers, BGP peer state, template caches, configuration data, and internal counters. Each of those is potentially-sensitive content that an attacker can extract one byte (or one field) at a time by carefully crafting templates that map heap memory into observable flow record fields.
Reachability and exposure
| Reachability factor | Detail |
|---|---|
| Default bind address | 0.0.0.0:2055 — listens on every interface |
| Protocol | UDP — no handshake, no source IP authentication, trivial to spoof from any vantage point that can reach the collector |
| Authentication | None — NetFlow v9 has no authentication mechanism in the protocol |
| Exploit prerequisites | The attacker must be able to push a template packet and one data packet to UDP/2055. That's it. Templates persist in collector memory. |
| Detection | Effectively none. Malformed templates look like normal traffic. The over-read does not crash the daemon in the common case — it corrupts flow records silently. |
How a fix should look
The fix is exactly the check that already exists ten lines below, in the options branch, applied at the top of the data-branch loop body:
for (uint32_t i = 0; i < num_flowsets; i++) {
if (pkt + offset + field_template->total_length > packet_end) {
return false;
}
netflow9_flowset_to_store(pkt + offset, ...);
offset += field_template->total_length;
}
One line. The check should be the very first thing the loop body does, before any pointer dereference inside netflow9_flowset_to_store. A defensive secondary improvement is to validate the template's total_length when the template is first installed: if a template declares a record size larger than the maximum possible NetFlow v9 packet body (1480 bytes minus header overhead), the template itself can be rejected at parse time, preventing any subsequent data flowset that references that template from being processed at all.
What to do today
1. Identify your collector exposure
Run ss -uln 'sport = :2055' on the FastNetMon host. If the bind address is 0.0.0.0:2055 (the default) and the host has any public-facing or untrusted-network interfaces, the collector is reachable by any attacker who can route a UDP packet to those interfaces.
2. Apply compensating controls
- Firewall the NetFlow port down to a strict allowlist of your own router IPs. NetFlow v9 has no authentication, so the only authentication is "do I recognize the source IP from my router inventory." Implement that at the firewall. This single control eliminates the bulk of the attack surface.
- Move the collector behind a private VRF or management network if your environment supports it. Many ISPs already do this for FastNetMon because it makes sense for operational reasons; if you don't, this CVE is a reason to.
- Bind to a specific management interface instead of
0.0.0.0. Thenetflow_hostdirective infastnetmon.confaccepts a specific IP. Bind the collector to the management interface so that data-plane interfaces can't reach it even if the firewall is misconfigured. - Monitor flow record outputs for anomalous field values — record sizes that don't match your router's actual templates, source IPs that don't match your inventory, or unusually high parse-error counts in FastNetMon's own statistics file.
3. Long-term hygiene
NetFlow v9 and IPFIX collectors should be standard fuzz targets in any organization that runs them. AFL++ or libFuzzer harnesses around the process_netflow_v9_packet entrypoint are small enough to write in an afternoon, and they will surface this entire class of bug. Any operator who maintains a fork of FastNetMon for their own infrastructure should be running such a harness in CI.
The pattern: copy the check, then forget to paste it
This vulnerability is in many ways more interesting than a one-off oversight because it tells a story about how parser bugs accumulate in long-lived codebases. The two branches of process_netflow_v9_data_flowset() are structural twins. They were written close together in time. They handle structurally identical loop bodies. The bounds check exists in one and not the other. This is the signature of code that was written, reviewed, possibly even tested — but where the reviewer's attention drifted between the two branches because they "look the same."
The lesson for code reviewers is that structurally identical loops are exactly where bounds-check omissions hide. The reviewer's brain pattern-matches both loops as "we've already checked this one" and the unchecked loop slides past. The defensive technique is to enforce on yourself: before you mark a parser-related diff approved, find every loop that walks an attacker-controlled length, and verify the bounds check is the first statement in the loop body. Make it a checklist item. Don't trust pattern matching.
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-48683 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.
Running flow collectors in production?
Lorikeet Security audits the protocol parsers that decide which traffic gets seen, mitigated, and reported. NetFlow, IPFIX, sFlow, and BGP parsers fail in the same shape over and over — we know where to look.