From 9648bc44ccdb7f3938e184b89d93b338a3936ba4 Mon Sep 17 00:00:00 2001 From: Rob Glew Date: Mon, 21 Mar 2016 13:57:52 -0500 Subject: [PATCH] Version 0.2.9 --- README.md | 2 + pappyproxy/__init__.py | 2 +- pappyproxy/comm.py | 42 +++++---- pappyproxy/context.py | 2 +- pappyproxy/http.py | 82 ++++++++++------- pappyproxy/plugin.py | 7 +- pappyproxy/plugins/decode.py | 25 +++-- pappyproxy/plugins/macrocmds.py | 2 +- pappyproxy/plugins/misc.py | 3 + pappyproxy/proxy.py | 137 ++++++++++++++-------------- pappyproxy/site/static/dickbutt.jpg | Bin 0 -> 48024 bytes pappyproxy/tests/test_comm.py | 24 +++-- pappyproxy/util.py | 16 ++++ setup.py | 2 +- 14 files changed, 202 insertions(+), 144 deletions(-) create mode 100644 pappyproxy/site/static/dickbutt.jpg diff --git a/README.md b/README.md index 4c09c16..53f0933 100644 --- a/README.md +++ b/README.md @@ -1123,6 +1123,8 @@ Changelog --------- The boring part of the readme +* 0.2.9 + * Fix bugs/clean up some code * 0.2.8 * Upstream HTTP proxy support * Usability improvements diff --git a/pappyproxy/__init__.py b/pappyproxy/__init__.py index 14e974f..cd9b137 100644 --- a/pappyproxy/__init__.py +++ b/pappyproxy/__init__.py @@ -1 +1 @@ -__version__ = '0.2.8' +__version__ = '0.2.9' diff --git a/pappyproxy/comm.py b/pappyproxy/comm.py index c8be914..499ce76 100644 --- a/pappyproxy/comm.py +++ b/pappyproxy/comm.py @@ -31,21 +31,21 @@ class CommServer(LineReceiver): if line == '': return - try: - command_data = json.loads(line) - command = command_data['action'] - valid = False - if command in self.action_handlers: - valid = True - result = {'success': True} - func_defer = self.action_handlers[command](command_data) - func_defer.addCallback(self.action_result_handler, result) - func_defer.addErrback(self.action_error_handler, result) - if not valid: - raise PappyException('%s is an invalid command' % command_data['action']) - except PappyException as e: - return_data = {'success': False, 'message': str(e)} - self.sendLine(json.dumps(return_data)) + #try: + command_data = json.loads(line) + command = command_data['action'] + valid = False + if command in self.action_handlers: + valid = True + result = {'success': True} + func_defer = self.action_handlers[command](command_data) + func_defer.addCallback(self.action_result_handler, result) + func_defer.addErrback(self.action_error_handler, result) + if not valid: + raise PappyException('%s is an invalid command' % command_data['action']) + # except PappyException as e: + # return_data = {'success': False, 'message': str(e)} + # self.sendLine(json.dumps(return_data)) def action_result_handler(self, data, result): result.update(data) @@ -94,11 +94,15 @@ class CommServer(LineReceiver): @defer.inlineCallbacks def action_submit_request(self, data): from .http import Request + from .plugin import active_intercepting_macros message = base64.b64decode(data['full_message']) - try: - req = yield Request.submit_new(data['host'].encode('utf-8'), data['port'], data['is_ssl'], message) - except Exception: - raise PappyException('Error submitting request. Please make sure request is a valid HTTP message.') + req = Request(message) + req.host = data['host'].encode('utf-8') + req.port = data['port'] + req.is_ssl = data['is_ssl'] + yield Request.submit_request(req, + save_request=True, + intercepting_macros=active_intercepting_macros()) if 'tags' in data: req.tags = set(data['tags']) yield req.async_deep_save() diff --git a/pappyproxy/context.py b/pappyproxy/context.py index 8ba020d..c26eaf2 100644 --- a/pappyproxy/context.py +++ b/pappyproxy/context.py @@ -375,7 +375,7 @@ def gen_filter_by_before(args): defer.returnValue(f) @defer.inlineCallbacks -def gen_filter_by_after(reqid, negate=False): +def gen_filter_by_after(args, negate=False): if len(args) != 1: raise PappyException('Invalid number of arguments') r = yield Request.load_request(args[0]) diff --git a/pappyproxy/http.py b/pappyproxy/http.py index 23b31bf..703ef2f 100644 --- a/pappyproxy/http.py +++ b/pappyproxy/http.py @@ -2130,60 +2130,74 @@ class Request(HTTPMessage): @staticmethod @defer.inlineCallbacks - def submit_new(host, port, is_ssl, full_request): + def submit_request(request, + save_request=False, + intercepting_macros={}, + stream_transport=None): """ - submit_new(host, port, is_ssl, full_request) - Submits a request with the given parameters and returns a request object - with the response. + submit_request(request, save_request=False, intercepting_macros={}, stream_transport=None) - :param host: The host to submit to - :type host: string - :param port: The port to submit to - :type port: Integer - :type is_ssl: Whether to use SSL - :param full_request: The request data to send - :type full_request: string - :rtype: Twisted deferred that calls back with a Request + Submits the request then sets ``request.response``. Returns a deferred that + is called with the request that was submitted. + + :param request: The request to submit + :type host: Request + :param save_request: Whether to save the request to history + :type save_request: Bool + :param intercepting_macros: Dictionary of intercepting macros to be applied to the request + :type intercepting_macros: Dict or collections.OrderedDict + :param stream_transport: Return transport to stream to. Set to None to not stream the response. + :type stream_transport: twisted.internet.interfaces.ITransport """ + from .proxy import ProxyClientFactory, get_next_connection_id, get_endpoint from .pappy import session - new_req = Request(full_request) - new_req.is_ssl = is_ssl - new_req.port = port - new_req._host = host - - factory = ProxyClientFactory(new_req, save_all=False, stream_response=False, return_transport=None) - factory.intercepting_macros = {} + factory = None + if stream_transport is None: + factory = ProxyClientFactory(request, + save_all=save_request, + stream_response=False, + return_transport=None) + else: + factory = ProxyClientFactory(request, + save_all=save_request, + stream_response=True, + return_transport=stream_transport) + factory.intercepting_macros = intercepting_macros factory.connection_id = get_next_connection_id() - yield factory.prepare_request() - endpoint = get_endpoint(host, port, is_ssl, - socks_config=session.config.socks_proxy) - yield endpoint.connect(factory) + factory.connect() new_req = yield factory.data_defer - defer.returnValue(new_req) + request.response = new_req.response + defer.returnValue(request) @defer.inlineCallbacks - def async_submit(self): + def async_submit(self, mangle=False): """ async_submit() Same as :func:`~pappyproxy.http.Request.submit` but generates deferreds. Submits the request using its host, port, etc. and updates its response value to the resulting response. + :param mangle: Whether to pass the request through active intercepting macros. + :type mangle: Bool + :rtype: Twisted deferred """ - new_req = yield Request.submit_new(self.host, self.port, self.is_ssl, - self.full_request) - self.set_metadata(new_req.get_metadata()) - self.unmangled = new_req.unmangled - self.response = new_req.response - self.time_start = new_req.time_start - self.time_end = new_req.time_end + from pappyproxy.plugin import active_intercepting_macros + + if mangle: + int_macros = active_intercepting_macros() + else: + int_macros = None + yield Request.submit_request(self, + save_request=False, + intercepting_macros=int_macros, + stream_transport=None) @crochet.wait_for(timeout=180.0) @defer.inlineCallbacks - def submit(self): + def submit(self, mangle=False): """ submit() Submits the request using its host, port, etc. and updates its response value @@ -2191,7 +2205,7 @@ class Request(HTTPMessage): Cannot be called in async functions. This is what you should use to submit your requests in macros. """ - yield self.async_submit() + yield self.async_submit(mangle=mangle) class Response(HTTPMessage): diff --git a/pappyproxy/plugin.py b/pappyproxy/plugin.py index 0bbdb1e..4325d35 100644 --- a/pappyproxy/plugin.py +++ b/pappyproxy/plugin.py @@ -107,12 +107,13 @@ def remove_intercepting_macro(name): def active_intercepting_macros(): """ - Returns a list of the active intercepting macro objects. Modifying + Returns a dict of the active intercepting macro objects. Modifying this list will not affect which macros are active. """ - ret = [] + ret = {} for factory in pappyproxy.pappy.session.server_factories: - ret += [v for k, v in factory.intercepting_macros.iteritems() ] + for k, v in factory.intercepting_macros.iteritems(): + ret[k] = v return ret def in_memory_reqs(): diff --git a/pappyproxy/plugins/decode.py b/pappyproxy/plugins/decode.py index 9072d61..5479126 100644 --- a/pappyproxy/plugins/decode.py +++ b/pappyproxy/plugins/decode.py @@ -1,14 +1,13 @@ import HTMLParser import StringIO import base64 -import clipboard import datetime import gzip import shlex import string import urllib -from pappyproxy.util import PappyException, hexdump, printable_data +from pappyproxy.util import PappyException, hexdump, printable_data, copy_to_clipboard, clipboard_contents def print_maybe_bin(s): binary = False @@ -45,6 +44,18 @@ def gzip_decode_helper(s): dec_data = dec_data.read() return dec_data +def base64_decode_helper(s): + try: + return base64.b64decode(s) + except TypeError: + for i in range(1, 5): + try: + s_padded = base64.b64decode(s + '='*i) + return s_padded + except: + pass + raise PappyException("Unable to base64 decode string") + def html_encode_helper(s): return ''.join(['&#x{0:x};'.format(ord(c)) for c in s]) @@ -54,13 +65,13 @@ def html_decode_helper(s): def _code_helper(line, func, copy=True): args = shlex.split(line) if not args: - s = clipboard.paste() + s = clipboard_contents() print 'Will decode:' print printable_data(s) s = func(s) if copy: try: - clipboard.copy(s) + copy_to_clipboard(s) except: print 'Result cannot be copied to the clipboard. Result not copied.' return s @@ -68,7 +79,7 @@ def _code_helper(line, func, copy=True): s = func(args[0].strip()) if copy: try: - clipboard.copy(s) + copy_to_clipboard(s) except: print 'Result cannot be copied to the clipboard. Result not copied.' return s @@ -79,7 +90,7 @@ def base64_decode(line): If no string is given, will decode the contents of the clipboard. Results are copied to the clipboard. """ - print_maybe_bin(_code_helper(line, base64.b64decode)) + print_maybe_bin(_code_helper(line, base64_decode_helper)) def base64_encode(line): """ @@ -159,7 +170,7 @@ def base64_decode_raw(line): results will not be copied. It is suggested you redirect the output to a file. """ - print _code_helper(line, base64.b64decode, copy=False) + print _code_helper(line, base64_decode_helper, copy=False) def base64_encode_raw(line): """ diff --git a/pappyproxy/plugins/macrocmds.py b/pappyproxy/plugins/macrocmds.py index 0f0eda4..427ad3d 100644 --- a/pappyproxy/plugins/macrocmds.py +++ b/pappyproxy/plugins/macrocmds.py @@ -127,7 +127,7 @@ def list_int_macros(line): running = [] not_running = [] for macro in loaded_int_macros: - if macro.name in [m.name for m in active_intercepting_macros()]: + if macro.name in [m.name for k, m in active_intercepting_macros().iteritems()]: running.append(macro) else: not_running.append(macro) diff --git a/pappyproxy/plugins/misc.py b/pappyproxy/plugins/misc.py index 5f0bc32..87934de 100644 --- a/pappyproxy/plugins/misc.py +++ b/pappyproxy/plugins/misc.py @@ -1,6 +1,7 @@ import crochet import pappyproxy import shlex +import sys from pappyproxy.colors import Colors, Styles, path_formatter, host_color, scode_color, verb_color from pappyproxy.util import PappyException, remove_color, confirm, load_reqlist, Capturing @@ -34,6 +35,7 @@ class PrintStreamInterceptMacro(InterceptMacro): s += req.url_color s += ', len=' + str(len(req.body)) print s + sys.stdout.flush() @staticmethod def _print_response(req): @@ -47,6 +49,7 @@ class PrintStreamInterceptMacro(InterceptMacro): s += req.url_color s += ', len=' + str(len(req.response.body)) print s + sys.stdout.flush() def mangle_request(self, request): PrintStreamInterceptMacro._print_request(request) diff --git a/pappyproxy/proxy.py b/pappyproxy/proxy.py index ffa749d..1e86ce7 100644 --- a/pappyproxy/proxy.py +++ b/pappyproxy/proxy.py @@ -62,11 +62,20 @@ def log_request(request, id=None, symbol='*', verbosity_level=3): for l in r_split: log(l, id, symbol, verbosity_level) -def get_endpoint(target_host, target_port, target_ssl, socks_config=None): +def get_endpoint(target_host, target_port, target_ssl, socks_config=None, use_http_proxy=False, debugid=None): # Imports go here to allow mocking for tests from twisted.internet.endpoints import SSL4ClientEndpoint, TCP4ClientEndpoint from txsocksx.client import SOCKS5ClientEndpoint from txsocksx.tls import TLSWrapClientEndpoint + from pappyproxy.pappy import session + + log("Getting endpoint for host '%s' on port %d ssl=%s, socks_config=%s, use_http_proxy=%s" % (target_host, target_port, target_ssl, str(socks_config), use_http_proxy), id=debugid, verbosity_level=3) + + if session.config.http_proxy and use_http_proxy: + target_host = session.config.http_proxy['host'] + target_port = session.config.http_proxy['port'] + target_ssl = False # We turn on ssl after CONNECT request if needed + log("Connecting to http proxy at %s:%d" % (target_host, target_port), id=debugid, verbosity_level=3) if socks_config is not None: sock_host = socks_config['host'] @@ -183,7 +192,7 @@ class UpstreamHTTPProxyClient(ProxyClient): sendreq.proxy_creds = self.creds lines = sendreq.full_request.splitlines() for l in lines: - self.log(l, symbol='>r', verbosity_level=3) + self.log(l, symbol='>rp', verbosity_level=3) self.transport.write(sendreq.full_message) def connectionMade(self): @@ -194,10 +203,16 @@ class UpstreamHTTPProxyClient(ProxyClient): self.connect_response = True if self.creds is not None: connreq.proxy_creds = self.creds + lines = connreq.full_message.splitlines() + for l in lines: + self.log(l, symbol='>p', verbosity_level=3) self.transport.write(connreq.full_message) else: self.proxy_connected = True self.stream_response = True + lines = self.request.full_message.splitlines() + for l in lines: + self.log(l, symbol='>p', verbosity_level=3) self.write_proxied_request(self.request) def handle_response_end(self, *args, **kwargs): @@ -211,6 +226,10 @@ class UpstreamHTTPProxyClient(ProxyClient): self.transport.loseConnection() assert self._response_obj.full_response self.data_defer.callback(self.request) + elif self._response_obj.response_code != 200: + print "Error establishing connection to proxy" + self.transport.loseConnection() + return elif self.connect_response: self.log("Response to CONNECT request recieved from http proxy", verbosity_level=3) self.proxy_connected = True @@ -220,10 +239,12 @@ class UpstreamHTTPProxyClient(ProxyClient): self.completed = False self._sent = False + self.log("Starting TLS", verbosity_level=3) self.transport.startTLS(ClientTLSContext()) + self.log("TLS started", verbosity_level=3) lines = self.request.full_message.splitlines() for l in lines: - self.log(l, symbol='>r', verbosity_level=3) + self.log(l, symbol='>rpr', verbosity_level=3) self.transport.write(self.request.full_message) class ProxyClientFactory(ClientFactory): @@ -240,6 +261,7 @@ class ProxyClientFactory(ClientFactory): self.return_transport = return_transport self.intercepting_macros = {} self.use_as_proxy = False + self.sendback_function = None def log(self, message, symbol='*', verbosity_level=1): log(message, id=self.connection_id, symbol=symbol, verbosity_level=verbosity_level) @@ -297,6 +319,8 @@ class ProxyClientFactory(ClientFactory): if session.config.http_proxy: self.use_as_proxy = True + if (not self.stream_response) and self.sendback_function: + self.data_defer.addCallback(self.sendback_function) else: self.log("Request out of scope, passing along unmangled") self.request = sendreq @@ -340,6 +364,29 @@ class ProxyClientFactory(ClientFactory): self.data_defer.callback(request) defer.returnValue(None) + @defer.inlineCallbacks + def connect(self): + from pappyproxy.pappy import session + + yield self.prepare_request() + if context.in_scope(self.request): + # Get connection using config + endpoint = get_endpoint(self.request.host, + self.request.port, + self.request.is_ssl, + socks_config=session.config.socks_proxy, + use_http_proxy=True) + else: + # Just forward it normally + endpoint = get_endpoint(self.request.host, + self.request.port, + self.request.is_ssl) + + # Connect via the endpoint + self.log("Accessing using endpoint") + yield endpoint.connect(self) + self.log("Connected") + class ProxyServerFactory(ServerFactory): def __init__(self, save_all=False): @@ -425,6 +472,8 @@ class ProxyServer(LineReceiver): @defer.inlineCallbacks def full_request_received(self): + from pappyproxy.http import Request + global cached_certs self.log('End of request', verbosity_level=3) @@ -447,8 +496,18 @@ class ProxyServer(LineReceiver): # if _request_obj.host is a listener, forward = False + if self.factory.intercepting_macros: + return_transport = None + else: + return_transport = self.transport + if forward: - self._generate_and_submit_client() + d = Request.submit_request(self._request_obj, + save_request=True, + intercepting_macros=self.factory.intercepting_macros, + stream_transport=return_transport) + if return_transport is None: + d.addCallback(self.send_response_back) self._reset() def _reset(self): @@ -467,73 +526,9 @@ class ProxyServer(LineReceiver): self._request_obj.port = self._connect_port self.setLineMode() - def _generate_and_submit_client(self): - """ - Sets up self._client_factory with self._request_obj then calls back to - submit the request - """ - - self.log("Forwarding to %s on %d" % (self._request_obj.host, self._request_obj.port)) - if self.factory.intercepting_macros: - stream = False - else: - stream = True - self.log('Creating client factory, stream=%s' % stream) - self._client_factory = ProxyClientFactory(self._request_obj, - save_all=self.factory.save_all, - stream_response=stream, - return_transport=self.transport) - self._client_factory.intercepting_macros = self.factory.intercepting_macros - self._client_factory.connection_id = self.connection_id - if not stream: - self._client_factory.data_defer.addCallback(self.send_response_back) - d = self._client_factory.prepare_request() - d.addCallback(self._make_remote_connection) - return d - - @defer.inlineCallbacks - def _make_remote_connection(self, req): - """ - Creates an endpoint to the target server using the given configuration - options then connects to the endpoint using self._client_factory - """ - from pappyproxy.pappy import session - - self._request_obj = req - - # If we have a socks proxy, wrap the endpoint in it - if context.in_scope(self._request_obj): - # Modify the request connection settings to match settings in the factory - if self.factory.force_ssl: - self._request_obj.is_ssl = True - if self.factory.forward_host: - self._request_obj.host = self.factory.forward_host - - usehost = self._request_obj.host - useport = self._request_obj.port - usessl = self._request_obj.is_ssl - if session.config.http_proxy: - usehost = session.config.http_proxy['host'] - useport = session.config.http_proxy['port'] - usessl = False # We turn on ssl after CONNECT request if needed - self.log("Connecting to http proxy at %s:%d" % (usehost, useport)) - - # Get connection from the request - endpoint = get_endpoint(usehost, useport, usessl, - socks_config=session.config.socks_proxy) - else: - endpoint = get_endpoint(self._request_obj.host, - self._request_obj.port, - self._request_obj.is_ssl) - - # Connect via the endpoint - self.log("Accessing using endpoint") - yield endpoint.connect(self._client_factory) - self.log("Connected") - - def send_response_back(self, response): - if response is not None: - self.transport.write(response.response.full_response) + def send_response_back(self, request): + if request.response is not None: + self.transport.write(request.response.full_response) self.log("Response sent back, losing connection") self.transport.loseConnection() diff --git a/pappyproxy/site/static/dickbutt.jpg b/pappyproxy/site/static/dickbutt.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3a3d983bf2bc9bad0d649e94d23cb66176543637 GIT binary patch literal 48024 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 V^xuB#|JVP&H}L=c1`xj|{~zO7=Dq*` literal 0 HcmV?d00001 diff --git a/pappyproxy/tests/test_comm.py b/pappyproxy/tests/test_comm.py index 3a046d8..c217358 100644 --- a/pappyproxy/tests/test_comm.py +++ b/pappyproxy/tests/test_comm.py @@ -10,6 +10,10 @@ from pappyproxy.comm import CommServer from pappyproxy.http import Request, Response from testutil import mock_deferred, func_deleted, TLSStringTransport, freeze, mock_int_macro, no_tcp +@pytest.fixture(autouse=True) +def no_int_macros(mocker): + mocker.patch('pappyproxy.plugin.active_intercepting_macros').return_value = {} + @pytest.fixture def http_request(): req = Request('GET / HTTP/1.1\r\n\r\n') @@ -42,6 +46,13 @@ def mock_loader(rsp): return rsp return classmethod(f) +def mock_submitter(rsp): + def f(_, req, *args, **kwargs): + req.response = rsp + req.reqid = 123 + return mock_deferred(req) + return classmethod(f) + def mock_loader_fail(): def f(*args, **kwargs): raise PappyException("lololo message don't exist dawg") @@ -80,7 +91,8 @@ def test_get_response_fail(mocker, http_request): assert 'message' in v def test_submit_request(mocker, http_request): - mocker.patch.object(pappyproxy.http.Request, 'submit_new', new=mock_loader(http_request)) + rsp = Response('HTTP/1.1 200 OK\r\n\r\n') + mocker.patch.object(pappyproxy.http.Request, 'submit_request', new=mock_submitter(rsp)) mocker.patch('pappyproxy.http.Request.async_deep_save').return_value = mock_deferred() comm_data = {"action": "submit"} @@ -92,14 +104,14 @@ def test_submit_request(mocker, http_request): v = perform_comm(json.dumps(comm_data)) expected_data = {} - expected_data['request'] = json.loads(http_request.to_json()) - expected_data['response'] = json.loads(http_request.response.to_json()) - expected_data['success'] = True - expected_data['request']['tags'] = ['footag'] + expected_data[u'request'] = json.loads(http_request.to_json()) + expected_data[u'response'] = json.loads(http_request.response.to_json()) + expected_data[u'success'] = True + expected_data[u'request'][u'tags'] = [u'footag'] assert json.loads(v) == expected_data def test_submit_request_fail(mocker, http_request): - mocker.patch.object(pappyproxy.http.Request, 'submit_new', new=mock_loader_fail()) + mocker.patch.object(pappyproxy.http.Request, 'submit_request', new=mock_loader_fail()) mocker.patch('pappyproxy.http.Request.async_deep_save').return_value = mock_deferred() comm_data = {"action": "submit"} diff --git a/pappyproxy/util.py b/pappyproxy/util.py index 8819ee9..74532ea 100644 --- a/pappyproxy/util.py +++ b/pappyproxy/util.py @@ -4,11 +4,20 @@ import re import string import sys import time +import pyperclip from .colors import Styles, Colors, verb_color, scode_color, path_formatter, host_color from twisted.internet import defer from twisted.test.proto_helpers import StringTransport +try: + # If you don't do this then pyperclip imports gtk, it blocks the twisted reactor. + # Dumb. I know. + import gtk + gtk.set_interactive(False) +except ImportError: + pass + class PappyException(Exception): """ The exception class for Pappy. If a plugin command raises one of these, the @@ -320,3 +329,10 @@ def confirm(message, default='n'): return True else: return False + + +def copy_to_clipboard(text): + pyperclip.copy(text) + +def clipboard_contents(): + return pyperclip.paste() diff --git a/setup.py b/setup.py index 56a3642..6fc998c 100755 --- a/setup.py +++ b/setup.py @@ -23,11 +23,11 @@ setup(name='pappyproxy', download_url='https://github.com/roglew/pappy-proxy/archive/%s.tar.gz'%VERSION, install_requires=[ 'beautifulsoup4>=4.4.1', - 'clipboard>=0.0.4', 'cmd2>=0.6.8', 'crochet>=1.4.0', 'Jinja2>=2.8', 'pygments>=2.0.2', + 'pyperclip>=1.5.26', 'pytest-cov>=2.2.0', 'pytest-mock>=0.9.0', 'pytest-twisted>=1.5',