Esempio n. 1
0
	def format_row_data(self, visit):
		msg_details = self.rpc.remote_table_row('messages', visit.message_id, cache=True)
		if not msg_details:
			return None
		visitor_ip = ipaddress.ip_address(visit.visitor_ip)
		geo_location = UNKNOWN_LOCATION_STRING
		if visitor_ip.is_loopback:
			geo_location = 'N/A (Loopback)'
		elif visitor_ip.is_private:
			geo_location = 'N/A (Private)'
		elif isinstance(visitor_ip, ipaddress.IPv6Address):
			geo_location = 'N/A (IPv6 Address)'
		else:
			if not visitor_ip in self._ips_for_georesolution:
				self._ips_for_georesolution[visitor_ip] = visit.first_visit
			elif self._ips_for_georesolution[visitor_ip] > visit.first_visit:
				self._ips_for_georesolution[visitor_ip] = visit.first_visit
		row = (
			msg_details.target_email,
			str(visitor_ip),
			visit.visit_count,
			visit.visitor_details,
			geo_location,
			visit.first_visit,
			visit.last_visit
		)
		return row
Esempio n. 2
0
	def format_node_data(self, node):
		geo_location = UNKNOWN_LOCATION_STRING
		visitor_ip = node['visitorIp']
		if visitor_ip is None:
			visitor_ip = ''
		else:
			visitor_ip = ipaddress.ip_address(visitor_ip)
			if visitor_ip.is_loopback:
				geo_location = 'N/A (Loopback)'
			elif visitor_ip.is_private:
				geo_location = 'N/A (Private)'
			elif isinstance(visitor_ip, ipaddress.IPv6Address):
				geo_location = 'N/A (IPv6 Address)'
			elif node['visitorGeoloc']:
				geo_location = node['visitorGeoloc']['city']
		row = (
			node['message']['targetEmail'],
			str(visitor_ip),
			node['visitCount'],
			node['visitorDetails'],
			geo_location,
			node['firstVisit'],
			node['lastVisit']
		)
		return row
Esempio n. 3
0
	def rpc_login(self, session, username, password, otp=None):
		logger = logging.getLogger('KingPhisher.Server.Authentication')
		if not ipaddress.ip_address(self.client_address[0]).is_loopback:
			logger.warning("failed login request from {0} for user {1}, (invalid source address)".format(self.client_address[0], username))
			raise ValueError('invalid source address for login')
		fail_default = (False, ConnectionErrorReason.ERROR_INVALID_CREDENTIALS, None)
		fail_otp = (False, ConnectionErrorReason.ERROR_INVALID_OTP, None)

		if not (username and password):
			logger.warning("failed login request from {0} for user {1}, (missing username or password)".format(self.client_address[0], username))
			return fail_default
		if not self.server.forked_authenticator.authenticate(username, password):
			logger.warning("failed login request from {0} for user {1}, (authentication failed)".format(self.client_address[0], username))
			return fail_default

		user = db_manager.get_row_by_id(session, db_models.User, username)
		if not user:
			logger.info('creating new user object with id: ' + username)
			user = db_models.User(id=username)
			session.add(user)
			session.commit()
		elif user.otp_secret:
			if otp is None:
				logger.debug("failed login request from {0} for user {1}, (missing otp)".format(self.client_address[0], username))
				return fail_otp
			if not (isinstance(otp, str) and len(otp) == 6 and otp.isdigit()):
				logger.warning("failed login request from {0} for user {1}, (invalid otp)".format(self.client_address[0], username))
				return fail_otp
			totp = pyotp.TOTP(user.otp_secret)
			now = datetime.datetime.now()
			if not otp in (totp.at(now + datetime.timedelta(seconds=offset)) for offset in (0, -30, 30)):
				logger.warning("failed login request from {0} for user {1}, (invalid otp)".format(self.client_address[0], username))
				return fail_otp
		logger.info("successful login request from {0} for user {1}".format(self.client_address[0], username))
		return True, ConnectionErrorReason.SUCCESS, self.server.session_manager.put(username)
Esempio n. 4
0
	def test_geoip_raw_geolocation(self):
		loc = geoip.GeoLocation(GEO_TEST_IP)
		loc_raw = geoip.GeoLocation(GEO_TEST_IP, result=geoip.lookup(GEO_TEST_IP))
		for field in geoip.DB_RESULT_FIELDS:
			self.assertEqual(getattr(loc, field), getattr(loc_raw, field))
		self.assertIsInstance(loc.ip_address, ipaddress.IPv4Address)
		self.assertEqual(loc.ip_address, ipaddress.ip_address(GEO_TEST_IP))
