Eighty-Three Defects in Twenty SNMP Implementations: Length Decoding and the Community Check

We tested twenty SNMP implementations and uncovered 83 defects. Most of them are in two places: the BER length decoder, which is reached before any credential is examined, and the community check, which in six of the twenty runs after the decoder or not at all.

SNMP is the management plane of most networks, and it is carried in BER, a self-describing encoding in which every element states its own length before its contents.

A decoder therefore reads an attacker-supplied length and then acts on it, and it does so before it has examined any credential, because the community string is itself a BER element some way into the message.

We tested twenty implementations and uncovered 83 defects. They span embedded agent libraries, operating-system agents, a commercial router platform tested without source, a caching proxy's management interface, a protocol analyser, and four manager-side and trap-side components. Three came back clean.

The distribution is narrow. Most of the 83 are one of four things: a length that is decoded without being compared to what arrived, a community check placed after the decoding it was meant to gate, a reply sized from the request's declaration rather than from the reply, and a manager or trap receiver that validates nothing at all.

Declared lengths never compared against the datagram

The most common defect in this family is a length that is read correctly and then used without being measured against the bytes that are actually present.

Product Q has the clearest form. An outer SEQUENCE that declares indefinite length, which is 40 bytes on the wire and needs no credential, sends the parser walking off the end of the request buffer for roughly 2^31 iterations until the process dies. The same implementation accepts a community length encoded as 84 FF FF FF FF, which is -1 read as a signed quantity, in 11 bytes, and writes one byte in front of a 16-byte global.

Three other implementations fail on the sign. Product L has a length check that wraps, and a 256-octet INTEGER that decodes as zero. Product K catches the value -1 specifically and no other negative value. Product S has the same pair: a negative length that is not huge, and an indefinite-length octet that is accepted.

Two fail on the magnitude instead. Product B terminates on a length of 0x84 FF FF FF FF through an uncaught length error, and again on the same length behind an OPAQUE tag, where the allocation result is used without being checked. Product F terminates on a declared length of 2^64-1 in a 23-byte datagram.

Product E is the narrowest case and the most instructive about why these persist. Its long-form length check is correct except for one octet: it compares the remaining bytes against the length count without accounting for the octet that introduced it. Alongside it, three separate functions advance a message cursor by a parsed element's total length without comparing that length to the message that remains.

Product G has a bounds check on the varbind length that performs no comparison at all, and a copy that reads past every buffer in a chained datagram.

Where the community check sits in the parse

Six of the twenty examine the community string after the decoding it exists to protect, or do not examine it.

Product F never compares it anywhere, so none of its four defects needs a credential. Product N, on the manager side, likewise never compares the v1 or v2c community.

Product D is more specific. A prefix of the configured community is accepted, because the comparison is length-limited by the attacker-supplied length rather than by the configured one. In the same statement, version 1 requests are never community-checked at all, because the check sits inside a test for versions other than 1.

Product Q does check, and the check is reached after two elements have already been parsed, which is where its two credential-free defects fire. Its shipped sample configuration grants read and write access to any address, so the remaining findings sit behind a credential printed in the product's own repository.

Product M was driven on the device rather than from source. A zero-length authentication digest is accepted as valid, which removes the v3 security model from the path entirely.

Replies sized from the request's declaration

An SNMP agent builds its response into a buffer, and several of these implementations size that buffer, or the fields inside it, from a quantity the request supplied.

Product Q returns a 719-byte reply to a 42-byte request that declared a PDU length of 700, and the bytes past the real content are the previous manager's datagram, still in the buffer.

Product P answers a 168-byte request with a 4,060-byte reply, an amplification factor of roughly 24, and the reply declares its own length as 4,096 inside a 4,060-byte datagram, so it does not parse. A request past its arc-count cap produces no reply and no tooBig, which is the response the specification requires.

Product B writes past the packet buffer when a single request asks for the same string object 34 times, and then sends no reply. Product S has the equivalent: a reply that outgrows its buffer, and a loop with no exit condition.

Product M's reply OID encoder under-allocates by one octet for each arc it writes.

The manager and trap-receiver side

Four of the twenty are manager-side and trap-side: software that parses what an agent or a device sends back. None of it is better defended, and some of it is worse.

