Merge branch 'master' into master

This commit is contained in:
Sandor Nemes 2016-01-17 21:24:47 +01:00
commit 192f033967
8 changed files with 141 additions and 26 deletions

View File

@ -36,14 +36,13 @@ We encourage you to either browse them locally or on `GitHub`_.
Events Events
------ ------
.. TODO: Split this into Connection, HTTP and TCP events once we have TCP events.
The ``context`` argument passed to each event method is always a The ``context`` argument passed to each event method is always a
:py:class:`~libmproxy.script.ScriptContext` instance. It is guaranteed to be the same object :py:class:`~libmproxy.script.ScriptContext` instance. It is guaranteed to be the same object
for the scripts lifetime and is not shared between multiple inline scripts. You can safely use it for the scripts lifetime and is not shared between multiple inline scripts. You can safely use it
to store any form of state you require. to store any form of state you require.
Events are listed in the order they usually occur. Script Lifecycle Events
^^^^^^^^^^^^^^^^^^^^^^^
.. py:function:: start(context, argv) .. py:function:: start(context, argv)
@ -52,6 +51,13 @@ Events are listed in the order they usually occur.
:param List[str] argv: The inline scripts' arguments. :param List[str] argv: The inline scripts' arguments.
For example, ``mitmproxy -s 'example.py --foo 42'`` sets argv to ``["--foo", "42"]``. For example, ``mitmproxy -s 'example.py --foo 42'`` sets argv to ``["--foo", "42"]``.
.. py:function:: done(context)
Called once on script shutdown, after any other events.
Connection Events
^^^^^^^^^^^^^^^^^
.. py:function:: clientconnect(context, root_layer) .. py:function:: clientconnect(context, root_layer)
Called when a client initiates a connection to the proxy. Note that Called when a client initiates a connection to the proxy. Note that
@ -64,14 +70,13 @@ Events are listed in the order they usually occur.
:py:class:`~libmproxy.proxy.RootContext`. For example, ``root_layer.client_conn.address`` :py:class:`~libmproxy.proxy.RootContext`. For example, ``root_layer.client_conn.address``
gives the remote address of the connecting client. gives the remote address of the connecting client.
.. py:function:: clientdisconnect(context, root_layer)
.. py:function:: request(context, flow) Called when a client disconnects from the proxy.
Called when a client request has been received. The ``flow`` object is .. versionchanged:: 0.14
guaranteed to have a non-None ``request`` attribute.
:param HTTPFlow flow: The flow containing the request which has been received. :param Layer root_layer: see :py:func:`clientconnect`
The object is guaranteed to have a non-None ``request`` attribute.
.. py:function:: serverconnect(context, server_conn) .. py:function:: serverconnect(context, server_conn)
@ -81,6 +86,25 @@ Events are listed in the order they usually occur.
:param ServerConnection server_conn: The server connection object. It is guaranteed to have a :param ServerConnection server_conn: The server connection object. It is guaranteed to have a
non-None ``address`` attribute. non-None ``address`` attribute.
.. py:function:: serverdisconnect(context, server_conn)
Called when the proxy has closed the server connection.
.. versionadded:: 0.14
:param ServerConnection server_conn: see :py:func:`serverconnect`
HTTP Events
^^^^^^^^^^^
.. py:function:: request(context, flow)
Called when a client request has been received. The ``flow`` object is
guaranteed to have a non-None ``request`` attribute.
:param HTTPFlow flow: The flow containing the request which has been received.
The object is guaranteed to have a non-None ``request`` attribute.
.. py:function:: responseheaders(context, flow) .. py:function:: responseheaders(context, flow)
Called when the headers of a server response have been received. Called when the headers of a server response have been received.
@ -109,26 +133,19 @@ Events are listed in the order they usually occur.
:param HTTPFlow flow: The flow containing the error. :param HTTPFlow flow: The flow containing the error.
It is guaranteed to have non-None ``error`` attribute. It is guaranteed to have non-None ``error`` attribute.
.. py:function:: serverdisconnect(context, server_conn) TCP Events
^^^^^^^^^^
Called when the proxy has closed the server connection. .. py:function:: tcp_message(context, tcp_msg)
.. versionadded:: 0.14 .. warning:: API is subject to change
:param ServerConnection server_conn: see :py:func:`serverconnect` If the proxy is in :ref:`TCP mode <tcpproxy>`, this event is called when it
receives a TCP payload from the client or server.
.. py:function:: clientdisconnect(context, root_layer) The sender and receiver are identifiable. The message is user-modifiable.
Called when a client disconnects from the proxy.
.. versionchanged:: 0.14
:param Layer root_layer: see :py:func:`clientconnect`
.. py:function:: done(context)
Called once on script shutdown, after any other events.
:param TcpMessage tcp_msg: see *examples/tcp_message.py*
API API
--- ---

40
examples/sslstrip.py Normal file
View File