Esempio n. 5
0
def lookup(ip, lang='en'):
	"""
	Lookup the geo location information for the specified IP from the configured
	GeoLite2 City database.

	:param str ip: The IP address to look up the information for.
	:param str lang: The language to prefer for regional names.
	:return: The geo location information as a dict. The keys are the values of
		:py:data:`.DB_RESULT_FIELDS`.
	:rtype: dict
	"""
	if not _geoip_db:
		raise RuntimeError('the geoip database has not been initialized yet')
	lang = (lang or 'en')
	if isinstance(ip, str):
		ip = ipaddress.ip_address(ip)
	if isinstance(ip, ipaddress.IPv6Address):
		raise TypeError('ipv6 addresses are not supported at this time')
	if ip.is_loopback or ip.is_private:
		raise RuntimeError('the specified IP address is not a public IP address')
	with _geoip_db_lock:
		city = _geoip_db.city(str(ip))
	result = {}
	result['city'] = city.city.names.get(lang)
	result['continent'] = city.continent.names.get(lang)
	result['coordinates'] = (city.location.latitude, city.location.longitude)
	result['country'] = city.country.names.get(lang)
	result['postal_code'] = city.postal.code
	result['time_zone'] = city.location.time_zone
	return result
Esempio n. 6
0
def campaign_visits_to_geojson(rpc, campaign_id, geojson_file):
	"""
	Export the geo location information for all the visits of a campaign into
	the `GeoJSON <http://geojson.org/>`_ format.

	:param rpc: The connected RPC instance to load the information with.
	:type rpc: :py:class:`.KingPhisherRPCClient`
	:param campaign_id: The ID of the campaign to load the information for.
	:param str geojson_file: The destination file for the GeoJSON data.
	"""
	ips_for_georesolution = {}
	ip_counter = collections.Counter()
	for visit in rpc.remote_table('visits', query_filter={'campaign_id': campaign_id}):
		ip_counter.update((visit.visitor_ip,))
		visitor_ip = ipaddress.ip_address(visit.visitor_ip)
		if not isinstance(visitor_ip, ipaddress.IPv4Address):
			continue
		if visitor_ip.is_loopback or visitor_ip.is_private:
			continue
		if not visitor_ip in ips_for_georesolution:
			ips_for_georesolution[visitor_ip] = visit.first_visit
		elif ips_for_georesolution[visitor_ip] > visit.first_visit:
			ips_for_georesolution[visitor_ip] = visit.first_visit
	ips_for_georesolution = [ip for (ip, _) in sorted(ips_for_georesolution.items(), key=lambda x: x[1])]
	locations = {}
	for ip_addresses in iterutils.chunked(ips_for_georesolution, 50):
		locations.update(rpc.geoip_lookup_multi(ip_addresses))
	points = []
	for ip, location in locations.items():
		if not (location.coordinates and location.coordinates[0] and location.coordinates[1]):
			continue
		points.append(geojson.Feature(geometry=location, properties={'count': ip_counter[ip], 'ip-address': ip}))
	feature_collection = geojson.FeatureCollection(points)
	with open(geojson_file, 'w') as file_h:
		json_ex.dump(feature_collection, file_h)
Esempio n. 7
0
	def from_ip_address(cls, ip_address):
		ip_address = ipaddress.ip_address(ip_address)
		if ip_address.is_private:
			return
		result = geoip.lookup(ip_address)
		if result is None:
			return
		return cls(**result)
Esempio n. 8
0
	def do_OPTIONS(self):
		available_methods = list(x[3:] for x in dir(self) if x.startswith('do_'))
		if 'RPC' in available_methods:
			if not len(self.rpc_handler_map) or not ipaddress.ip_address(self.client_address[0]).is_loopback:
				self.logger.debug('removed RPC method from Allow header in OPTIONS reply')
				available_methods.remove('RPC')
		self.send_response(200)
		self.send_header('Content-Length', 0)
		self.send_header('Allow', ', '.join(available_methods))
		self.end_headers()
Esempio n. 9
0
	def from_ip_address(cls, ip_address):
		ip_address = ipaddress.ip_address(ip_address)
		if ip_address.is_private:
			return
		try:
			result = geoip.lookup(ip_address)
		except geoip2.errors.AddressNotFoundError:
			result = None
		if result is None:
			return
		return cls(**result)
Esempio n. 10
0
	def _resolve_geolocations(self, all_ips):
		geo_locations = {}
		public_ips = []
		for visitor_ip in all_ips:
			ip = ipaddress.ip_address(visitor_ip)
			if ip.is_private or ip.is_loopback:
				continue
			public_ips.append(visitor_ip)
		public_ips.sort()
		for ip_chunk in iterutils.chunked(public_ips, 100):
			geo_locations.update(self.rpc.geoip_lookup_multi(ip_chunk))
		return geo_locations