Product Q's trap receiver validates nothing. A 255-byte unsolicited trap overflows a 16-byte global, a 733-byte varbind list overflows a 256-byte one, a 200-arc enterprise OID overflows a stack array, and an enterprise OID ending in a large arc formats into an 8-byte buffer. These arrive unsolicited on UDP 162 and need no prior exchange.

Product J matches a varbind by name on its fast path and copies the value as an OID without checking the type it actually carries.

Product N accepts a cleartext, unauthenticated reply on a session negotiated as authPriv. It also reads off the end of the heap on an OID that ends mid-sub-identifier, and resends a report indefinitely without consuming its retry budget.

Product C has the same shape on its client side: a response sub-identifier used directly as a length in 32-bit arithmetic, and an operator-precedence error that causes every error response to be read as the end of a table.

The three with no findings

Product A, Product H and Product I produced no finding. Two are agents from maintained suites and the third is the same suite's agent behind DTLS.

That is a result about those three builds under this method, not a general one. The method is given above for each: the same length shapes, the same credential-placement tests, the same reply-sizing measurements, against a control request that is answered normally so that a quiet run can be distinguished from a dead target.

Finding these with Penzzer

SNMP has a protocol-aware target on both address families, for an agent and for a manager. Agent-side runs select SNMP Agent - BER encoding for the encoding layer and SNMP Agent - Values for the value layer; manager-side and trap-side components are tested by selecting SNMP Manager - BER encoding, where Penzzer listens and the software under test connects to it.

| Attack module | What it drives | | --- | --- | | BER length field abuse | Declared lengths against the bytes actually present, in every form including indefinite and negative | | Nested sequence depth | Recursion and stack consumption in the decoder | | OID arc count and subid encoding | Arc counts and multi-octet sub-identifiers against fixed arrays | | Oversized varbind value | A value length against the buffer that receives it | | Varbind list structure abuse | Bindings appended past the end of the declared list | | Community string brute force | The credential path, and where in the parse it sits | | v3 USM field abuse | Security-level, engine-id and digest fields in the v3 header | | Unauthorized write abuse | SET on objects a session should not be able to write | | GetBulk repetition abuse | Repetition counts against the response builder | | Reply amplification and tooBig | Response size against request size, and the required overflow signal | | Request id and error index echo | What of the request is copied into the reply, and how | | Cross datagram state reuse | Buffer contents surviving from one datagram to the next | | Trap spoofing | The trap receiver, unsolicited and unauthenticated | | TCP transport framing | The same decoder reached over TCP rather than UDP |

‍

A run also generates cases from the protocol's own structure, walking each field of the message through the boundaries where decoders break, which is how a length-prefixed encoding is covered without enumerating the shapes by hand. The same target can be put through vulnerability and misconfiguration scanning and a port scan, which is usually how an agent left enabled on a device is found in the first place.

What this article is, and is not

The 83 defects above are a sample of what this kind of testing surfaces, not a description of Penzzer's SNMP coverage. They are what twenty specific implementations happened to get wrong.

The attack library spans defect classes well beyond what this article shows: memory safety, information disclosure, resource exhaustion, availability, hangs and algorithmic complexity, input validation, protocol state logic, type and format handling, and error handling. Across the 106 protocols with a protocol-aware target, a run also generates its test cases from the structure of the protocol itself.

The same limit applies in reverse. What a test looks for is a property of the test, not a claim about your device. Three of the twenty here came back clean, and that is what a clean result means.

If your product answers on UDP 161

Three questions, in the order the parser reaches them.

Does every declared length get compared against the bytes remaining in the datagram before anything is copied or allocated, including when the declaration is negative, indefinite, or larger than the transport can carry? Is the community or v3 credential examined before the decoder it is meant to gate, rather than after the first two elements have already been parsed? And is the response buffer sized from the response, rather than from a number the request supplied?

The fourth question is for the manager side. A manager, a trap receiver and a monitoring poller parse attacker-influenced BER with the same decoders, often with less attention, and on the evidence here the trap receiver is the least defended surface in the family.

Other Post
Uncover Hidden Vulnerabilities

Identify security flaws before attackers do, automatically and at scale with Penzzer's intelligent fuzzing engine.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.