@ -0,0 +1,40 @@
from netlib.http import decoded
import re
from six.moves import urllib
def start(context, argv) :
#set of SSL/TLS capable hosts
context.secure_hosts = set()
def request(context, flow) :
flow.request.headers.pop('If-Modified-Since', None)
flow.request.headers.pop('Cache-Control', None)
#proxy connections to SSL-enabled hosts
if flow.request.pretty_host in context.secure_hosts :
flow.request.scheme = 'https'
flow.request.port = 443
def response(context, flow) :
with decoded(flow.response) :
flow.request.headers.pop('Strict-Transport-Security', None)
flow.request.headers.pop('Public-Key-Pins', None)
#strip links in response body
flow.response.content = flow.response.content.replace('https://', 'http://')
#strip links in 'Location' header
if flow.response.headers.get('Location','').startswith('https://'):
location = flow.response.headers['Location']
hostname = urllib.parse.urlparse(location).hostname
if hostname:
context.secure_hosts.add(hostname)
flow.response.headers['Location'] = location.replace('https://', 'http://', 1)
#strip secure flag from 'Set-Cookie' headers
cookies = flow.response.headers.get_all('Set-Cookie')
cookies = [re.sub(r';\s*secure\s*', '', s) for s in cookies]
flow.response.headers.set_all('Set-Cookie', cookies)

24
examples/tcp_message.py Normal file
View File

@ -0,0 +1,24 @@
'''
tcp_message Inline Script Hook API Demonstration
------------------------------------------------
* modifies packets containing "foo" to "bar"
* prints various details for each packet.
example cmdline invocation:
mitmdump -T --host --tcp ".*" -q -s examples/tcp_message.py
'''
from netlib.utils import clean_bin
def tcp_message(ctx, tcp_msg):
modified_msg = tcp_msg.message.replace("foo", "bar")
is_modified = False if modified_msg == tcp_msg.message else True
tcp_msg.message = modified_msg
print("[tcp_message{}] from {} {} to {} {}:\r\n{}".format(
" (modified)" if is_modified else "",
"client" if tcp_msg.sender == tcp_msg.client_conn else "server",
tcp_msg.sender.address,
"server" if tcp_msg.receiver == tcp_msg.server_conn else "client",
tcp_msg.receiver.address, clean_bin(tcp_msg.message)))

View File

@ -1050,6 +1050,10 @@ class FlowMaster(controller.Master):
self.add_event('"{}" reloaded.'.format(s.filename)) self.add_event('"{}" reloaded.'.format(s.filename))
return ok return ok
def handle_tcp_message(self, m):
self.run_script_hook("tcp_message", m)
m.reply()
def shutdown(self): def shutdown(self):
self.unload_scripts() self.unload_scripts()
controller.Master.shutdown(self) controller.Master.shutdown(self)

View File

@ -13,6 +13,15 @@ from ..exceptions import ProtocolException
from .base import Layer from .base import Layer
class TcpMessage(object):
def __init__(self, client_conn, server_conn, sender, receiver, message):
self.client_conn = client_conn
self.server_conn = server_conn
self.sender = sender
self.receiver = receiver
self.message = message
class RawTCPLayer(Layer): class RawTCPLayer(Layer):
chunk_size = 4096 chunk_size = 4096
@ -50,7 +59,13 @@ class RawTCPLayer(Layer):
return return
continue continue
dst.sendall(buf[:size]) tcp_message = TcpMessage(
self.client_conn, self.server_conn,
self.client_conn if dst == server else self.server_conn,
self.server_conn if dst == server else self.client_conn,
buf[:size].tobytes())
self.channel.ask("tcp_message", tcp_message)
dst.sendall(tcp_message.message)
if self.logging: if self.logging:
# log messages are prepended with the client address, # log messages are prepended with the client address,
@ -59,7 +74,7 @@ class RawTCPLayer(Layer):
direction = "-> tcp -> {}".format(repr(self.server_conn.address)) direction = "-> tcp -> {}".format(repr(self.server_conn.address))
else: else:
direction = "<- tcp <- {}".format(repr(self.server_conn.address)) direction = "<- tcp <- {}".format(repr(self.server_conn.address))
data = clean_bin(buf[:size].tobytes()) data = clean_bin(tcp_message.message)
self.log( self.log(
"{}\r\n{}".format(direction, data), "{}\r\n{}".format(direction, data),
"info" "info"

View File

@ -26,7 +26,7 @@ deps = {
"construct>=2.5.2, <2.6", "construct>=2.5.2, <2.6",
"six>=1.10.0, <1.11", "six>=1.10.0, <1.11",
"lxml==3.4.4", # there are no Windows wheels for newer versions, so we pin this. "lxml==3.4.4", # there are no Windows wheels for newer versions, so we pin this.
"Pillow>=3.0.0, <3.1", "Pillow>=3.0.0, <3.2",
"watchdog>=0.8.3, <0.9", "watchdog>=0.8.3, <0.9",
} }
# A script -> additional dependencies dict. # A script -> additional dependencies dict.

View File

@ -0,0 +1,3 @@
def tcp_message(ctx,tm):
if tm.sender == tm.server_conn:
tm.message = tm.message.replace("foo", "bar")

View File

@ -502,6 +502,18 @@ class TestHttps2Http(tservers.ReverseProxTest):
class TestTransparent(tservers.TransparentProxTest, CommonMixin, TcpMixin): class TestTransparent(tservers.TransparentProxTest, CommonMixin, TcpMixin):
ssl = False ssl = False
def test_tcp_stream_modify(self):
self.master.load_script(
tutils.test_data.path("scripts/tcp_stream_modify.py"))
self._tcpproxy_on()
d = self.pathod('200:b"foo"')
self._tcpproxy_off()
assert d.content == "bar"
self.master.unload_scripts()
class TestTransparentSSL(tservers.TransparentProxTest, CommonMixin, TcpMixin): class TestTransparentSSL(tservers.TransparentProxTest, CommonMixin, TcpMixin):
ssl = True ssl = True