Esempio n. 11
0
	def expand_macros(self, value, ip, domain, sender):
		"""
		Expand a string based on the macros it contains as specified by section
		7 of RFC 7208.

		:param str value: The string containing macros to expand.
		:param ip: The IP address to use when expanding macros.
		:type ip: str, :py:class:`ipaddress.IPv4Address`, :py:class:`ipaddress.IPv6Address`
		:param str domain: The domain name to use when expanding macros.
		:param str sender: The email address of the sender to use when expanding macros.
		:return: The string with the interpreted macros replaced within it.
		:rtype: str
		"""
		if isinstance(ip, str):
			ip = ipaddress.ip_address(ip)

		macro_table = {
			's': sender,
			'l': sender.split('@', 1)[0],
			'o': sender.split('@', 1)[1],
			'd': domain,
			'i': (str(ip) if isinstance(ip, ipaddress.IPv4Address) else '.'.join(ip.exploded.replace(':', ''))),
			#'p'
			'v': ('in-addr' if isinstance(ip, ipaddress.IPv4Address) else 'ip6'),
			'h': self.helo_domain
		}

		for escape in (('%%', '%'), ('%-', '%20'), ('%_', ' ')):
			value = value.replace(*escape)

		end = 0
		result = ''
		for match in MACRO_REGEX.finditer(value):
			result += value[end:match.start()]
			macro_type = match.group(1)
			macro_digit = int(match.group(2) or 128)
			macro_reverse = (match.group(3) == 'r')
			macro_delimiter = (match.group(4) or '.')

			if not macro_type in macro_table:
				raise SPFPermError("unsupported macro type: '{0}'".format(macro_type))
			macro_value = macro_table[macro_type]
			macro_value = macro_value.split(macro_delimiter)
			if macro_reverse:
				macro_value.reverse()
			macro_value = macro_value[-macro_digit:]
			macro_value = '.'.join(macro_value)

			result += macro_value
			end = match.end()
		result += value[end:]
		return result
Esempio n. 12
0
 def do_OPTIONS(self):
     available_methods = list(x[3:] for x in dir(self)
                              if x.startswith('do_'))
     if 'RPC' in available_methods:
         if not len(self.rpc_handler_map) or not ipaddress.ip_address(
                 self.client_address[0]).is_loopback:
             self.logger.debug(
                 'removed RPC method from Allow header in OPTIONS reply')
             available_methods.remove('RPC')
     self.send_response(200)
     self.send_header('Content-Length', 0)
     self.send_header('Allow', ', '.join(available_methods))
     self.end_headers()
Esempio n. 13
0
	def check_authorization(self):
		# don't require authentication for non-RPC requests
		if self.command != 'RPC':
			return True
		if not ipaddress.ip_address(self.client_address[0]).is_loopback:
			return False

		if self.path in ('/version', '/login'):
			return True
		self.rpc_session = self.server.session_manager.get(self.rpc_session_id)
		if not isinstance(self.rpc_session, aaa.AuthenticatedSession):
			return False
		return True
Esempio n. 14
0
    def check_authorization(self):
        # don't require authentication for non-RPC requests
        if self.command != 'RPC':
            return True
        if not ipaddress.ip_address(self.client_address[0]).is_loopback:
            return False

        if self.path in ('/version', '/login'):
            return True
        self.rpc_session = self.server.session_manager.get(self.rpc_session_id)
        if not isinstance(self.rpc_session, aaa.AuthenticatedSession):
            return False
        return True
Esempio n. 15
0
def guess_smtp_server_address(host, forward_host=None):
    """
	Guess the IP address of the SMTP server that will be connected to given the
	SMTP host information and an optional SSH forwarding host. If a hostname is
	in use it will be resolved to an IP address, either IPv4 or IPv6 and in that
	order. If a hostname resolves to multiple IP addresses, None will be
	returned. This function is intended to guess the SMTP servers IP address
	given the client configuration so it can be used for SPF record checks.

	:param str host: The SMTP server that is being connected to.
	:param str forward_host: An optional host that is being used to tunnel the connection.
	:return: The IP address of the SMTP server.
	:rtype: None, :py:class:`ipaddress.IPv4Address`, :py:class:`ipaddress.IPv6Address`
	"""
    host = host.rsplit(':', 1)[0]
    if ipaddress.is_valid(host):
        ip = ipaddress.ip_address(host)
        if not ip.is_loopback:
            return ip
    else:
        info = None
        for family in (socket.AF_INET, socket.AF_INET6):
            try:
                info = socket.getaddrinfo(host, 1, family)
            except socket.gaierror:
                continue
            info = set(list([r[4][0] for r in info]))
            if len(info) != 1:
                return
            break
        if info:
            ip = ipaddress.ip_address(info.pop())
            if not ip.is_loopback:
                return ip
    if forward_host:
        return guess_smtp_server_address(forward_host)
    return
