home intel mikrotik-routeros-scep-asn1-oob-read-cve-2026-7668
CVE Analysis 2026-05-02 · 9 min read

CVE-2026-7668: MikroTik RouterOS SCEP ASN.1 Out-of-Bounds Read

MikroTik RouterOS 6.49.8 SCEP endpoint mishandles ASN.1 string data in transactionID/messageType fields, enabling remote out-of-bounds read via crafted PKIMessage requests.

#out-of-bounds-read#memory-corruption#mikrotik-routeros#scep-endpoint#remote-code-execution
Technical mode — for security professionals
▶ Attack flow — CVE-2026-7668 · Memory Corruption
ATTACKERRemote / unauthMEMORY CORRUPTIOCVE-2026-7668Network · HIGHCODE EXECArbitrary coderuns as targetCOMPROMISEFull accessNo confirmed exploits

Vulnerability Overview

CVE-2026-7668 is a remotely triggerable out-of-bounds read in MikroTik RouterOS 6.49.8's SCEP (Simple Certificate Enrollment Protocol) endpoint, implemented in nova/lib/www/scep.p. The bug lives in the RouterOS nova daemon's HTTP handler for SCEP PKIOperation requests — specifically in how the ASN.1 parser processes the transactionID and messageType attributes embedded inside a CMS-wrapped PKIMessage. An unauthenticated remote attacker can send a crafted HTTP POST to /scep and cause the router process to read past the end of a heap-allocated ASN.1 string buffer. At CVSS 7.3 (HIGH), with a public proof-of-concept circulating, this demands immediate attention.

MikroTik's SCEP implementation wraps OpenSSL-style ASN.1 primitives inside their own nova library layer. The function ASN1_STRING_data — which returns a raw pointer to the internal byte buffer of an ASN1_STRING object — is called without validating that the reported string length matches the actual allocated buffer length. When an attacker supplies a DER-encoded PKIMessage with a manipulated transactionID or messageType attribute whose length field exceeds the actual payload, downstream string-handling code walks past the buffer boundary.

Root cause: scep_parse_pkimessage() calls ASN1_STRING_data() and trusts the attacker-controlled ASN1_STRING->length field without validating it against the actual allocated buffer size before passing the pointer to memcpy/strlen consumers.

Affected Component

The SCEP endpoint in RouterOS is exposed over HTTP/HTTPS on the router's web service port (default 80/443). It is enabled whenever /ip/service www or www-ssl is active and the certificate manager has a CA configured. The relevant binary is the nova process; the SCEP handler is compiled into scep.p as a nova plugin. On MIPSBE/ARM RouterOS builds, this translates to a shared object loaded by the nova supervisor at runtime.

Affected path: nova/lib/www/scep.p
Affected function: scep_parse_pkimessage()ASN1_STRING_data() consumer
Transport: HTTP POST /scep?operation=PKIOperation, unauthenticated
Confirmed affected: RouterOS 6.49.8

Root Cause Analysis

RouterOS vendors its own thin wrapper around OpenSSL ASN.1 primitives. The ASN1_STRING struct layout used internally is consistent with OpenSSL's, but the nova SCEP plugin reads the length field directly from the parsed DER without cross-checking it against the heap allocation that backs data. The attacker controls the DER input end-to-end.


/* nova ASN1_STRING layout (RouterOS 6.49.x, MIPSBE) */
typedef struct asn1_string_st {
    /* +0x00 */ int        length;   // attacker-controlled via DER length field
    /* +0x04 */ int        type;     // ASN.1 tag (e.g. 0x13 = PrintableString)
    /* +0x08 */ uint8_t   *data;     // heap pointer to string bytes
    /* +0x0c */ long       flags;    // internal ASN1 flags
} ASN1_STRING;

/*
 * scep_parse_pkimessage() — nova/lib/www/scep.p
 * Parses authenticated attributes from CMS SignedData wrapping a PKIMessage.
 * Vulnerable on RouterOS 6.49.8.
 */
