diff options
Diffstat (limited to 'examples')
-rw-r--r-- | examples/addons/addheader.py | 13 | ||||
-rw-r--r-- | examples/addons/anatomy.py | 15 | ||||
-rw-r--r-- | examples/addons/commands-flows.py | 21 | ||||
-rw-r--r-- | examples/addons/commands-paths.py | 32 | ||||
-rw-r--r-- | examples/addons/commands-simple.py | 17 | ||||
-rw-r--r-- | examples/addons/events.py | 179 | ||||
-rw-r--r-- | examples/addons/options-configure.py | 28 | ||||
-rw-r--r-- | examples/addons/options-simple.py | 24 | ||||
-rw-r--r-- | examples/addons/scripting.py | 3 | ||||
-rw-r--r-- | examples/complex/README.md | 2 | ||||
-rw-r--r-- | examples/complex/stream_modify.py | 2 | ||||
-rwxr-xr-x | examples/complex/xss_scanner.py | 8 | ||||
-rw-r--r-- | examples/simple/custom_contentview.py | 4 | ||||
-rw-r--r-- | examples/simple/custom_option.py | 10 | ||||
-rw-r--r-- | examples/simple/internet_in_mirror.py | 9 | ||||
-rw-r--r-- | examples/simple/log_events.py | 4 | ||||
-rw-r--r-- | examples/simple/upsidedownternet.py | 16 |
17 files changed, 358 insertions, 29 deletions
diff --git a/examples/addons/addheader.py b/examples/addons/addheader.py new file mode 100644 index 00000000..f4b29268 --- /dev/null +++ b/examples/addons/addheader.py @@ -0,0 +1,13 @@ + +class AddHeader: + def __init__(self): + self.num = 0 + + def response(self, flow): + self.num = self.num + 1 + flow.response.headers["count"] = str(self.num) + + +addons = [ + AddHeader() +] diff --git a/examples/addons/anatomy.py b/examples/addons/anatomy.py new file mode 100644 index 00000000..c60afeaa --- /dev/null +++ b/examples/addons/anatomy.py @@ -0,0 +1,15 @@ +from mitmproxy import ctx + + +class Counter: + def __init__(self): + self.num = 0 + + def request(self, flow): + self.num = self.num + 1 + ctx.log.info("We've seen %d flows" % self.num) + + +addons = [ + Counter() +] diff --git a/examples/addons/commands-flows.py b/examples/addons/commands-flows.py new file mode 100644 index 00000000..cebc8f9d --- /dev/null +++ b/examples/addons/commands-flows.py @@ -0,0 +1,21 @@ +import typing + +from mitmproxy import command +from mitmproxy import ctx +from mitmproxy import flow + + +class MyAddon: + def __init__(self): + self.num = 0 + + @command.command("myaddon.addheader") + def addheader(self, flows: typing.Sequence[flow.Flow]) -> None: + for f in flows: + f.request.headers["myheader"] = "value" + ctx.log.alert("done") + + +addons = [ + MyAddon() +] diff --git a/examples/addons/commands-paths.py b/examples/addons/commands-paths.py new file mode 100644 index 00000000..f37a0fbc --- /dev/null +++ b/examples/addons/commands-paths.py @@ -0,0 +1,32 @@ +import typing + +from mitmproxy import command +from mitmproxy import ctx +from mitmproxy import flow +from mitmproxy import types + + +class MyAddon: + def __init__(self): + self.num = 0 + + @command.command("myaddon.histogram") + def histogram( + self, + flows: typing.Sequence[flow.Flow], + path: types.Path, + ) -> None: + totals = {} + for f in flows: + totals[f.request.host] = totals.setdefault(f.request.host, 0) + 1 + + fp = open(path, "w+") + for cnt, dom in sorted([(v, k) for (k, v) in totals.items()]): + fp.write("%s: %s\n" % (cnt, dom)) + + ctx.log.alert("done") + + +addons = [ + MyAddon() +] diff --git a/examples/addons/commands-simple.py b/examples/addons/commands-simple.py new file mode 100644 index 00000000..c9cd6341 --- /dev/null +++ b/examples/addons/commands-simple.py @@ -0,0 +1,17 @@ +from mitmproxy import command +from mitmproxy import ctx + + +class MyAddon: + def __init__(self): + self.num = 0 + + @command.command("myaddon.inc") + def inc(self) -> None: + self.num += 1 + ctx.log.info("num = %s" % self.num) + + +addons = [ + MyAddon() +] diff --git a/examples/addons/events.py b/examples/addons/events.py new file mode 100644 index 00000000..d3c90430 --- /dev/null +++ b/examples/addons/events.py @@ -0,0 +1,179 @@ +import typing + +import mitmproxy.addonmanager +import mitmproxy.connections +import mitmproxy.http +import mitmproxy.log +import mitmproxy.tcp +import mitmproxy.websocket +import mitmproxy.proxy.protocol + + +class Events: + # HTTP lifecycle + def http_connect(self, flow: mitmproxy.http.HTTPFlow): + """ + An HTTP CONNECT request was received. Setting a non 2xx response on + the flow will return the response to the client abort the + connection. CONNECT requests and responses do not generate the usual + HTTP handler events. CONNECT requests are only valid in regular and + upstream proxy modes. + """ + + def requestheaders(self, flow: mitmproxy.http.HTTPFlow): + """ + HTTP request headers were successfully read. At this point, the body + is empty. + """ + + def request(self, flow: mitmproxy.http.HTTPFlow): + """ + The full HTTP request has been read. + """ + + def responseheaders(self, flow: mitmproxy.http.HTTPFlow): + """ + HTTP response headers were successfully read. At this point, the body + is empty. + """ + + def response(self, flow: mitmproxy.http.HTTPFlow): + """ + The full HTTP response has been read. + """ + + def error(self, flow: mitmproxy.http.HTTPFlow): + """ + An HTTP error has occurred, e.g. invalid server responses, or + interrupted connections. This is distinct from a valid server HTTP + error response, which is simply a response with an HTTP error code. + """ + + # TCP lifecycle + def tcp_start(self, flow: mitmproxy.tcp.TCPFlow): + """ + A TCP connection has started. + """ + + def tcp_message(self, flow: mitmproxy.tcp.TCPFlow): + """ + A TCP connection has received a message. The most recent message + will be flow.messages[-1]. The message is user-modifiable. + """ + + def tcp_error(self, flow: mitmproxy.tcp.TCPFlow): + """ + A TCP error has occurred. + """ + + def tcp_end(self, flow: mitmproxy.tcp.TCPFlow): + """ + A TCP connection has ended. + """ + + # Websocket lifecycle + def websocket_handshake(self, flow: mitmproxy.http.HTTPFlow): + """ + Called when a client wants to establish a WebSocket connection. The + WebSocket-specific headers can be manipulated to alter the + handshake. The flow object is guaranteed to have a non-None request + attribute. + """ + + def websocket_start(self, flow: mitmproxy.websocket.WebsocketFlow): + """ + A websocket connection has commenced. + """ + + def websocket_message(self, flow: mitmproxy.websocket.WebsocketFlow): + """ + Called when a WebSocket message is received from the client or + server. The most recent message will be flow.messages[-1]. The + message is user-modifiable. Currently there are two types of + messages, corresponding to the BINARY and TEXT frame types. + """ + + def websocket_error(self, flow: mitmproxy.websocket.WebsocketFlow): + """ + A websocket connection has had an error. + """ + + def websocket_end(self, flow: mitmproxy.websocket.WebsocketFlow): + """ + A websocket connection has ended. + """ + + # Network lifecycle + def clientconnect(self, layer: mitmproxy.proxy.protocol.Layer): + """ + A client has connected to mitmproxy. Note that a connection can + correspond to multiple HTTP requests. + """ + + def clientdisconnect(self, layer: mitmproxy.proxy.protocol.Layer): + """ + A client has disconnected from mitmproxy. + """ + + def serverconnect(self, conn: mitmproxy.connections.ServerConnection): + """ + Mitmproxy has connected to a server. Note that a connection can + correspond to multiple requests. + """ + + def serverdisconnect(self, conn: mitmproxy.connections.ServerConnection): + """ + Mitmproxy has disconnected from a server. + """ + + def next_layer(self, layer: mitmproxy.proxy.protocol.Layer): + """ + Network layers are being switched. You may change which layer will + be used by returning a new layer object from this event. + """ + + # General lifecycle + def configure(self, updated: typing.Set[str]): + """ + Called when configuration changes. The updated argument is a + set-like object containing the keys of all changed options. This + event is called during startup with all options in the updated set. + """ + + def done(self): + """ + Called when the addon shuts down, either by being removed from the + mitmproxy instance, or when mitmproxy itself shuts down. + """ + + def load(self, entry: mitmproxy.addonmanager.Loader): + """ + Called when an addon is first loaded. This event receives a Loader + object, which contains methods for adding options and commands. This + method is where the addon configures itself. + """ + + def log(self, entry: mitmproxy.log.LogEntry): + """ + Called whenever a new log entry is created through the mitmproxy + context. Be careful not to log from this event, which will cause an + infinite loop! + """ + + def running(self): + """ + Called when the proxy is completely up and running. At this point, + you can expect the proxy to be bound to a port, and all addons to be + loaded. + """ + + def tick(self): + """ + A regular ticker - called approximately once every 100ms. + """ + + def update(self, flows: typing.Sequence[mitmproxy.flow.Flow]): + """ + Update is called when one or more flow objects have been modified, + usually from a different addon. + """ diff --git a/examples/addons/options-configure.py b/examples/addons/options-configure.py new file mode 100644 index 00000000..c7638e87 --- /dev/null +++ b/examples/addons/options-configure.py @@ -0,0 +1,28 @@ +import typing + +from mitmproxy import ctx +from mitmproxy import exceptions + + +class AddHeader: + def load(self, loader): + loader.add_option( + name = "addheader", + typespec = typing.Optional[int], + default = None, + help = "Add a header to responses", + ) + + def configure(self, updates): + if "addheader" in updates: + if ctx.options.addheader is not None and ctx.options.addheader > 100: + raise exceptions.OptionsError("addheader must be <= 100") + + def response(self, flow): + if ctx.options.addheader is not None: + flow.response.headers["addheader"] = str(ctx.options.addheader) + + +addons = [ + AddHeader() +] diff --git a/examples/addons/options-simple.py b/examples/addons/options-simple.py new file mode 100644 index 00000000..0acefb3f --- /dev/null +++ b/examples/addons/options-simple.py @@ -0,0 +1,24 @@ +from mitmproxy import ctx + + +class AddHeader: + def __init__(self): + self.num = 0 + + def load(self, loader): + loader.add_option( + name = "addheader", + typespec = bool, + default = False, + help = "Add a count header to responses", + ) + + def response(self, flow): + if ctx.options.addheader: + self.num = self.num + 1 + flow.response.headers["count"] = str(self.num) + + +addons = [ + AddHeader() +] diff --git a/examples/addons/scripting.py b/examples/addons/scripting.py new file mode 100644 index 00000000..8b23680e --- /dev/null +++ b/examples/addons/scripting.py @@ -0,0 +1,3 @@ + +def request(flow): + flow.request.headers["myheader"] = "value" diff --git a/examples/complex/README.md b/examples/complex/README.md index 77dbe2f5..c53503e4 100644 --- a/examples/complex/README.md +++ b/examples/complex/README.md @@ -10,7 +10,7 @@ | mitmproxywrapper.py | Bracket mitmproxy run with proxy enable/disable on OS X | | nonblocking.py | Demonstrate parallel processing with a blocking script | | remote_debug.py | This script enables remote debugging of the mitmproxy _UI_ with PyCharm. | -| sslstrip.py | sslstrip-like funtionality implemented with mitmproxy | +| sslstrip.py | sslstrip-like functionality implemented with mitmproxy | | stream.py | Enable streaming for all responses. | | stream_modify.py | Modify a streamed response body. | | tcp_message.py | Modify a raw TCP connection | diff --git a/examples/complex/stream_modify.py b/examples/complex/stream_modify.py index 5e5da95b..46bdcb78 100644 --- a/examples/complex/stream_modify.py +++ b/examples/complex/stream_modify.py @@ -3,7 +3,7 @@ This inline script modifies a streamed response. If you do not need streaming, see the modify_response_body example. Be aware that content replacement isn't trivial: - If the transfer encoding isn't chunked, you cannot simply change the content length. - - If you want to replace all occurences of "foobar", make sure to catch the cases + - If you want to replace all occurrences of "foobar", make sure to catch the cases where one chunk ends with [...]foo" and the next starts with "bar[...]. """ diff --git a/examples/complex/xss_scanner.py b/examples/complex/xss_scanner.py index 0ee38cd4..0c0dd0f3 100755 --- a/examples/complex/xss_scanner.py +++ b/examples/complex/xss_scanner.py @@ -215,7 +215,7 @@ def get_SQLi_data(new_body: str, original_body: str, request_URL: str, injection # A qc is either ' or " def inside_quote(qc: str, substring_bytes: bytes, text_index: int, body_bytes: bytes) -> bool: - """ Whether the Numberth occurence of the first string in the second + """ Whether the Numberth occurrence of the first string in the second string is inside quotes as defined by the supplied QuoteChar """ substring = substring_bytes.decode('utf-8') body = body_bytes.decode('utf-8') @@ -246,7 +246,7 @@ def paths_to_text(html: str, string: str) -> List[str]: - Note that it does a BFS """ def remove_last_occurence_of_sub_string(string: str, substr: str) -> str: - """ Delete the last occurence of substr from str + """ Delete the last occurrence of substr from str String String -> String """ index = string.rfind(substr) @@ -274,7 +274,7 @@ def paths_to_text(html: str, string: str) -> List[str]: def get_XSS_data(body: Union[str, bytes], request_URL: str, injection_point: str) -> Optional[XSSData]: """ Return a XSSDict if there is a XSS otherwise return None """ def in_script(text, index, body) -> bool: - """ Whether the Numberth occurence of the first string in the second + """ Whether the Numberth occurrence of the first string in the second string is inside a script tag """ paths = paths_to_text(body.decode('utf-8'), text.decode("utf-8")) try: @@ -284,7 +284,7 @@ def get_XSS_data(body: Union[str, bytes], request_URL: str, injection_point: str return False def in_HTML(text: bytes, index: int, body: bytes) -> bool: - """ Whether the Numberth occurence of the first string in the second + """ Whether the Numberth occurrence of the first string in the second string is inside the HTML but not inside a script tag or part of a HTML attribute""" # if there is a < then lxml will interpret that as a tag, so only search for the stuff before it diff --git a/examples/simple/custom_contentview.py b/examples/simple/custom_contentview.py index b958bdce..77d32474 100644 --- a/examples/simple/custom_contentview.py +++ b/examples/simple/custom_contentview.py @@ -7,10 +7,6 @@ from mitmproxy import contentviews class ViewSwapCase(contentviews.View): name = "swapcase" - - # We don't have a good solution for the keyboard shortcut yet - - # you manually need to find a free letter. Contributions welcome :) - prompt = ("swap case text", "z") content_types = ["text/plain"] def __call__(self, data, **metadata) -> contentviews.TViewResult: diff --git a/examples/simple/custom_option.py b/examples/simple/custom_option.py index 5b6070dd..8d0cfe7f 100644 --- a/examples/simple/custom_option.py +++ b/examples/simple/custom_option.py @@ -1,3 +1,13 @@ +""" +This example shows how addons can register custom options +that can be configured at startup or during execution +from the options dialog within mitmproxy. + +Example: + +$ mitmproxy --set custom=true +$ mitmproxy --set custom # shorthand for boolean options +""" from mitmproxy import ctx diff --git a/examples/simple/internet_in_mirror.py b/examples/simple/internet_in_mirror.py new file mode 100644 index 00000000..5d3e555d --- /dev/null +++ b/examples/simple/internet_in_mirror.py @@ -0,0 +1,9 @@ +""" +This script reflects all content passing through the proxy. +""" +from mitmproxy import http + + +def response(flow: http.HTTPFlow) -> None: + reflector = b"<style>body {transform: scaleX(-1);}</style></head>" + flow.response.content = flow.response.content.replace(b"</head>", reflector) diff --git a/examples/simple/log_events.py b/examples/simple/log_events.py index 581b99f3..b9aa2c1f 100644 --- a/examples/simple/log_events.py +++ b/examples/simple/log_events.py @@ -1,8 +1,6 @@ """ It is recommended to use `ctx.log` for logging within a script. -This goes to the event log in mitmproxy and to stdout in mitmdump. - -If you want to help us out: https://github.com/mitmproxy/mitmproxy/issues/1530 :-) +print() statements are equivalent to ctx.log.warn(). """ from mitmproxy import ctx diff --git a/examples/simple/upsidedownternet.py b/examples/simple/upsidedownternet.py deleted file mode 100644 index f150a5c3..00000000 --- a/examples/simple/upsidedownternet.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -This script rotates all images passing through the proxy by 180 degrees. -""" -import io -from PIL import Image -from mitmproxy import http - - -def response(flow: http.HTTPFlow) -> None: - if flow.response.headers.get("content-type", "").startswith("image"): - s = io.BytesIO(flow.response.content) - img = Image.open(s).rotate(180) - s2 = io.BytesIO() - img.save(s2, "png") - flow.response.content = s2.getvalue() - flow.response.headers["content-type"] = "image/png" |