Esempio n. 16
0
def guess_smtp_server_address(host, forward_host=None):
	"""
	Guess the IP address of the SMTP server that will be connected to given the
	SMTP host information and an optional SSH forwarding host. If a hostname is
	in use it will be resolved to an IP address, either IPv4 or IPv6 and in that
	order. If a hostname resolves to multiple IP addresses, None will be
	returned. This function is intended to guess the SMTP servers IP address
	given the client configuration so it can be used for SPF record checks.

	:param str host: The SMTP server that is being connected to.
	:param str forward_host: An optional host that is being used to tunnel the connection.
	:return: The IP address of the SMTP server.
	:rtype: None, :py:class:`ipaddress.IPv4Address`, :py:class:`ipaddress.IPv6Address`
	"""
	host = host.rsplit(':', 1)[0]
	if ipaddress.is_valid(host):
		ip = ipaddress.ip_address(host)
		if not ip.is_loopback:
			return ip
	else:
		info = None
		for family in (socket.AF_INET, socket.AF_INET6):
			try:
				info = socket.getaddrinfo(host, 1, family)
			except socket.gaierror:
				continue
			info = set(list([r[4][0] for r in info]))
			if len(info) != 1:
				return
			break
		if info:
			ip = ipaddress.ip_address(info.pop())
			if not ip.is_loopback:
				return ip
	if forward_host:
		return guess_smtp_server_address(forward_host)
	return
Esempio n. 17
0
 def wrapped(handler, params):
     client_ip = ipaddress.ip_address(handler.client_address[0])
     config = handler.config
     if not config.get('server.rest_api.enabled'):
         logger.warning(
             "denying REST API request from {0} (REST API is disabled)".
             format(client_ip))
         handler.respond_unauthorized()
         return
     networks = config.get_if_exists('server.rest_api.networks')
     if networks is not None:
         if isinstance(networks, str):
             networks = (networks, )
         found = False
         for network in networks:
             if client_ip in ipaddress.ip_network(network, strict=False):
                 found = True
                 break
         if not found:
             logger.warning(
                 "denying REST API request from {0} (origin is from an unauthorized network)"
                 .format(client_ip))
             handler.respond_unauthorized()
             return
     if not handler.config.get('server.rest_api.token'):
         logger.warning(
             "denying REST API request from {0} (configured token is invalid)"
             .format(client_ip))
         handler.respond_unauthorized()
         return
     if config.get('server.rest_api.token') != handler.get_query('token'):
         logger.warning(
             "denying REST API request from {0} (invalid authentication token)"
             .format(client_ip))
         handler.respond_unauthorized()
         return
     response = dict(result=handle_function(handler, params))
     response = json.dumps(response,
                           sort_keys=True,
                           indent=2,
                           separators=(',', ': '))
     response = response.encode('utf-8')
     handler.send_response(200)
     handler.send_header('Content-Type', 'application/json')
     handler.send_header('Content-Length', str(len(response)))
     handler.end_headers()
     handler.wfile.write(response)
     return
Esempio n. 18
0
def campaign_visits_to_geojson(rpc, campaign_id, geojson_file):
    """
	Export the geo location information for all the visits of a campaign into
	the `GeoJSON <http://geojson.org/>`_ format.

	:param rpc: The connected RPC instance to load the information with.
	:type rpc: :py:class:`.KingPhisherRPCClient`
	:param campaign_id: The ID of the campaign to load the information for.
	:param str geojson_file: The destination file for the GeoJSON data.
	"""
    ips_for_georesolution = {}
    ip_counter = collections.Counter()
    for visit in rpc.remote_table('visits',
                                  query_filter={'campaign_id': campaign_id}):
        ip_counter.update((visit.visitor_ip, ))
        visitor_ip = ipaddress.ip_address(visit.visitor_ip)
        if not isinstance(visitor_ip, ipaddress.IPv4Address):
            continue
        if visitor_ip.is_loopback or visitor_ip.is_private:
            continue
        if not visitor_ip in ips_for_georesolution:
            ips_for_georesolution[visitor_ip] = visit.first_visit
        elif ips_for_georesolution[visitor_ip] > visit.first_visit:
            ips_for_georesolution[visitor_ip] = visit.first_visit
    ips_for_georesolution = [
        ip for (ip,
                _) in sorted(ips_for_georesolution.items(), key=lambda x: x[1])
    ]
    locations = {}
    for ip_addresses in iterutils.chunked(ips_for_georesolution, 50):
        locations.update(rpc.geoip_lookup_multi(ip_addresses))
    points = []
    for ip, location in locations.items():
        if not (location.coordinates and location.coordinates[0]
                and location.coordinates[1]):
            continue
        points.append(
            geojson.Feature(geometry=location,
                            properties={
                                'count': ip_counter[ip],
                                'ip-address': ip
                            }))
    feature_collection = geojson.FeatureCollection(points)
    with open(geojson_file, 'w') as file_h:
        serializers.JSON.dump(feature_collection, file_h, pretty=True)
