aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/src/content/addons-scripting.md17
-rw-r--r--docs/src/content/howto-transparent.md6
-rw-r--r--examples/addons/scripting-headers.py (renamed from examples/addons/scripting.py)0
-rw-r--r--examples/complex/dns_spoofing.py6
-rw-r--r--mitmproxy/tools/console/commander/commander.py49
-rw-r--r--mitmproxy/tools/console/defaultkeys.py2
-rw-r--r--mitmproxy/tools/console/options.py2
-rw-r--r--mitmproxy/tools/console/statusbar.py7
-rw-r--r--mitmproxy/tools/web/app.py67
-rw-r--r--test/mitmproxy/tools/console/test_commander.py62
10 files changed, 181 insertions, 37 deletions
diff --git a/docs/src/content/addons-scripting.md b/docs/src/content/addons-scripting.md
index e31d291a..4e9916ca 100644
--- a/docs/src/content/addons-scripting.md
+++ b/docs/src/content/addons-scripting.md
@@ -14,4 +14,19 @@ handler functions in the module scope. For instance, here is a complete script
that adds a header to every request.
-{{< example src="examples/addons/scripting.py" lang="py" >}} \ No newline at end of file
+{{< example src="examples/addons/scripting-headers.py" lang="py" >}}
+
+
+Here's another example that intercepts requests to a particular URL and sends
+an arbitrary response instead:
+
+{{< example src="examples/simple/send_reply_from_proxy.py" lang="py" >}}
+
+
+You can look at the [http][] module, or the [Request][], and
+[Response][] classes for other attributes that you can use when
+scripting.
+
+[http][]: https://github.com/mitmproxy/mitmproxy/blob/master/mitmproxy/http.py
+[Request]: https://github.com/mitmproxy/mitmproxy/blob/master/mitmproxy/net/http/request.py
+[Response]: https://github.com/mitmproxy/mitmproxy/blob/master/mitmproxy/net/http/response.py
diff --git a/docs/src/content/howto-transparent.md b/docs/src/content/howto-transparent.md
index 07a21ec9..ae36f579 100644
--- a/docs/src/content/howto-transparent.md
+++ b/docs/src/content/howto-transparent.md
@@ -163,14 +163,14 @@ for earlier versions of OSX.
sudo sysctl -w net.inet.ip.forwarding=1
{{< / highlight >}}
-### 2. Place the following two lines in a file called, say, **pf.conf**.
+### 2. Place the following line in a file called, say, **pf.conf**.
{{< highlight none >}}
-rdr on en0 inet proto tcp to any port {80, 443} -> 127.0.0.1 port 8080
+rdr pass on en0 inet proto tcp to any port {80, 443} -> 127.0.0.1 port 8080
{{< / highlight >}}
-These rules tell pf to redirect all traffic destined for port 80 or 443
+This rule tells pf to redirect all traffic destined for port 80 or 443
to the local mitmproxy instance running on port 8080. You should replace
`en0` with the interface on which your test device will appear.
diff --git a/examples/addons/scripting.py b/examples/addons/scripting-headers.py
index 8b23680e..8b23680e 100644
--- a/examples/addons/scripting.py
+++ b/examples/addons/scripting-headers.py
diff --git a/examples/complex/dns_spoofing.py b/examples/complex/dns_spoofing.py
index e28934ab..a3c1a017 100644
--- a/examples/complex/dns_spoofing.py
+++ b/examples/complex/dns_spoofing.py
@@ -13,12 +13,12 @@ Usage:
-p 443
-s dns_spoofing.py
# Used as the target location if neither SNI nor host header are present.
- -R http://example.com/
+ --mode reverse:http://example.com/
# To avoid auto rewriting of host header by the reverse proxy target.
- --keep-host-header
+ --set keep-host-header
mitmdump
-p 80
- -R http://localhost:443/
+ --mode reverse:http://localhost:443/
(Setting up a single proxy instance and using iptables to redirect to it
works as well)
diff --git a/mitmproxy/tools/console/commander/commander.py b/mitmproxy/tools/console/commander/commander.py
index df3eaa5a..e8550f86 100644
--- a/mitmproxy/tools/console/commander/commander.py
+++ b/mitmproxy/tools/console/commander/commander.py
@@ -1,5 +1,7 @@
import abc
+import copy
import typing
+import collections
import urwid
from urwid.text_layout import calc_coords
@@ -156,13 +158,53 @@ class CommandBuffer:
self.completion = None
+class CommandHistory:
+ def __init__(self, master: mitmproxy.master.Master, size: int=30) -> None:
+ self.saved_commands: collections.deque = collections.deque(
+ [CommandBuffer(master, "")],
+ maxlen=size
+ )
+ self.index: int = 0
+
+ @property
+ def last_index(self):
+ return len(self.saved_commands) - 1
+
+ def get_next(self) -> typing.Optional[CommandBuffer]:
+ if self.index < self.last_index:
+ self.index = self.index + 1
+ return self.saved_commands[self.index]
+ return None
+
+ def get_prev(self) -> typing.Optional[CommandBuffer]:
+ if self.index > 0:
+ self.index = self.index - 1
+ return self.saved_commands[self.index]
+ return None
+
+ def add_command(self, command: CommandBuffer, execution: bool=False) -> None:
+ if self.index == self.last_index or execution:
+ last_item = self.saved_commands[-1]
+ last_item_empty = not last_item.text
+ if last_item.text == command.text or (last_item_empty and execution):
+ self.saved_commands[-1] = copy.copy(command)
+ else:
+ self.saved_commands.append(command)
+ if not execution and self.index < self.last_index:
+ self.index += 1
+ if execution:
+ self.index = self.last_index
+
+
class CommandEdit(urwid.WidgetWrap):
leader = ": "
- def __init__(self, master: mitmproxy.master.Master, text: str) -> None:
+ def __init__(self, master: mitmproxy.master.Master,
+ text: str, history: CommandHistory) -> None:
super().__init__(urwid.Text(self.leader))
self.master = master
self.cbuf = CommandBuffer(master, text)
+ self.history = history
self.update()
def keypress(self, size, key):
@@ -172,6 +214,11 @@ class CommandEdit(urwid.WidgetWrap):
self.cbuf.left()
elif key == "right":
self.cbuf.right()
+ elif key == "up":
+ self.history.add_command(self.cbuf)
+ self.cbuf = self.history.get_prev() or self.cbuf
+ elif key == "down":
+ self.cbuf = self.history.get_next() or self.cbuf
elif key == "tab":
self.cbuf.cycle_completion()
elif len(key) == 1:
diff --git a/mitmproxy/tools/console/defaultkeys.py b/mitmproxy/tools/console/defaultkeys.py
index 0f2e9072..0a6c5561 100644
--- a/mitmproxy/tools/console/defaultkeys.py
+++ b/mitmproxy/tools/console/defaultkeys.py
@@ -60,7 +60,7 @@ def map(km):
km.add("M", "view.properties.marked.toggle", ["flowlist"], "Toggle viewing marked flows")
km.add(
"n",
- "console.command view.create get https://example.com/",
+ "console.command view.flows.create get https://example.com/",
["flowlist"],
"Create a new flow"
)
diff --git a/mitmproxy/tools/console/options.py b/mitmproxy/tools/console/options.py
index 6e1399ce..5e5ef2a6 100644
--- a/mitmproxy/tools/console/options.py
+++ b/mitmproxy/tools/console/options.py
@@ -174,7 +174,7 @@ class OptionsList(urwid.ListBox):
foc, idx = self.get_focus()
v = self.walker.get_edit_text()
try:
- d = self.master.options.parse_setval(foc.opt.name, v)
+ d = self.master.options.parse_setval(foc.opt, v)
self.master.options.update(**{foc.opt.name: d})
except exceptions.OptionsError as v:
signals.status_message.send(message=str(v))
diff --git a/mitmproxy/tools/console/statusbar.py b/mitmproxy/tools/console/statusbar.py
index 215cf500..2d32f487 100644
--- a/mitmproxy/tools/console/statusbar.py
+++ b/mitmproxy/tools/console/statusbar.py
@@ -42,6 +42,8 @@ class ActionBar(urwid.WidgetWrap):
signals.status_prompt_onekey.connect(self.sig_prompt_onekey)
signals.status_prompt_command.connect(self.sig_prompt_command)
+ self.command_history = commander.CommandHistory(master)
+
self.prompting = None
self.onekey = False
@@ -98,7 +100,8 @@ class ActionBar(urwid.WidgetWrap):
def sig_prompt_command(self, sender, partial=""):
signals.focus.send(self, section="footer")
- self._w = commander.CommandEdit(self.master, partial)
+ self._w = commander.CommandEdit(self.master, partial,
+ self.command_history)
self.prompting = commandexecutor.CommandExecutor(self.master)
def sig_prompt_onekey(self, sender, prompt, keys, callback, args=()):
@@ -125,6 +128,7 @@ class ActionBar(urwid.WidgetWrap):
def keypress(self, size, k):
if self.prompting:
if k == "esc":
+ self.command_history.index = self.command_history.last_index
self.prompt_done()
elif self.onekey:
if k == "enter":
@@ -132,6 +136,7 @@ class ActionBar(urwid.WidgetWrap):
elif k in self.onekey:
self.prompt_execute(k)
elif k == "enter":
+ self.command_history.add_command(self._w.cbuf, True)
self.prompt_execute(self._w.get_edit_text())
else:
if common.is_keypress(k):
diff --git a/mitmproxy/tools/web/app.py b/mitmproxy/tools/web/app.py
index ae2394eb..b72e0d77 100644
--- a/mitmproxy/tools/web/app.py
+++ b/mitmproxy/tools/web/app.py
@@ -463,34 +463,20 @@ class SaveOptions(RequestHandler):
pass
+class DnsRebind(RequestHandler):
+ def get(self):
+ raise tornado.web.HTTPError(
+ 403,
+ reason="To protect against DNS rebinding, mitmweb can only be accessed by IP at the moment. "
+ "(https://github.com/mitmproxy/mitmproxy/issues/3234)"
+ )
+
+
class Application(tornado.web.Application):
def __init__(self, master, debug):
self.master = master
- handlers = [
- (r"/", IndexHandler),
- (r"/filter-help(?:\.json)?", FilterHelp),
- (r"/updates", ClientConnection),
- (r"/events(?:\.json)?", Events),
- (r"/flows(?:\.json)?", Flows),
- (r"/flows/dump", DumpFlows),
- (r"/flows/resume", ResumeFlows),
- (r"/flows/kill", KillFlows),
- (r"/flows/(?P<flow_id>[0-9a-f\-]+)", FlowHandler),
- (r"/flows/(?P<flow_id>[0-9a-f\-]+)/resume", ResumeFlow),
- (r"/flows/(?P<flow_id>[0-9a-f\-]+)/kill", KillFlow),
- (r"/flows/(?P<flow_id>[0-9a-f\-]+)/duplicate", DuplicateFlow),
- (r"/flows/(?P<flow_id>[0-9a-f\-]+)/replay", ReplayFlow),
- (r"/flows/(?P<flow_id>[0-9a-f\-]+)/revert", RevertFlow),
- (r"/flows/(?P<flow_id>[0-9a-f\-]+)/(?P<message>request|response)/content.data", FlowContent),
- (
- r"/flows/(?P<flow_id>[0-9a-f\-]+)/(?P<message>request|response)/content/(?P<content_view>[0-9a-zA-Z\-\_]+)(?:\.json)?",
- FlowContentView),
- (r"/settings(?:\.json)?", Settings),
- (r"/clear", ClearAll),
- (r"/options(?:\.json)?", Options),
- (r"/options/save", SaveOptions)
- ]
- settings = dict(
+ super().__init__(
+ default_host="dns-rebind-protection",
template_path=os.path.join(os.path.dirname(__file__), "templates"),
static_path=os.path.join(os.path.dirname(__file__), "static"),
xsrf_cookies=True,
@@ -498,4 +484,33 @@ class Application(tornado.web.Application):
debug=debug,
autoreload=False,
)
- super().__init__(handlers, **settings)
+
+ self.add_handlers("dns-rebind-protection", [(r"/.*", DnsRebind)])
+ self.add_handlers(
+ # make mitmweb accessible by IP only to prevent DNS rebinding.
+ r'^(localhost|[0-9.:\[\]]+)$',
+ [
+ (r"/", IndexHandler),
+ (r"/filter-help(?:\.json)?", FilterHelp),
+ (r"/updates", ClientConnection),
+ (r"/events(?:\.json)?", Events),
+ (r"/flows(?:\.json)?", Flows),
+ (r"/flows/dump", DumpFlows),
+ (r"/flows/resume", ResumeFlows),
+ (r"/flows/kill", KillFlows),
+ (r"/flows/(?P<flow_id>[0-9a-f\-]+)", FlowHandler),
+ (r"/flows/(?P<flow_id>[0-9a-f\-]+)/resume", ResumeFlow),
+ (r"/flows/(?P<flow_id>[0-9a-f\-]+)/kill", KillFlow),
+ (r"/flows/(?P<flow_id>[0-9a-f\-]+)/duplicate", DuplicateFlow),
+ (r"/flows/(?P<flow_id>[0-9a-f\-]+)/replay", ReplayFlow),
+ (r"/flows/(?P<flow_id>[0-9a-f\-]+)/revert", RevertFlow),
+ (r"/flows/(?P<flow_id>[0-9a-f\-]+)/(?P<message>request|response)/content.data", FlowContent),
+ (
+ r"/flows/(?P<flow_id>[0-9a-f\-]+)/(?P<message>request|response)/content/(?P<content_view>[0-9a-zA-Z\-\_]+)(?:\.json)?",
+ FlowContentView),
+ (r"/settings(?:\.json)?", Settings),
+ (r"/clear", ClearAll),
+ (r"/options(?:\.json)?", Options),
+ (r"/options/save", SaveOptions)
+ ]
+ )
diff --git a/test/mitmproxy/tools/console/test_commander.py b/test/mitmproxy/tools/console/test_commander.py
index 2a96995d..b5e226fe 100644
--- a/test/mitmproxy/tools/console/test_commander.py
+++ b/test/mitmproxy/tools/console/test_commander.py
@@ -28,6 +28,68 @@ class TestListCompleter:
assert c.cycle() == expected
+class TestCommandHistory:
+ def fill_history(self, commands):
+ with taddons.context() as tctx:
+ history = commander.CommandHistory(tctx.master, size=3)
+ for c in commands:
+ cbuf = commander.CommandBuffer(tctx.master, c)
+ history.add_command(cbuf)
+ return history, tctx.master
+
+ def test_add_command(self):
+ commands = ["command1", "command2"]
+ history, tctx_master = self.fill_history(commands)
+
+ saved_commands = [buf.text for buf in history.saved_commands]
+ assert saved_commands == [""] + commands
+
+ # The history size is only 3. So, we forget the first
+ # one command, when adding fourth command
+ cbuf = commander.CommandBuffer(tctx_master, "command3")
+ history.add_command(cbuf)
+ saved_commands = [buf.text for buf in history.saved_commands]
+ assert saved_commands == commands + ["command3"]
+
+ # Commands with the same text are not repeated in the history one by one
+ history.add_command(cbuf)
+ saved_commands = [buf.text for buf in history.saved_commands]
+ assert saved_commands == commands + ["command3"]
+
+ # adding command in execution mode sets index at the beginning of the history
+ # and replace the last command buffer if it is empty or has the same text
+ cbuf = commander.CommandBuffer(tctx_master, "")
+ history.add_command(cbuf)
+ history.index = 0
+ cbuf = commander.CommandBuffer(tctx_master, "command4")
+ history.add_command(cbuf, True)
+ assert history.index == history.last_index
+ saved_commands = [buf.text for buf in history.saved_commands]
+ assert saved_commands == ["command2", "command3", "command4"]
+
+ def test_get_next(self):
+ commands = ["command1", "command2"]
+ history, tctx_master = self.fill_history(commands)
+
+ history.index = -1
+ expected_items = ["", "command1", "command2"]
+ for i in range(3):
+ assert history.get_next().text == expected_items[i]
+ # We are at the last item of the history
+ assert history.get_next() is None
+
+ def test_get_prev(self):
+ commands = ["command1", "command2"]
+ history, tctx_master = self.fill_history(commands)
+
+ expected_items = ["command2", "command1", ""]
+ history.index = history.last_index + 1
+ for i in range(3):
+ assert history.get_prev().text == expected_items[i]
+ # We are at the first item of the history
+ assert history.get_prev() is None
+
+
class TestCommandBuffer:
def test_backspace(self):