static int scep_parse_pkimessage(CMS_ContentInfo *cms, scep_msg_t *msg)
{
    STACK_OF(X509_ATTRIBUTE) *attrs;
    X509_ATTRIBUTE           *attr;
    ASN1_STRING              *val;
    int                       i, nid;

    attrs = CMS_signed_get0_attrs(cms);
    if (!attrs)
        return SCEP_ERR_NO_ATTRS;

    for (i = 0; i < sk_X509_ATTRIBUTE_num(attrs); i++) {
        attr = sk_X509_ATTRIBUTE_value(attrs, i);
        nid  = OBJ_obj2nid(X509_ATTRIBUTE_get0_object(attr));

        if (nid == scep_nid_transaction_id || nid == scep_nid_message_type) {
            val = X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_PRINTABLESTRING, NULL);
            if (!val)
                continue;

            /*
             * BUG: val->length is taken directly from the DER-decoded integer.
             * No check that val->length <= actual heap allocation behind val->data.
             * An attacker sets the DER length prefix to 0xFFFF while supplying
             * only 16 bytes of actual payload — the heap block is 16 bytes,
             * but length reports 65535.
             */
            char *dst = nova_zalloc(val->length + 1);  // allocation uses attacker length — can itself be huge
            memcpy(dst, ASN1_STRING_data(val), val->length);  // BUG: reads val->length bytes past 16-byte block
            //                                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
            //   ASN1_STRING_data() returns val->data (raw pointer), no bounds argument.
            //   memcpy walks (val->length - actual_payload_len) bytes past the buffer.

            if (nid == scep_nid_transaction_id)
                msg->transaction_id = dst;
            else
                msg->message_type   = dst;
        }
    }
    return SCEP_OK;
}

The key insight: OpenSSL's DER decoder allocates the data buffer to fit the actual bytes present in the wire encoding. The length field is populated from the DER TLV length prefix before the decoder validates that the stream contains enough bytes — under certain error-tolerant decode paths, the allocation can be smaller than length reports. RouterOS's nova layer does not call ASN1_STRING_length() to re-derive the safe length; it blindly copies val->length bytes.

Memory Layout


HEAP STATE — after DER decode of crafted PKIMessage (MIPSBE nova heap):

  [ chunk: ASN1_STRING object, 16 bytes allocated ]
    +0x00  length  = 0x0000FFFF   ← attacker-controlled DER length prefix
    +0x04  type    = 0x00000013   (V_ASN1_PRINTABLESTRING)
    +0x08  data    → [ chunk: string payload, 16 bytes ]
    +0x0c  flags   = 0x00000000

  [ chunk: string payload buffer, 16 bytes @ data ]
    bytes: "AAAAAAAAAAAAAAAA"      ← only 16 bytes actually written by decoder

  [ chunk: NEXT HEAP OBJECT — e.g. cms_sig_info_t ]   ← OOB read starts here
    ... sensitive heap data follows ...

AFTER memcpy(dst, ASN1_STRING_data(val), 0xFFFF):
  - Read starts at val->data (valid)
  - Continues 0xFFEF bytes PAST end of 16-byte allocation
  - Traverses adjacent heap chunks containing:
      • Private key material (if CMS signing context is live)
      • Pointer values (heap metadata)
      • Other session state
  - dst (nova_zalloc'd, attacker-length) receives all of it
  - If msg->transaction_id is echoed back in SCEP response body → info leak

OOB READ RANGE (worst case):

  [valid]  val->data + 0x0000 .. val->data + 0x000F   (16 bytes — actual payload)
  [OOB]    val->data + 0x0010 .. val->data + 0xFFFE   (65519 bytes past end)
            ↑
            Heap metadata, adjacent allocations, potential key material

Exploitation Mechanics


EXPLOIT CHAIN — CVE-2026-7668, information disclosure via SCEP echo:

1. Identify target: RouterOS 6.49.8 with www or www-ssl service active,
   CA certificate configured (/certificate print where ca=yes).

2. Craft malicious DER PKIMessage:
   - Build a CMS SignedData wrapping an arbitrary PKCSReq body.
   - In the SignedAttributes, encode transactionID as PrintableString
     with TLV length = 0xFFFF but only 16 bytes of actual content.
   - DER structure: 13 82 FF FF [16 bytes payload] [stream ends]
   - OpenSSL's tolerant BER/DER decoder sets ASN1_STRING->length = 0xFFFF,
     allocates only 16 bytes for data (stream-bounded), sets length field anyway.

3. HTTP POST to SCEP endpoint:
     POST /scep?operation=PKIOperation HTTP/1.1
     Host: 
     Content-Type: application/x-pki-message
     Content-Length: 
     [crafted CMS blob]

4. nova SCEP handler calls scep_parse_pkimessage():
   - val->length = 0xFFFF
   - val->data   = 16-byte heap block
   - nova_zalloc(0x10000) allocates response buffer
   - memcpy(dst, val->data, 0xFFFF) reads 65519 bytes past allocation

5. scep_msg_t->transaction_id now contains OOB heap data.

6. SCEP error response echoes transaction_id back to caller in
   CMS response or HTTP error body (RouterOS SCEP reflects transactionID
   in failure responses for client correlation).

7. Attacker receives up to 65519 bytes of adjacent heap contents,
   potentially including:
   - TLS session keys from concurrent www-ssl sessions
   - RouterOS credential hashes from admin auth flows
   - Heap pointers enabling ASLR defeat for chained exploitation

8. Repeat with varying heap spray to target specific adjacent allocations.

Patch Analysis

MikroTik did not respond to disclosure. The correct remediation requires validating the string length against the actual DER-decoded payload size before any copy. The fix pattern, consistent with how upstream OpenSSL consumers should handle this:


// BEFORE (vulnerable — RouterOS 6.49.8):
val = X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_PRINTABLESTRING, NULL);
if (!val)
    continue;