Esempio n. 19
0
    def rpc_version(self):
        """
		Get the version information of the server. This returns a
		dictionary with keys of version, version_info and rpc_api_version.
		These values are provided for the client to determine
		compatibility.

		:return: A dictionary with version information.
		:rtype: dict
		"""
        assert ipaddress.ip_address(self.client_address[0]).is_loopback

        vinfo = {
            'rpc_api_version': version.rpc_api_version,
            'version': version.version,
            'version_info': version.version_info._asdict()
        }
        return vinfo
Esempio n. 20
0
	def rpc_version(self):
		"""
		Get the version information of the server. This returns a
		dictionary with keys of version, version_info and rpc_api_version.
		These values are provided for the client to determine
		compatibility.

		:return: A dictionary with version information.
		:rtype: dict
		"""
		assert ipaddress.ip_address(self.client_address[0]).is_loopback

		vinfo = {
			'rpc_api_version': version.rpc_api_version,
			'version': version.version,
			'version_info': version.version_info._asdict()
		}
		return vinfo
Esempio n. 21
0
    def __init__(self, ip, domain, sender=None, timeout=DEFAULT_DNS_TIMEOUT):
        """
		:param ip: The IP address of the host sending the message.
		:type ip: str, :py:class:`ipaddress.IPv4Address`, :py:class:`ipaddress.IPv6Address`
		:param str domain: The domain to check the SPF policy of.
		:param str sender: The "MAIL FROM" identity of the message being sent.
		:param int timeout: The timeout for DNS queries.
		"""
        if isinstance(ip, str):
            ip = ipaddress.ip_address(ip)
        self.ip_address = ip
        self.domain = domain
        self.helo_domain = 'unknown'
        sender = (sender or 'postmaster')
        if not '@' in sender:
            sender = sender + '@' + self.domain
        self.sender = sender
        self.records = collections.OrderedDict()
        """
		A :py:class:`collections.OrderedDict` of all the SPF records that were
		resolved. This would be any records resolved due to an "include"
		directive in addition to the top level domain.
		"""
        self.matches = []
        """
		A list of :py:class:`.SPFMatch` instances showing the path traversed to
		identify a matching directive. Multiple entries in this list are
		present when include directives are used and a match is found within
		the body of one. The list is ordered from the top level domain to the
		matching record.
		"""
        # dns lookup limit per https://tools.ietf.org/html/rfc7208#section-4.6.4
        self.query_limit = MAX_QUERIES
        self.query_limit_void = MAX_QUERIES_VOID
        self.policy = None
        self.timeout = timeout
        """
		The human readable policy result, one of the
		:py:class:`.SPFResult` constants`.
		"""
        self._policy_checked = False
        self.logger = logging.getLogger(
            'KingPhisher.SPF.SenderPolicyFramework')
Esempio n. 22
0
 def format_node_data(self, node):
     geo_location = UNKNOWN_LOCATION_STRING
     visitor_ip = node['visitorIp']
     if visitor_ip is None:
         visitor_ip = ''
     else:
         visitor_ip = ipaddress.ip_address(visitor_ip)
         if visitor_ip.is_loopback:
             geo_location = 'N/A (Loopback)'
         elif visitor_ip.is_private:
             geo_location = 'N/A (Private)'
         elif isinstance(visitor_ip, ipaddress.IPv6Address):
             geo_location = 'N/A (IPv6 Address)'
         elif node['visitorGeoloc']:
             geo_location = node['visitorGeoloc']['city']
     row = (node['message']['targetEmail'], str(visitor_ip),
            node['visitCount'], node['visitorDetails'], geo_location,
            node['firstVisit'], node['lastVisit'])
     return row
Esempio n. 23
0
	def __init__(self, ip, lang='en', result=None):
		"""
		:param str ip: The IP address to look up geographic location data for.
		:param str lang: The language to prefer for regional names.
		:param dict result: A raw query result from a previous call to :py:func:`.lookup`.
		"""
		if isinstance(ip, str):
			ip = ipaddress.ip_address(ip)
		if not result:
			result = lookup(ip, lang=lang)
		self.ip_address = ip
		"""The :py:class:`~ipaddress.IPv4Address` which this geographic location data describes."""
		for field in DB_RESULT_FIELDS:
			if field not in result:
				raise RuntimeError('the retrieved information is missing required data field: ' + field)
			if field in ('coordinates',):
				continue
			setattr(self, field, result[field])
		self.coordinates = Coordinates(latitude=result['coordinates'][0], longitude=result['coordinates'][1])
Esempio n. 24
0
	def check_authorization(self):
		# don't require authentication for non-RPC requests
		cmd = self.command
		if cmd == 'GET':
			# check if the GET request is to open a web socket
			if 'upgrade' not in self.headers:
				return True
		elif cmd != 'RPC':
			return True

		if not ipaddress.ip_address(self.client_address[0]).is_loopback:
			return False

		# the only two RPC methods that do not require authentication
		if self.path in ('/login', '/version'):
			return True
		self.rpc_session = self.server.session_manager.get(self.rpc_session_id)
		if not isinstance(self.rpc_session, aaa.AuthenticatedSession):
			return False
		return True
