def _respond_file_check_id(self): if re.match(r'^[._]metadata\.(json|yaml|yml)$', os.path.basename(self.request_path)): self.server.logger.warning('received request for template metadata file') raise errors.KingPhisherAbortRequestError() if re.match(r'^/\.well-known/acme-challenge/[a-zA-Z0-9\-_]{40,50}$', self.request_path): self.server.logger.info('received request for .well-known/acme-challenge') return if not self.config.get('server.require_id'): return if self.message_id == self.config.get('server.secret_id'): self.server.logger.debug('request received with the correct secret id') return # a valid campaign_id requires a valid message_id if not self.campaign_id: self.server.logger.warning('denying request due to lack of a valid id') raise errors.KingPhisherAbortRequestError() campaign = db_manager.get_row_by_id(self._session, db_models.Campaign, self.campaign_id) query = self._session.query(db_models.LandingPage) query = query.filter_by(campaign_id=self.campaign_id, hostname=self.vhost) if query.count() == 0: self.server.logger.warning('denying request with not found due to invalid hostname') raise errors.KingPhisherAbortRequestError() if campaign.has_expired: self.server.logger.warning('denying request because the campaign has expired') raise errors.KingPhisherAbortRequestError() if campaign.max_credentials is not None and self.visit_id is None: query = self._session.query(db_models.Credential) query = query.filter_by(message_id=self.message_id) if query.count() >= campaign.max_credentials: self.server.logger.warning('denying request because the maximum number of credentials have already been harvested') raise errors.KingPhisherAbortRequestError() return
def _respond_file_check_id(self): if not self.config.get('server.require_id'): return if self.message_id == self.config.get('server.secret_id'): return # a valid campaign_id requires a valid message_id if not self.campaign_id: self.server.logger.warning( 'denying request due to lack of a valid id') raise errors.KingPhisherAbortRequestError() session = db_manager.Session() campaign = db_manager.get_row_by_id(session, db_models.Campaign, self.campaign_id) query = session.query(db_models.LandingPage) query = query.filter_by(campaign_id=self.campaign_id, hostname=self.vhost) if query.count() == 0: self.server.logger.warning( 'denying request with not found due to invalid hostname') session.close() raise errors.KingPhisherAbortRequestError() if campaign.reject_after_credentials and self.visit_id == None: query = session.query(db_models.Credential) query = query.filter_by(message_id=self.message_id) if query.count(): self.server.logger.warning( 'denying request because credentials were already harvested' ) session.close() raise errors.KingPhisherAbortRequestError() session.close() return
def adjust_path(self): """Adjust the :py:attr:`~.KingPhisherRequestHandler.path` attribute based on multiple factors.""" self.request_path = self.path.split('?', 1)[0] if not self.config.get('server.vhost_directories'): return if not self.vhost: raise errors.KingPhisherAbortRequestError() if self.vhost in ('localhost', '127.0.0.1') and self.client_address[0] != '127.0.0.1': raise errors.KingPhisherAbortRequestError() self.path = '/' + self.vhost + self.path
def _respond_file_check_id(self): if re.match(r'^/\.well-known/acme-challenge/[a-zA-Z0-9\-_]{40,50}$', self.request_path): self.server.logger.info( 'received request for .well-known/acme-challenge') return if not self.config.get('server.require_id'): return self.semaphore_acquire() if self.message_id == self.config.get('server.secret_id'): self.semaphore_release() self.server.logger.debug( 'request received with the correct secret id') return # a valid campaign_id requires a valid message_id if not self.campaign_id: self.semaphore_release() self.server.logger.warning( 'denying request due to lack of a valid id') raise errors.KingPhisherAbortRequestError() session = db_manager.Session() campaign = db_manager.get_row_by_id(session, db_models.Campaign, self.campaign_id) query = session.query(db_models.LandingPage) query = query.filter_by(campaign_id=self.campaign_id, hostname=self.vhost) if query.count() == 0: session.close() self.semaphore_release() self.server.logger.warning( 'denying request with not found due to invalid hostname') raise errors.KingPhisherAbortRequestError() if campaign.has_expired: session.close() self.semaphore_release() self.server.logger.warning( 'denying request because the campaign has expired') raise errors.KingPhisherAbortRequestError() if campaign.reject_after_credentials and self.visit_id is None: query = session.query(db_models.Credential) query = query.filter_by(message_id=self.message_id) if query.count(): session.close() self.semaphore_release() self.server.logger.warning( 'denying request because credentials were already harvested' ) raise errors.KingPhisherAbortRequestError() session.close() self.semaphore_release() return
def _respond_file_raw(self, file_path, attachment): try: file_obj = open(file_path, 'rb') except IOError: raise errors.KingPhisherAbortRequestError() fs = os.fstat(file_obj.fileno()) headers = collections.deque([('Content-Type', self.guess_mime_type(file_path)), ('Content-Length', fs[6])]) if attachment: file_name = os.path.basename(file_path) headers.append(('Content-Disposition', 'attachment; filename=' + file_name)) headers.append(('Last-Modified', self.date_time_string(fs.st_mtime))) self.semaphore_acquire() try: headers.extend(self.handle_page_visit() or []) except Exception as error: self.server.logger.error('handle_page_visit raised error: {0}.{1}'.format(error.__class__.__module__, error.__class__.__name__), exc_info=True) finally: self.semaphore_release() self.send_response(200) for header in headers: self.send_header(*header) self.end_headers() shutil.copyfileobj(file_obj, self.wfile) file_obj.close() return
def on_request_handle(self, handler): if handler.command == 'RPC' or handler.path.startswith('/_/'): return client_ip = ipaddress.ip_address(handler.client_address[0]) for rule in self.config.get('rules', []): if client_ip not in rule['source']: continue target = rule.get('target') if not target: self.logger.debug("request redirect rule for {0} matched exception".format(str(client_ip))) break self.logger.debug("request redirect rule for {0} matched target: {1}".format(str(client_ip), target)) self.respond_redirect(handler, rule) raise errors.KingPhisherAbortRequestError(response_sent=True)
def _respond_file_raw(self, file_path, attachment): try: file_obj = open(file_path, 'rb') except IOError: raise errors.KingPhisherAbortRequestError() fs = os.fstat(file_obj.fileno()) self.send_response(200) self.send_header('Content-Type', self.guess_mime_type(file_path)) self.send_header('Content-Length', fs[6]) if attachment: file_name = os.path.basename(file_path) self.send_header('Content-Disposition', 'attachment; filename=' + file_name) self.send_header('Last-Modified', self.date_time_string(fs.st_mtime)) self.end_headers() shutil.copyfileobj(file_obj, self.wfile) file_obj.close() return
def on_request_handle(self, handler): if handler.command == 'RPC' or handler.path.startswith('/_/'): return client_ip = ipaddress.ip_address(handler.client_address[0]) for entry in self.entries: if 'rule' in entry and not entry['rule'].matches(handler): continue if 'source' in entry and client_ip not in entry['source']: continue target = entry.get('target') if not target: self.logger.debug( "request redirect rule for {0} matched exception".format( str(client_ip))) break self.logger.debug( "request redirect rule for {0} matched target: {1}".format( str(client_ip), target)) self.respond_redirect(handler, entry) raise errors.KingPhisherAbortRequestError(response_sent=True)
def respond_file(self, file_path, attachment=False, query=None): self._respond_file_check_id() file_path = os.path.abspath(file_path) mime_type = self.guess_mime_type(file_path) if attachment or (mime_type != 'text/html' and mime_type != 'text/plain'): self._respond_file_raw(file_path, attachment) return try: template = self.server.template_env.get_template( os.path.relpath(file_path, self.config.get('server.web_root'))) except jinja2.exceptions.TemplateSyntaxError as error: self.server.logger.error( "jinja2 syntax error in template {0}:{1} {2}".format( error.filename, error.lineno, error.message)) raise errors.KingPhisherAbortRequestError() except jinja2.exceptions.TemplateError: raise errors.KingPhisherAbortRequestError() except UnicodeDecodeError as error: self.server.logger.error( "unicode error {0} in template file: {1}:{2}-{3}".format( error.reason, file_path, error.start, error.end)) raise errors.KingPhisherAbortRequestError() template_data = '' headers = [] template_vars = { 'client': self.get_template_vars_client(), 'request': { 'command': self.command, 'cookies': dict((c[0], c[1].value) for c in self.cookies.items()), 'parameters': dict( zip(self.query_data.keys(), map(self.get_query, self.query_data.keys()))), 'user_agent': self.headers.get('user-agent') }, 'server': { 'hostname': self.vhost, 'address': self.connection.getsockname()[0] } } template_vars.update(self.server.template_env.standard_variables) try: template_module = template.make_module(template_vars) except (TypeError, jinja2.TemplateError) as error: self.server.logger.error( "jinja2 template {0} render failed: {1} {2}".format( template.filename, error.__class__.__name__, error.message)) raise errors.KingPhisherAbortRequestError() if getattr(template_module, 'require_basic_auth', False) and not all(self.get_query_creds(check_query=False)): mime_type = 'text/html' self.send_response(401) headers.append(('WWW-Authenticate', "Basic realm=\"{0}\"".format( getattr(template_module, 'basic_auth_realm', 'Authentication Required')))) else: try: template_data = template.render(template_vars) except (TypeError, jinja2.TemplateError) as error: self.server.logger.error( "jinja2 template {0} render failed: {1} {2}".format( template.filename, error.__class__.__name__, error.message)) raise errors.KingPhisherAbortRequestError() self.send_response(200) headers.append( ('Last-Modified', self.date_time_string(os.stat(template.filename).st_mtime))) if mime_type.startswith('text'): mime_type += '; charset=utf-8' self.send_header('Content-Type', mime_type) self.send_header('Content-Length', len(template_data)) for header in headers: self.send_header(*header) try: self.handle_page_visit() except Exception as error: self.server.logger.error( 'handle_page_visit raised error: {0}.{1}'.format( error.__class__.__module__, error.__class__.__name__), exc_info=True) self.end_headers() self.wfile.write(template_data.encode('utf-8', 'ignore')) return
def respond_file(self, file_path, attachment=False, query=None): self.semaphore_acquire() try: self._set_ids() self._respond_file_check_id() finally: self.semaphore_release() file_path = os.path.abspath(file_path) mime_type = self.guess_mime_type(file_path) if attachment or (mime_type != 'text/html' and mime_type != 'text/plain'): self._respond_file_raw(file_path, attachment) return try: template = self.server.template_env.get_template(os.path.relpath(file_path, self.config.get('server.web_root'))) except jinja2.exceptions.TemplateSyntaxError as error: self.server.logger.error("jinja2 syntax error in template {0}:{1} {2}".format(error.filename, error.lineno, error.message)) raise errors.KingPhisherAbortRequestError() except jinja2.exceptions.TemplateError: raise errors.KingPhisherAbortRequestError() except UnicodeDecodeError as error: self.server.logger.error("unicode error {0} in template file: {1}:{2}-{3}".format(error.reason, file_path, error.start, error.end)) raise errors.KingPhisherAbortRequestError() self.semaphore_acquire() headers = collections.deque() try: headers.extend(self.handle_page_visit() or []) except Exception as error: self.server.logger.error('handle_page_visit raised error: {0}.{1}'.format(error.__class__.__module__, error.__class__.__name__), exc_info=True) template_vars = self.get_template_vars() try: template_module = template.make_module(template_vars) except (TypeError, jinja2.TemplateError) as error: self.semaphore_release() self.server.logger.error("jinja2 template {0} render failed: {1} {2}".format(template.filename, error.__class__.__name__, getattr(error, 'message', ''))) raise errors.KingPhisherAbortRequestError() query_creds = self.get_query_creds(check_query=False) require_basic_auth = getattr(template_module, 'require_basic_auth', False) require_basic_auth &= not (query_creds.username and query_creds.password) require_basic_auth &= self.message_id != self.config.get('server.secret_id') template_data = b'' if require_basic_auth: mime_type = 'text/html' self.send_response(401) headers.append(('WWW-Authenticate', "Basic realm=\"{0}\"".format(getattr(template_module, 'basic_auth_realm', 'Authentication Required')))) else: self.send_response(200) headers.append(('Last-Modified', self.date_time_string(os.stat(template.filename).st_mtime))) template_data = str(template_module).encode('utf-8', 'ignore') if mime_type.startswith('text'): mime_type += '; charset=utf-8' headers.extendleft([('Content-Type', mime_type), ('Content-Length', len(template_data))]) for header in headers: self.send_header(*header) self.semaphore_release() self.end_headers() self.wfile.write(template_data) return
def respond_file(self, file_path, attachment=False, query={}): self._respond_file_check_id() file_path = os.path.abspath(file_path) mime_type = self.guess_mime_type(file_path) if attachment or mime_type != 'text/html': self._respond_file_raw(file_path, attachment) return try: template = self.server.template_env.get_template( os.path.relpath(file_path, self.server.serve_files_root)) except jinja2.exceptions.TemplateSyntaxError as error: self.server.logger.error( "jinja2 syntax error in template {0}:{1} {2}".format( error.filename, error.lineno, error.message)) raise errors.KingPhisherAbortRequestError() except jinja2.exceptions.TemplateError: raise errors.KingPhisherAbortRequestError() template_vars = { 'client': { 'address': self.client_address[0] }, 'request': { 'command': self.command, 'cookies': dict((c[0], c[1].value) for c in self.cookies.items()), 'parameters': dict( zip(self.query_data.keys(), map(self.get_query, self.query_data.keys()))) }, 'server': { 'hostname': self.vhost, 'address': self.connection.getsockname()[0] } } template_vars.update(self.server.template_env.standard_variables) template_vars['client'].update(self.get_template_vars_client() or {}) try: template_data = template.render(template_vars) except jinja2.TemplateError as error: self.server.logger.error( "jinja2 template {0} render failed: {1} {2}".format( template.filename, error.__class__.__name__, error.message)) raise errors.KingPhisherAbortRequestError() fs = os.stat(template.filename) if mime_type.startswith('text'): mime_type = mime_type + '; charset=utf-8' self.send_response(200) self.send_header('Content-Type', mime_type) self.send_header('Content-Length', str(len(template_data))) self.send_header('Last-Modified', self.date_time_string(fs.st_mtime)) try: self.handle_page_visit() except Exception as error: self.server.logger.error( 'handle_page_visit raised error: {0}.{1}'.format( error.__class__.__module__, error.__class__.__name__), exc_info=True) self.end_headers() self.wfile.write(template_data.encode('utf-8', 'ignore')) return