PoC Archive PoC Archive
Critical CVE-2026-32746 unpatched

GNU InetUtils telnetd LINEMODE SLC Pre-Auth Buffer Overflow (CVE-2026-32746)

by DREAM Security Research Team (Adiel Sol, Arad Inbar, Erez Cohen, Nir Somech, Ben Grinberg, Daniel Lubel); PoC by jeffaf · 2026-07-05

CVSS 9.8/10
Severity
Critical
CVE
CVE-2026-32746
Category
network
Affected product
GNU InetUtils telnetd
Affected versions
Through 2.7 (all versions); any telnetd derived from the BSD SLC codebase
Disclosed
2026-07-05
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-03
Author / ResearcherDREAM Security Research Team (Adiel Sol, Arad Inbar, Erez Cohen, Nir Somech, Ben Grinberg, Daniel Lubel); PoC by jeffaf
CVE / AdvisoryCVE-2026-32746
Categorynetwork
SeverityCritical
CVSS Score9.8 (CVSS 3.1)
StatusPoC (overflow trigger + verification only; no code execution/shellcode included)
Tagstelnetd, inetutils, linemode, slc, buffer-overflow, pre-auth, cwe-120, cwe-787
RelatedN/A

Affected Target

FieldValue
Software / SystemGNU InetUtils telnetd
Versions AffectedThrough 2.7 (all versions); any telnetd derived from the BSD SLC codebase
Language / PlatformPython 3 PoC targeting a C telnetd daemon on Linux
Authentication RequiredNo (triggered before login prompt/authentication)
Network Access RequiredYes

Summary

GNU InetUtils telnetd’s add_slc() function in telnetd/slc.c appends 3 bytes per SLC (Set Local Characters) triplet into a fixed 108-byte buffer (slcbuf) with no bounds checking. During telnet option negotiation, before any login prompt is shown, an unauthenticated client can request LINEMODE and then send a crafted SLC suboption containing 40-60 triplets with out-of-range function codes, causing the buffer to overflow and corrupting the slcptr pointer plus adjacent BSS data. This PoC completes the negotiation, sends the oversized SLC suboption, and confirms the overflow by observing an anomalously large SLC response that leaks BSS memory back to the client — it does not attempt to weaponize the overflow into code execution.


Vulnerability Details

Root Cause

add_slc() writes 3 bytes per received SLC triplet into a fixed 108-byte slcbuf without validating the total triplet count or buffer bounds, allowing an attacker-controlled overflow of the buffer and the slcptr pointer that tracks it.

Attack Vector

  1. Connect to telnetd and complete initial telnet option negotiation.
  2. Client proactively sends WILL LINEMODE to initiate LINEMODE negotiation.
  3. Server responds DO LINEMODE and begins SLC suboption processing.
  4. Client sends a crafted SLC suboption with 40-60 triplets using function codes greater than 18 (NSLC), each triggering a 3-byte “not supported” reply queued into slcbuf.
  5. After roughly 35 triplets the buffer overflows, corrupting slcptr and adjacent BSS memory.
  6. end_slc() sends data from slcbuf up through the corrupted slcptr position back to the client, leaking BSS memory and confirming the overflow via the oversized response.

Impact

Pre-authentication memory corruption in telnetd; per the WatchTowr writeup referenced in the source repo, the same primitive can reportedly be extended toward remote code execution on 32-bit systems via a def_slcbuf/free() primitive, though this PoC only demonstrates the crash/leak, not RCE.


Environment / Lab Setup

Target:   Debian container running inetutils-telnetd 2.4 under xinetd, exposed on port 2323 (via included Docker Compose lab)
Attacker: Python 3, Docker + Docker Compose

Proof of Concept

PoC Script

See exploit.py, detect.py, Dockerfile, docker-compose.yml, xinetd-telnet.conf in this folder.

1
2
3
4
docker compose up -d
python3 detect.py 127.0.0.1 2323
python3 exploit.py 127.0.0.1 2323
docker compose down

detect.py performs non-destructive version detection; exploit.py connects to the target, negotiates LINEMODE, sends the oversized SLC suboption, and verifies the overflow by inspecting the resulting oversized/leaked SLC response.


Detection & Indicators of Compromise

Signs of compromise:

  • telnetd crash or restart correlated with SLC suboption negotiation in session logs/pcap
  • Anomalously large SLC suboption responses containing non-ASCII/binary leaked memory
  • Telnet sessions with LINEMODE negotiation containing 40+ SLC triplets from a single client before authentication

