diff --git a/pyscripter.py b/pyscripter.py index 29244ac..eec910d 100644 --- a/pyscripter.py +++ b/pyscripter.py @@ -10,7 +10,42 @@ def __init__(self, extender, callbacks, helpers, toolFlag, messageIsRequest, mes self.messageInfo = messageInfo self.debug = False + def _context(context=None, tools=[], scope=False): + """Currently unused decorator because it hides the method's signature + from introspection, which is needed. See `_in_context` hack below. + """ + + def wrapper(func): + @wraps(func) + def wrapped(self, *args, **kwargs): + if (context and + context == 'request' and not self.messageIsRequest or + context == 'response' and self.messageIsRequest): + return + toolFlags = [getattr(self.callbacks, 'TOOL_{}'.format(t.upper)) for t in tools] + if tools and self.toolFlag not in toolFlags: + return + if scope and not self._in_scope(): + return + return func(self, *args, **kwargs) + return wrapped + return wrapper + + def _in_context(self, context=None, tools=[], scope=False): + """Checks the provided parameters against the current context.""" + + if (context and + context == 'request' and not self.messageIsRequest or + context == 'response' and self.messageIsRequest): + return False + if (tools and self.toolFlag not in tools): + return False + if (scope and not self._in_scope()): + return False + return True + def _in_scope(self): + """Determines if the current request is in scope.""" in_scope = self.callbacks.isInScope(self.messageInfo.getUrl()) mode = 'Response' if self.messageIsRequest else 'Request' @@ -28,26 +63,28 @@ def _debug(self, message): def help(self): """Displays this help interface.""" - if self.messageIsRequest: - print(help(self)) + if not self._in_context(context='request'): return + + print(help(self)) def introspect(self): """Provides introspection into the Python Scripter API.""" + if not self._in_context(context='request'): return + import sys apis = ('extender', 'callbacks', 'helpers', 'toolFlag', 'messageIsRequest', 'messageInfo') funcs = (type, dir) - if self.messageIsRequest: - for api in apis: - print('\n{}:\n{}'.format(api, '='*len(api))) - for func in funcs: - print('\n{}:\n'.format(func.__name__)) - print(func(getattr(self, api))) + for api in apis: + print('\n{}:\n{}'.format(api, '='*len(api))) + for func in funcs: + print('\n{}:\n'.format(func.__name__)) + print(func(getattr(self, api))) self._debug('Introspection complete.') - def remove_header(self, headers, header_name): + def _remove_header(self, headers, header_name): """Removes a specific header from a list of headers.""" for header in headers: @@ -60,82 +97,73 @@ def remove_header(self, headers, header_name): def remove_headers(self, header_names): """Removes a list of headers from the current request.""" - if self.messageIsRequest: - request = self.helpers.analyzeRequest(self.messageInfo) - headers = request.getHeaders() - for header_name in header_names: - headers = self.remove_header(headers, header_name) - body = self.messageInfo.getRequest()[request.getBodyOffset():] - new_request = self.helpers.buildHttpMessage(headers, body) - self.messageInfo.setRequest(new_request) - self._debug('Headers removed: {}'.format(', '.join(header_names))) + if not self._in_context(context='request'): return + + request = self.helpers.analyzeRequest(self.messageInfo) + headers = request.getHeaders() + for header_name in header_names: + headers = self._remove_header(headers, header_name) + body = self.messageInfo.getRequest()[request.getBodyOffset():] + new_request = self.helpers.buildHttpMessage(headers, body) + self.messageInfo.setRequest(new_request) + self._debug('Headers removed: {}'.format(', '.join(header_names))) def replace_bearer_token(self, new_token): """Replaces a Bearer token in the current request.""" - if self.messageIsRequest: - request = self.helpers.analyzeRequest(self.messageInfo) - headers = request.getHeaders() - headers = self.remove_header(headers, 'Authorization') - headers.add('Authorization: Bearer {}'.format(new_token)) - body = self.messageInfo.getRequest()[request.getBodyOffset():] - new_request = self.helpers.buildHttpMessage(headers, body) - self.messageInfo.setRequest(new_request) - self._debug('Token replaced.') - - def enable_passive_audit_checks(self): - """Runs passive check methods against in-scope proxy traffic. - - Additional checks are added by creating new methods and prefixing the - name with `_passive_request_` or `_passive_response_`. The second - chunk of the method name determines what is analyzed by the method, - and the method must receive it as an argument. - """ + if not self._in_context(context='request'): return + + request = self.helpers.analyzeRequest(self.messageInfo) + headers = request.getHeaders() + headers = self._remove_header(headers, 'Authorization') + headers.add('Authorization: Bearer {}'.format(new_token)) + body = self.messageInfo.getRequest()[request.getBodyOffset():] + new_request = self.helpers.buildHttpMessage(headers, body) + self.messageInfo.setRequest(new_request) + self._debug('Token replaced.') + + def passive_autocomplete_text(self): + """Checks for autocomplete on text fields in the current response.""" - if self.toolFlag == self.callbacks.TOOL_PROXY and self._in_scope(): - self._debug('Passive checks enabled.') - methods =[x for x in dir(self.__class__) if x.startswith('_passive_')] - for method in methods: - mode = method.split('_')[2] - if mode == 'request' and self.messageIsRequest: - request = self.messageInfo.getRequest() - getattr(self, method)(request) - elif mode == 'response' and not self.messageIsRequest: - response = self.messageInfo.getResponse() - getattr(self, method)(response) - - def _passive_response_autocomplete_enabled(self, response): - """Checks for autocomplete on text form fields in a response.""" + if not self._in_context(context='response', + tools=[self.callbacks.TOOL_PROXY], + scope=True): return import re + response = self.messageInfo.getResponse() results = re.findall(r'(]*>)', response) for result in results: if re.search(r'''type=['"]text['"]''', result) and not re.search(r'autocomplete', result): - self.create_issue( + self._create_issue( issue_name='Text field with autocomplete enabled', issue_detail='The following text field has autocomplete enabled:\n\n