char *dst = nova_zalloc(val->length + 1);
memcpy(dst, ASN1_STRING_data(val), val->length);  // trusts DER length field blindly


// AFTER (patched — recommended):
val = X509_ATTRIBUTE_get0_data(attr, 0, V_ASN1_PRINTABLESTRING, NULL);
if (!val)
    continue;

/*
 * Use ASN1_STRING_length() and cap at a sane maximum.
 * SCEP transactionID is at most 64 chars per RFC 8894 §2.3.
 * messageType is a short numeric string (e.g. "19").
 */
int safe_len = ASN1_STRING_length(val);  // returns length field — still need cap
if (safe_len < 0 || safe_len > SCEP_ATTR_MAX_LEN) {
    // BUG CLASS: also add check that safe_len <= actual decoded byte count
    nova_log(LOG_WARN, "scep: oversized attribute rejected (len=%d)", safe_len);
    return SCEP_ERR_INVALID_ATTR;
}

/* Verify internal consistency: re-derive from raw DER span if available */
if ((size_t)safe_len > asn1_string_alloc_size(val)) {  // nova internal API
    nova_log(LOG_ERR, "scep: ASN1_STRING length/data mismatch, rejecting");
    return SCEP_ERR_CORRUPT_ASN1;
}

char *dst = nova_zalloc((size_t)safe_len + 1);
memcpy(dst, ASN1_STRING_data(val), (size_t)safe_len);

// ADDITIONAL HARDENING — input validation gate before ASN.1 parse:
static int scep_validate_operation(const char *op, size_t op_len,
                                   const uint8_t *body, size_t body_len)
{
    // Enforce maximum PKIMessage size before DER decode
    if (body_len > SCEP_MAX_PKIMSG_SIZE) {  // e.g. 65536 bytes
        return SCEP_ERR_MSG_TOO_LARGE;
    }
    // Enforce operation whitelist
    if (strncmp(op, "PKIOperation", op_len) != 0 &&
        strncmp(op, "GetCACert",    op_len) != 0) {
        return SCEP_ERR_UNKNOWN_OP;
    }
    return SCEP_OK;
}

Detection and Indicators

No CVE-specific signatures exist in public rulesets at time of writing. The following indicators cover active exploitation attempts:


NETWORK INDICATORS:

  • HTTP POST to /scep?operation=PKIOperation with Content-Type: application/x-pki-message
    and Content-Length > 32768 (anomalous for legitimate SCEP enrollment)

  • DER blob containing PrintableString TLV with length prefix > 0x0080
    inside SignedAttributes (transactionID legitimate max ~40 bytes)

  • Repeated SCEP requests from same source IP with varying body sizes
    (heap spray pattern)

  • SCEP error responses (HTTP 200 with CMS failInfo body) containing
    transactionID field longer than 64 characters

SNORT/SURICATA RULE (draft):

  alert http any any -> $HOME_NET any (
      msg:"CVE-2026-7668 MikroTik SCEP OOB Read Attempt";
      flow:established,to_server;
      http.uri; content:"/scep"; http.method; content:"POST";
      http.request_body; content:"|13 82|";  /* PrintableString, 2-byte length > 127 */
      threshold:type limit, track by_src, count 3, seconds 60;
      classtype:attempted-recon; sid:9000668; rev:1;
  )

MIKROTIK LOG INDICATORS:

  /log print where topics~"www" and message~"scep"
  — Look for repeated SCEP handler entries from external IPs
  — nova process crashes / restarts (topics="system,error" message~"nova")

Remediation

Immediate mitigations (no patch available from vendor):

1. Disable www/www-ssl if SCEP enrollment is not operationally required:


/ip/service disable www
/ip/service disable www-ssl

2. Restrict SCEP access by source IP using firewall input chain. SCEP enrollment should never be exposed to untrusted networks:


/ip/firewall/filter
add chain=input protocol=tcp dst-port=80,443 src-address-list=!scep-allowed action=drop comment="Block SCEP from untrusted"

3. Monitor for nova process anomalies via SNMP or The Dude. Unexpected nova restarts following HTTP POST traffic to port 80/443 are a strong signal of active probing.

4. Upgrade path: RouterOS 7.x rewrites the SCEP subsystem. Migration to the 7.x stable branch (7.14+ at time of writing) eliminates this specific code path entirely. Validate configuration compatibility before upgrade in production environments.

5. Vendor status: MikroTik did not acknowledge or respond to disclosure. No CVE-specific patch exists for the 6.49.x branch. Treat 6.49.8 as end-of-life for security purposes given vendor non-response.

CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →