/
    Zpět na blog
    Security

    Finding an Integer Overflow in MikroTik RouterOS Core IPC Library


    Finding an Integer Overflow in MikroTik RouterOS Core IPC Library

    image

    TL;DR: I found a pre-authentication integer overflow in nv::message::unflatten() — the core message deserialization function shared by every RouterOS service. This affects all RouterOS 7.x versions including the latest 7.22.1 in default configuration. It was assigned CVE-2026-39042 by MITRE and fixed by MikroTik in 7.21.4 and 7.22.2.


    Motivation

    MikroTik routers are everywhere. With millions of devices deployed worldwide, RouterOS security matters. After seeing CVE-2025-10948 (a buffer overflow in libjson.so) get published recently, I decided to take a deeper look at the RouterOS binary internals — specifically the libraries shared across all services.

    My goal was simple: are my own routers safe?

    Methodology

    I downloaded two official NPK packages from mikrotik.com:

    • routeros-7.20.8.npk (x86)
    • routeros-7.20.8-arm64.npk (ARM)

    And later routeros-7.22.1-arm64.npk (latest at time of research) for verification.

    Tools used: binwalk for extraction, readelf/objdump for static analysis, arm-linux-gnueabi-objdump for ARM disassembly, Python3 for live testing scripts.

    NPK Format

    RouterOS NPK packages use a simple structure: magic 0xBAD0F11E, followed by a TLV (tag-length-value) chain containing metadata, a SquashFS filesystem image, compressed parts data, and an EdDSA signature block. Extraction with binwalk is straightforward.

    The extracted SquashFS contains the full RouterOS filesystem — all ELF binaries, shared libraries, kernel modules, web interface files, and configuration templates.

    The Attack Surface

    A default RouterOS installation exposes a significant number of services:

    PORT      SERVICE
    21/tcp    FTP
    22/tcp    SSH (ROSSSH)
    23/tcp    Telnet
    80/tcp    HTTP (WebFig)
    2000/tcp  Bandwidth Test
    8291/tcp  Winbox
    8728/tcp  API
    8729/tcp  API-SSL
    

    All of these services share a common foundation: libumsg.so — MikroTik's internal IPC and message serialization library. Every network-facing service parses incoming data into nv::message objects using this library. If there's a bug in message deserialization, it affects everything.

    Binary Hardening Status

    Before looking for bugs, I checked the exploit mitigation status:

    RouterOS 7.20.8 (x86)

    MitigationStatus
    NX (DEP)DISABLED — stack is RWE
    PIE/ASLRDISABLED — fixed load at 0x0804xxxx
    Stack canariesDISABLED
    FORTIFY_SOURCEDISABLED
    Full RELROPartial only

    Every binary. Every library. Zero protections.

    RouterOS 7.22.1 (ARM, latest)

    MitigationStatus
    NX (DEP)ENABLED
    PIE/ASLRDISABLED
    Stack canariesDISABLED
    FORTIFY_SOURCEDISABLED
    Full RELROPartial only

    Good news: NX was recently enabled — stack shellcode no longer works directly. Bad news: without PIE or canaries, ROP attacks against fixed addresses remain trivial.

    The custom libc.so is musl libc (version string says "musl libc"), and while it exports __stack_chk_fail, the implementation is a 2-byte stub (int3; ret) — a debug trap, not a meaningful protection.

    The Vulnerability: Integer Overflow in nv::message::unflatten()

    The Function

    nv::message::unflatten() is the deserialization entry point in libumsg.so. It parses a binary blob into a structured message object. The function is approximately 2,100 bytes and handles multiple field types: booleans, u32, u64, IPv6 addresses, strings, raw blobs, nested messages, and arrays of each.

    The Bug

    The LONG string field handler (type 0x20000000 with bit 25 set) reads a 4-byte length from the wire and adds it to the current position to compute the end-of-data pointer. This addition is subject to 32-bit integer overflow.

    x86 (libumsg.so from 7.20.8, offset 0x2c02d):

    mov    0x4(%edi),%edx      ; edx = length from wire (4 bytes, attacker-controlled)
    lea    (%eax,%edx,1),%ecx  ; ecx = current_pos + length  ← 32-BIT OVERFLOW
    cmp    %ecx,-0x30(%ebp)    ; compare ecx vs buffer_end
    jb     error               ; reject if ecx > buffer_end
    ; ...
    call   string::C1(start, end)  ; proceeds with potentially wrapped pointer
    

    ARM (libumsg.so from 7.22.1, offset 0x2c2a4):

    ldr    r4, [r8, #4]        ; r4 = length from wire (4 bytes)
    add    r4, r1, r4          ; r4 = header_end + length  ← 32-BIT OVERFLOW
    cmp    r5, r4              ; buffer_end >= end_ptr?
    bcc    error               ; reject if not
    bl     string::C1(start, end)
    

    The identical pattern on both architectures confirms this is a source-level issue.

    The Overflow

    When current_pos + attacker_length exceeds 0xFFFFFFFF, the 32-bit result wraps to a small value:

    current_pos = 0x08100008    (pointer into message buffer)
    wire_length = 0xF7F00000    (attacker-controlled)
    
    0x08100008 + 0xF7F00000 = 0x100000008 → truncated to 0x00000008
    
    Bounds check: 0x00000008 < buffer_end (0x08200000) → PASSES
    
    string::rangeInitialize(start=0x08100008, end=0x00000008):
      length = end - start = 0xF7F00000 (~4GB)
      allocateBlock(0xF7F00000) → malloc(~4GB) → FAIL → CRASH
    

    Impact

    Confirmed: Remote pre-authentication Denial of Service. The affected service crashes and is restarted by the RouterOS watchdog.

    Potential: If heap allocation partially succeeds (e.g., returns a small buffer due to allocator behavior), the subsequent rep movsb (x86) or equivalent copy could write past the buffer boundary — heap corruption leading to potential code execution.

    This is particularly concerning because:

    • No PIE means all gadget addresses are predictable
    • No stack canaries means no overflow detection
    • Only partial RELRO means GOT is writable
    • The function is reachable pre-auth through Winbox (8291), SSH (22), and HTTP (80) — all open by default

    What I Verified on a Live Router

    I tested against a RouterOS 7.22.1 device (latest available version) in default configuration on an isolated VLAN. All findings were first discovered on 7.18.2, then after upgrading to 7.22.1, retested and confirmed still present.

    SSH integer overflow behavior was confirmed via router logs on both versions. When sending crafted SSH packets with oversized length fields, the router logged post-addition values proving the arithmetic occurs on attacker-controlled data:

    packet size too small: 0x0    ← sent 0xFFFFFFFC, after +4 = 0x00000000
    packet size too small: 0x1    ← sent 0xFFFFFFFD, after +4 = 0x00000001
    packet size too small: 0x2    ← sent 0xFFFFFFFE, after +4 = 0x00000002
    packet size too small: 0x3    ← sent 0xFFFFFFFF, after +4 = 0x00000003
    

    These logs are identical on 7.18.2 and 7.22.1 — the vulnerable code pattern has not changed.

    The SSH path has post-overflow validation that catches these wrapped values. The unflatten path in libumsg.so uses the same dangerous pattern but the bounds check is structured differently — the wrapped value passes instead of being caught.

    Open ports on 7.22.1 default config are identical to 7.18.2: FTP(21), SSH(22), Telnet(23), HTTP(80), BTest(2000), Winbox(8291), API(8728), API-SSL(8729).

    What I Didn't Find

    To be transparent about what didn't work:

    • HTTP header overflow — The web server has robust input limits (~4KB per header, ~8KB total) in libuhttp.so. It returned 400 Bad Request and never crashed. Good engineering.
    • SSH integer overflow as RCE — While the overflow is real (proven by logs), the SSH code has post-overflow size checks that catch all 4 wrapping values (0-3). It's a code quality issue but not exploitable on this specific path.
    • Direct shellcode on 7.22.1 — NX is now enabled, so even if you hit a stack overflow, direct shellcode execution fails. This is meaningful progress.

    All tests were reproduced on both 7.18.2 and the latest 7.22.1 with identical results.

    Disclosure Timeline

    • 2026-03-31: Vulnerabilities discovered and verified
    • 2026-03-31: Disclosure sent to security@mikrotik.com
    • 2026-04-06: CVE-2026-39042 reserved by MITRE
    • 2026-06-29: Planned public disclosure (90 days)
    • 2026-07-13: CVE-2026-39042 published by MITRE

    MikroTik confirmed the fix, released in RouterOS 7.21.4 and 7.22.2. Users should upgrade to one of these versions (or later) to remediate the integer overflow.

    MITRE has assigned this issue CVE-2026-39042. The published record describes it as: "An issue in MikroTik (SIA Mikrotikls, Latvia) RouterOS 7.21.x before v.7.21.4 and 7.22.x before v.7.22.2 allows a remote attacker to cause a denial of service via the unflatten() function in libumsg.so."

    Recommendations for RouterOS Users

    While waiting for fixes, you can significantly reduce your attack surface:

    # Disable unnecessary services
    /ip service disable telnet,ftp,api
    
    # Enable strong crypto (disables weak SSH algorithms)
    /ip ssh set strong-crypto=yes
    
    # Restrict management access to trusted IPs
    /ip service set ssh address=192.168.88.0/24
    /ip service set www address=192.168.88.0/24
    /ip service set winbox address=192.168.88.0/24
    
    # Add firewall rules
    /ip firewall filter add chain=input action=drop \
        dst-port=22,80,8291,8728,8729 \
        src-address=!192.168.88.0/24 \
        comment="Restrict management"
    
    # Keep RouterOS updated
    /system package update check-for-updates
    

    Conclusion

    Credit where it's due: MikroTik confirmed and fixed the overflow in RouterOS 7.21.4 and 7.22.2, and the issue is now tracked publicly as CVE-2026-39042. If you're running RouterOS, upgrade to one of these versions (or later) — this pre-auth bug is now closed.


    This research was conducted for defensive purposes on my own equipment. All testing was performed in an isolated environment. Disclosure follows a 90-day responsible disclosure timeline.

    © 2026 Patrik Žák. Všechna práva vyhrazena.