Esempio n. 25
0
    def check_authorization(self):
        # don't require authentication for non-RPC requests
        cmd = self.command
        if cmd == 'GET':
            # check if the GET request is to open a web socket
            if 'upgrade' not in self.headers:
                return True
        elif cmd != 'RPC':
            return True

        if not ipaddress.ip_address(self.client_address[0]).is_loopback:
            return False

        # the only two RPC methods that do not require authentication
        if self.path in ('/login', '/version'):
            return True
        self.rpc_session = self.server.session_manager.get(self.rpc_session_id)
        if not isinstance(self.rpc_session, aaa.AuthenticatedSession):
            return False
        return True
Esempio n. 26
0
def rpc_login(handler, session, username, password, otp=None):
	logger = logging.getLogger('KingPhisher.Server.Authentication')
	if not ipaddress.ip_address(handler.client_address[0]).is_loopback:
		logger.warning("failed login request from {0} for user {1}, (invalid source address)".format(handler.client_address[0], username))
		raise ValueError('invalid source address for login')
	fail_default = (False, ConnectionErrorReason.ERROR_INVALID_CREDENTIALS, None)
	fail_otp = (False, ConnectionErrorReason.ERROR_INVALID_OTP, None)

	if not (username and password):
		logger.warning("failed login request from {0} for user {1}, (missing username or password)".format(handler.client_address[0], username))
		return fail_default
	if not handler.server.forked_authenticator.authenticate(username, password):
		logger.warning("failed login request from {0} for user {1}, (authentication failed)".format(handler.client_address[0], username))
		return fail_default

	user = session.query(db_models.User).filter_by(name=username).first()
	if not user:
		logger.info('creating new user object with name: ' + username)
		user = db_models.User(name=username)
	elif user.has_expired:
		logger.warning("failed login request from {0} for user {1}, (user has expired)".format(handler.client_address[0], username))
		return fail_default
	elif user.otp_secret:
		if otp is None:
			logger.debug("failed login request from {0} for user {1}, (missing otp)".format(handler.client_address[0], username))
			return fail_otp
		if not (isinstance(otp, str) and len(otp) == 6 and otp.isdigit()):
			logger.warning("failed login request from {0} for user {1}, (invalid otp)".format(handler.client_address[0], username))
			return fail_otp
		totp = pyotp.TOTP(user.otp_secret)
		now = datetime.datetime.now()
		if otp not in (totp.at(now + datetime.timedelta(seconds=offset)) for offset in (0, -30, 30)):
			logger.warning("failed login request from {0} for user {1}, (invalid otp)".format(handler.client_address[0], username))
			return fail_otp
	user.last_login = db_models.current_timestamp()
	session.add(user)
	session.commit()
	session_id = handler.server.session_manager.put(user.id)
	logger.info("successful login request from {0} for user {1}".format(handler.client_address[0], username))
	signals.send_safe('rpc-user-logged-in', logger, handler, session=session_id, name=username)
	return True, ConnectionErrorReason.SUCCESS, session_id
Esempio n. 27
0
def rpc_login(handler, session, username, password, otp=None):
	logger = logging.getLogger('KingPhisher.Server.Authentication')
	if not ipaddress.ip_address(handler.client_address[0]).is_loopback:
		logger.warning("failed login request from {0} for user {1}, (invalid source address)".format(handler.client_address[0], username))
		raise ValueError('invalid source address for login')
	fail_default = (False, ConnectionErrorReason.ERROR_INVALID_CREDENTIALS, None)
	fail_otp = (False, ConnectionErrorReason.ERROR_INVALID_OTP, None)

	if not (username and password):
		logger.warning("failed login request from {0} for user {1}, (missing username or password)".format(handler.client_address[0], username))
		return fail_default
	if not handler.server.forked_authenticator.authenticate(username, password):
		logger.warning("failed login request from {0} for user {1}, (authentication failed)".format(handler.client_address[0], username))
		return fail_default

	user = session.query(db_models.User).filter_by(name=username).first()
	if not user:
		logger.info('creating new user object with name: ' + username)
		user = db_models.User(name=username)
	elif user.has_expired:
		logger.warning("failed login request from {0} for user {1}, (user has expired)".format(handler.client_address[0], username))
		return fail_default
	elif user.otp_secret:
		if otp is None:
			logger.debug("failed login request from {0} for user {1}, (missing otp)".format(handler.client_address[0], username))
			return fail_otp
		if not (isinstance(otp, str) and len(otp) == 6 and otp.isdigit()):
			logger.warning("failed login request from {0} for user {1}, (invalid otp)".format(handler.client_address[0], username))
			return fail_otp
		totp = pyotp.TOTP(user.otp_secret)
		now = datetime.datetime.now()
		if otp not in (totp.at(now + datetime.timedelta(seconds=offset)) for offset in (0, -30, 30)):
			logger.warning("failed login request from {0} for user {1}, (invalid otp)".format(handler.client_address[0], username))
			return fail_otp
	user.last_login = db_models.current_timestamp()
	session.add(user)
	session.commit()
	session_id = handler.server.session_manager.put(user)
	logger.info("successful login request from {0} for user {1}".format(handler.client_address[0], username))
	signals.send_safe('rpc-user-logged-in', logger, handler, session=session_id, name=username)
	return True, ConnectionErrorReason.SUCCESS, session_id