Remediation

ActionDetail
Primary fixVendor patch expected by April 1, 2026 per source repo — confirm current InetUtils release notes for the fix commit
Interim mitigationDisable or firewall the telnet service; if telnet must remain available, restrict access via network ACLs and monitor/limit SLC triplet counts during LINEMODE negotiation

References


Notes

Mirrored from https://github.com/jeffaf/cve-2026-32746 on 2026-07-05.

detect.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#!/usr/bin/env python3
"""
CVE-2026-32746 - Non-destructive telnetd version detection
============================================================

Connects to a telnet service, negotiates options, and checks whether
the service supports LINEMODE (indicating GNU InetUtils telnetd,
which is affected by CVE-2026-32746).

Does NOT send any exploit payload. Safe for production scanning.

Usage:
    python3 detect.py <target_ip> [port]
"""

import argparse
import socket
import sys
import time

IAC  = 0xFF
DO   = 0xFD
WILL = 0xFB
WONT = 0xFC
DONT = 0xFE
SB   = 0xFA
SE   = 0xF0

OPT_TTYPE    = 0x18
OPT_TSPEED   = 0x20
OPT_LINEMODE = 0x22


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 detect(host, port, timeout):
    """Check if target appears to run vulnerable telnetd."""

    print(f"[*] Checking {host}:{port} for GNU InetUtils telnetd")
    print(f"    (CVE-2026-32746 - LINEMODE SLC Buffer Overflow)\n")

    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(timeout)
        s.connect((host, port))
    except (socket.error, OSError) as e:
        print(f"[-] Connection failed: {e}")
        return

    # Round 1: Read initial negotiation
    time.sleep(1)
    data = recv_all(s)
    if not data:
        print("[-] No data received")
        s.close()
        return

    # Parse initial options
    options = []
    i = 0
    while i < len(data) - 2:
        if data[i] == IAC:
            cmd = data[i + 1]
            opt = data[i + 2]
            if cmd in (DO, WILL):
                options.append((cmd, opt))
            i += 3
        else:
            i += 1

    print(f"  Port {port}/tcp open")
    print(f"  Initial options: {len(options)}")

    # Respond to everything + offer WILL LINEMODE
    resp = bytearray()
    for cmd, opt in options:
        if cmd == DO:
            resp.extend([IAC, WILL, opt])
        elif cmd == WILL:
            resp.extend([IAC, DO, opt])

    # Proactively offer LINEMODE
    resp.extend([IAC, WILL, OPT_LINEMODE])

    # Send TTYPE and TSPEED suboptions (server expects 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])

    s.send(resp)

    # Round 2: Check for DO LINEMODE
    time.sleep(1)
    data2 = recv_all(s)

    got_linemode = False
    got_slc = False
    if data2:
        i = 0
        while i < len(data2) - 2:
            if data2[i] == IAC:
                if data2[i + 1] == DO and data2[i + 2] == OPT_LINEMODE:
                    got_linemode = True
                elif data2[i + 1] == SB and i + 3 < len(data2) and data2[i + 2] == OPT_LINEMODE:
                    if i + 4 < len(data2) and data2[i + 3] == 0x03:  # LM_SLC
                        got_slc = True
                i += 3 if data2[i + 1] not in (SB,) else 1
            else:
                i += 1

    print(f"  LINEMODE accepted: {'Yes' if got_linemode else 'No'}")
    print(f"  SLC negotiation:   {'Yes' if got_slc else 'No'}")

    if got_linemode:
        print(f"\n[!] LIKELY VULNERABLE to CVE-2026-32746")
        print(f"    Server supports LINEMODE with SLC negotiation")
        print(f"    GNU InetUtils telnetd through 2.7 is affected")
        print(f"    CVSS: 9.8 (Critical) | No patch available")
        print(f"\n    Run exploit.py to confirm (crashes the service)")
    else:
        print(f"\n[*] LINEMODE not accepted")
        print(f"    Likely not GNU InetUtils telnetd, or LINEMODE disabled")

    # Clean disconnect
    s.close()


def main():
    parser = argparse.ArgumentParser(
        description="CVE-2026-32746 - Non-destructive telnetd detection",
        epilog="Safe for production use. Does not send exploit payload."
    )
    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("-t", "--timeout", type=int, default=10,
                        help="Socket timeout in seconds (default: 10)")
    args = parser.parse_args()

    detect(args.host, args.port, args.timeout)


if __name__ == "__main__":
    main()