Skip to content
HN On Hacker News ↗

A 32-Year-Old Bug Walks Into A Telnet Server (GNU inetutils Telnetd CVE-2026-32746 Pre-Auth RCE)

▲ 112 points • 46 comments • by paimapi • 3w ago • HN discussion ↗

Pangram verdict · v3.3

We believe that this entire text is human-written.

0 %

AI likelihood · overall

Human
100% human-written 0% AI-generated
SEGMENTS · HUMAN 1 of 1
SEGMENTS · AI 0 of 1
WORD COUNT 1,591
PEAK AI % 0% · §1
Analyzed
Sep 17
backend: pangram/v3.3
Segments scanned
1 windows
avg 1591 words each
Distribution
100 / 0%
human / AI fraction
Verdict
Human
Pangram v3.3

Article text · 1,591 words · 1 segments analyzed

Human AI-generated
§1 Human · 0%

A long, long time ago, in a land free of binary exploit mitigations, when Unix still roamed the Earth, there lived a pre-authentication Telnetd vulnerability.In fact, this vulnerability was born so long ago (way back in 1994) that it may even be older than you. To put the timespan in perspective: it came into existence the same year the seminal movie Hackers was released.That was so long ago that RISC was still a distant dream.Come to think of it, maybe it was even the product of Zero Cool himself?Anyway. Recently, this vulnerability was brutally put to rest.What Are We Looking At Here?If you're not familiar with Telnet, that's okay.Telnet is a network protocol that provides a command-line interface for communicating with a remote server over TCP/IP. In other words, remote code execution as a service. Typical setups do have an authentication barrier, requiring you to log in before you can access the system's shell. It also operates over plaintext, which means yes, it transmits your username and password across the network in the clear.The de facto replacement these days is SSH, and Telnet is becoming increasingly uncommon.What Is CVE-2026-32746?CVE-2026-32746, discovered by the DREAM Security Research Team, is a BSS-based buffer overflow that allows an attacker to corrupt roughly 400 bytes of adjacent variables.It resides in the LINEMODE SLC (Set Linemode Characters) negotiation handler. While strictly speaking it affects 'just' GNU inetutils, most vendors have based their Telnetd implementations on the same code, making the blast radius vast and somewhat difficult to estimate. It definitely includes all the major Linux distributions (we checked).With a vulnerability like this, we expected the Internet to explode with excitement - yet it’s been almost a week now with no good analysis. We thought we might as well publish where we got to.We’ll go through a few things - how we isolated the vulnerability, what it enables attackers to do (and under what circumstances), and we’ll talk about why this particular vulnerability is more of a Pandora's box to exploit than you might think.But let's start with the obvious question, which we're sure is on the lips of everyone living in a magical Unicorn land where legacy technologies simply don't exist: why use Telnet in the first place?!What’s Affected?Well, this is a tricky one. The patch was applied to inetutils-telnetd, but many forks exist, and changes have been made throughout the years, copying and pasting this vulnerability from system to system.We have identified this CVE in at least the following:inetutils-telnetd itselfUbuntuDebianFreeBSD 13 / FreeBSD 15 PortNetBSD 10.1Citrix NetScalerApple Mac TahoeHaikuTrueNAS CoreuCLinuxlibmtevDragonFlyBSDIt’s 2026, Why Telnet? Where Is The MCP?Telnet has been around since the dawn of time. Many of you will be screaming at your screen right now - ‘who would use such an insecure protocol?!’.Well, as some of you may be surprised to learn, the venerable Telnet is still very much present on production systems, for a surprisingly wide variety of reasons. Maybe it's the only thing the vendor supports ('This CNC machine costs $X thousand a minute in downtime if it breaks, and you want to add... what?! An SSH client?! Dude, it runs on an 8-bit microcontroller!'). Maybe there's some deeply technical reason migration isn't possible.The fact remains: people still run it, as evidenced by its strong presence in the repositories of every major distribution. It has truly stood the test of time.A Vulnerability In Telnet?! Isn't Telnet Just, Like, The Same As Netcat?Some of our readers, blissfully unaware, may be under the impression that Telnet is simply a TCP stream at the protocol level. Indeed, some implementations are exactly that: a TCP socket connected to a shell. Simple, impossible (?) to break.This, however, is not the case. Telnet supports a range of features: terminal control (want to turn on echoing? turn it off?), client window size negotiation, authentication, and even encryption. We're sure that somewhere, some poor soul is tasked with maintaining enterprise security across all of these features.As everyone knows, 'more features' means 'more attack surface'. Everyone remembers CVE-2026-24061, right? In that bug, a Telnet protocol feature intended to share environment variables with the server could be abused for RCE. Pretty painful, and less far-reaching than today's vulnerability, though considerably easier to exploit.This particular vulnerability resides in the 'LINEMODE' feature of the Telnet protocol. As RFC 1184 puts it: While in Linemode with editing enabled for the local side, network traffic is reduced to a couple of packets per command line, rather than a couple of packets per character typed. This is very useful for long delay networks, because the user has local response time while typing the command line, and only incurs the network delays after the command is typed. It is also useful to reduce costs on networks that charge on a per packet basis... Yes, the vulnerability is so old, it dates from a time when networks charged on a ‘per-packet basis’.The real details of the feature itself aren’t super-important to us (our priority is simply ‘hack all the things’). However, to get to the vulnerable code, we’ll need to know how to enable it, which means we need to learn a little about how Telnet negotiates connection parameters.Tense NegotiationsFor interoperability, the Telnet client and server won't enable these fancy options by default. When a connection is first established, some in-band signaling takes place (ho ho ho, whenever did that go wrong) via the IAC, or 'Interpret As Command', byte, defined as 0xFF.There’s a whole bunch of things that can be negotiated, like local echo, for example, or the speed of your teletype (remember those? We don’t). As previously alluded to, however, the feature we’re interested in is the ‘LINEMODE’ option.One of the things defined by this feature, named SLC (or ‘Set Linemode Characters’), is particularly interesting. It enables the server to communicate with the client and inform it that certain special features - whizz-bang new technology like ‘backspace’, for example - should be represented by specific control codes.To zoom in a little, let’s take a look at a hypothetical negotiation. First, we connect to a Telnet server via TCP. The server immediately sends the following data:IAC DO LINEMODE (or 0xFF 0xFD 0x22, for those that prefer hex) Here, the ‘DO’ is indicating that the server is requesting the capability. To enable it, the client responds with a ‘WILL’:IAC WILL LINEMODE (0xFF 0xFB 0x22) Once that’s sorted, the server sends a list of three-byte triplets, each indicating a special character to be replaced, the ‘support level’, and the actual character value. The client can then take a good look, decide if it wants to modify any, and if so, send a reply containing new values - again, a list of three-byte triplets.IAC SB LINEMODE LM_SLC <triplets> IAC SE (0xFF 0xFA 0x03 <triplets> 0xFF 0xF0) The server will duly store all these values in a global array of a fixed size, without doing any bounds checking. Wait, what?!Yes, that’s the vulnerability. Undetected since 1994, people. The patch is as close to modern art as information security will ever get.But wait! It gets even ‘funnier’!What could be funnier, you ask?Well, what if exactly the same vulnerability was present in the Telnet client, rather than the server, way back in 2005?Yes, it’s true, folks. CVE-2005-0469 is this vulnerability’s doppelganger - essentially the same, but on the client side, where a function named slc_add_reply was missing a bounds check. The fix is an identical bounds check patch to today’s vulnerability.Thankfully, twenty years later, somebody thought to check the server end for the same vulnerability.They say history doesn't repeat, but it sure does rhyme.On To ExploitationOf course, as many of our readers know, this isn’t the end of the story - nay, this is merely the beginning of our odyssey of pain. Yes, it’s a new vulnerability. Yes, it’s a CVSS three squillion. But can baddies actually exploit it to do something useful?Well. That’s the question we’re here to answer.Unfortunately, though, the answer is somewhat muddy and requires a little bit of nuance. Yes, we can overflow a global variable with data we control (and thus corrupt the memory locations that follow it). But that’s where the happy days end, and the real world sets in.Firstly, the data we can send to the server is somewhat limited. As mentioned before, it is in the form of triplets: a function, a flag, and a value, each 1 byte long. Each of these has its own restrictions, mostly found in the process_slc function.Let’s take a look at it.void process_slc (register unsigned char func, register unsigned char flag, register cc_t val) { register int hislevel, mylevel, ack; /* * Ensure that we know something about this function */ if (func > NSLC) { add_slc (func, SLC_NOSUPPORT, 0); return; } Here’s our first restriction - if the func byte is greater than the NSLC constant (which comes to 0x1e), then the rest of the triplet will be discarded. While the first byte makes it through unscathed, the remaining bytes are set to SLC_NOSUPPORT (which is zero) and zero itself. This triplet is then added to the global variable via add_slc.Not ideal, but we can work with it, right?What’s the rest of the function look like? /* * Process the special case requests of 0 SLC_DEFAULT 0 * and 0 SLC_VARIABLE 0. Be a little forgiving here, don't * worry about whether the value is actually 0 or not. */ if (func == 0) { if ((flag = flag & SLC_LEVELBITS) == SLC_DEFAULT) { default_slc (); send_slc (); } else if (flag == SLC_VARIABLE) { send_slc (); } return; }