Esempio n. 28
0
    def __init__(self, ip, lang='en', result=None):
        """
		:param str ip: The IP address to look up geographic location data for.
		:param str lang: The language to prefer for regional names.
		:param dict result: A raw query result from a previous call to :py:func:`.lookup`.
		"""
        if isinstance(ip, str):
            ip = ipaddress.ip_address(ip)
        if not result:
            result = lookup(ip, lang=lang)
        self.ip_address = ip
        """The :py:class:`~ipaddress.IPv4Address` which this geographic location data describes."""
        for field in DB_RESULT_FIELDS:
            if not field in result:
                raise RuntimeError(
                    'the retrieved information is missing required data')
            if field in ('coordinates', ):
                continue
            setattr(self, field, result[field])
        self.coordinates = Coordinates(latitude=result['coordinates'][0],
                                       longitude=result['coordinates'][1])
Esempio n. 29
0
def rpc_version(handler):
	"""
	Get the version information of the server. This returns a
	dictionary with keys of version, version_info and rpc_api_version.
	These values are provided for the client to determine
	compatibility.

	:return: A dictionary with version information.
	:rtype: dict
	"""
	if not ipaddress.ip_address(handler.client_address[0]).is_loopback:
		message = "an rpc request to /version was received from non-loopback IP address: {0}".format(handler.client_address[0])
		rpc_logger.error(message)
		raise errors.KingPhisherAPIError(message)

	vinfo = {
		'rpc_api_version': version.rpc_api_version,
		'version': version.version,
		'version_info': version.version_info._asdict()
	}
	return vinfo
Esempio n. 30
0
    def dispatch(self, handler):
        """
		A method that is suitable for use as a
		:py:attr:`~advancedhttpserver.RequestHandler.web_socket_handler`.

		:param handler: The current request handler instance.
		:type handler: :py:class:`~king_phisher.server.server.KingPhisherRequestHandler`
		"""
        if not ipaddress.ip_address(handler.client_address[0]).is_loopback:
            return
        prefix = '/'
        if self.config.get('server.vhost_directories'):
            prefix += handler.vhost + '/'
        request_path = handler.path
        if request_path.startswith(prefix):
            request_path = request_path[len(prefix):]
            if request_path == '_/ws/events/json':
                EventSocket(handler, self)
                return
        handler.respond_not_found()
        return
Esempio n. 31
0
	def dispatch(self, handler):
		"""
		A method that is suitable for use as a
		:py:attr:`~advancedhttpserver.RequestHandler.web_socket_handler`.

		:param handler: The current request handler instance.
		:type handler: :py:class:`~king_phisher.server.server.KingPhisherRequestHandler`
		"""
		if not ipaddress.ip_address(handler.client_address[0]).is_loopback:
			return
		prefix = '/'
		if self.config.get('server.vhost_directories'):
			prefix += handler.vhost + '/'
		request_path = handler.path
		if request_path.startswith(prefix):
			request_path = request_path[len(prefix):]
			if request_path == '_/ws/events/json':
				EventSocket(handler, self)
				return
		handler.respond_not_found()
		return
Esempio n. 32
0
def rpc_version(handler):
	"""
	Get the version information of the server. This returns a
	dictionary with keys of version, version_info and rpc_api_version.
	These values are provided for the client to determine
	compatibility.

	:return: A dictionary with version information.
	:rtype: dict
	"""
	if not ipaddress.ip_address(handler.client_address[0]).is_loopback:
		message = "an rpc request to /version was received from non-loopback IP address: {0}".format(handler.client_address[0])
		rpc_logger.error(message)
		raise errors.KingPhisherAPIError(message)

	vinfo = {
		'rpc_api_version': version.rpc_api_version,
		'version': version.version,
		'version_info': version.version_info._asdict()
	}
	return vinfo
Esempio n. 33
0
	def __init__(self, ip, domain, sender=None):
		"""
		:param ip: The IP address of the host sending the message.
		:type ip: str, :py:class:`ipaddress.IPv4Address`, :py:class:`ipaddress.IPv6Address`
		:param str domain: The domain to check the SPF policy of.
		:param str sender: The "MAIL FROM" identity of the message being sent.
		"""
		if isinstance(ip, str):
			ip = ipaddress.ip_address(ip)
		self.ip_address = ip
		self.domain = domain
		self.helo_domain = 'unknown'
		sender = (sender or 'postmaster')
		if not '@' in sender:
			sender = sender + '@' + self.domain
		self.sender = sender
		self.records = collections.OrderedDict()
		"""
		A :py:class:`collections.OrderedDict` of all the SPF records that were
		resolved. This would be any records resolved due to an "include"
		directive in addition to the top level domain.
		"""
		self.matches = []
		"""
		A list of :py:class:`.SPFMatch` instances showing the path traversed to
		identify a matching directive. Multiple entries in this list are
		present when include directives are used and a match is found within
		the body of one. The list is ordered from the top level domain to the
		matching record.
		"""
		# dns lookup limit per https://tools.ietf.org/html/rfc7208#section-4.6.4
		self.query_limit = MAX_QUERIES
		self.query_limit_void = MAX_QUERIES_VOID
		self.policy = None
		"""
		The human readable policy result, one of the
		:py:class:`.SPFResult` constants`.
		"""
		self._policy_checked = False
		self.logger = logging.getLogger('KingPhisher.SPF.SenderPolicyFramework')
Esempio n. 34
0
 def wrapped(handler, params):
     client_ip = ipaddress.ip_address(handler.client_address[0])
     config = handler.config
     if not config.get("server.rest_api.enabled"):
         logger.warning("denying REST API request from {0} (REST API is disabled)".format(client_ip))
         handler.respond_unauthorized()
         return
     networks = config.get_if_exists("server.rest_api.networks")
     if networks is not None:
         if isinstance(networks, str):
             networks = (networks,)
         found = False
         for network in networks:
             if client_ip in ipaddress.ip_network(network, strict=False):
                 found = True
                 break
         if not found:
             logger.warning(
                 "denying REST API request from {0} (origin is from an unauthorized network)".format(client_ip)
             )
             handler.respond_unauthorized()
             return
     if not handler.config.get("server.rest_api.token"):
         logger.warning("denying REST API request from {0} (configured token is invalid)".format(client_ip))
         handler.respond_unauthorized()
         return
     if config.get("server.rest_api.token") != handler.get_query("token"):
         logger.warning("denying REST API request from {0} (invalid authentication token)".format(client_ip))
         handler.respond_unauthorized()
         return
     response = dict(result=handle_function(handler, params))
     response = json.dumps(response, sort_keys=True, indent=2, separators=(",", ": "))
     response = response.encode("utf-8")
     handler.send_response(200)
     handler.send_header("Content-Type", "application/json")
     handler.send_header("Content-Length", str(len(response)))
     handler.end_headers()
     handler.wfile.write(response)
     return
Esempio n. 35
0
 def format_row_data(self, visit):
     msg_details = self.rpc.remote_table_row('messages',
                                             visit.message_id,
                                             cache=True)
     if not msg_details:
         return None
     visitor_ip = ipaddress.ip_address(visit.visitor_ip)
     geo_location = UNKNOWN_LOCATION_STRING
     if visitor_ip.is_loopback:
         geo_location = 'N/A (Loopback)'
     elif visitor_ip.is_private:
         geo_location = 'N/A (Private)'
     elif isinstance(visitor_ip, ipaddress.IPv6Address):
         geo_location = 'N/A (IPv6 Address)'
     else:
         if not visitor_ip in self._ips_for_georesolution:
             self._ips_for_georesolution[visitor_ip] = visit.first_visit
         elif self._ips_for_georesolution[visitor_ip] > visit.first_visit:
             self._ips_for_georesolution[visitor_ip] = visit.first_visit
     row = (msg_details.target_email, str(visitor_ip), visit.visit_count,
            visit.visitor_details, geo_location, visit.first_visit,
            visit.last_visit)
     return row
Esempio n. 36
0
	def __init__(self, ip, domain, sender=None, spf_records=None):
		"""
		:param ip: The IP address of the host sending the message.
		:type ip: str, :py:class:`ipaddress.IPv4Address`, :py:class:`ipaddress.IPv6Address`
		:param str domain: The domain to check the SPF policy of.
		:param str sender: The "MAIL FROM" identity of the message being sent.
		"""
		if isinstance(ip, str):
			ip = ipaddress.ip_address(ip)
		self.ip_address = ip
		self.domain = domain
		self.helo_domain = 'unknown'
		sender = (sender or 'postmaster')
		if not '@' in sender:
			sender = sender + '@' + self.domain
		self.sender = sender
		self.spf_records = (spf_records or [])
		self.spf_record_id = -1

		# dns lookup limit per https://tools.ietf.org/html/rfc7208#section-4.6.4
		self.query_limit = 10
		self.policy = None
		self._policy_checked = False
		self.logger = logging.getLogger('KingPhisher.SPF.SenderPolicyFramework')