mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-26 10:16:27 +00:00
Merge pull request #2120 from ujjwal96/replace-from-file
Replace from file through replacement editor #1458
This commit is contained in:
commit
961747c4bc
@ -48,14 +48,14 @@ In practice, it's pretty common for the replacement literal to be long and
|
|||||||
complex. For instance, it might be an XSS exploit that weighs in at hundreds or
|
complex. For instance, it might be an XSS exploit that weighs in at hundreds or
|
||||||
thousands of characters. To cope with this, there's a variation of the
|
thousands of characters. To cope with this, there's a variation of the
|
||||||
replacement hook specifier that lets you load the replacement text from a file.
|
replacement hook specifier that lets you load the replacement text from a file.
|
||||||
So, you might start **mitmdump** as follows:
|
To specify a file as replacement, prefix the file path with ``@``.
|
||||||
|
You might start **mitmdump** as follows:
|
||||||
|
|
||||||
>>> mitmdump --replace-from-file :~q:foo:~/xss-exploit
|
>>> mitmdump --replacements :~q:foo:@~/xss-exploit
|
||||||
|
|
||||||
This will load the replacement text from the file ``~/xss-exploit``.
|
This will load the replacement text from the file ``~/xss-exploit``.
|
||||||
|
|
||||||
Both the ``--replace`` and ``--replace-from-file`` flags can be passed multiple
|
The ``--replacements`` flag can be passed multiple times.
|
||||||
times.
|
|
||||||
|
|
||||||
|
|
||||||
Interactively
|
Interactively
|
||||||
@ -66,7 +66,6 @@ replacement hooks using a built-in editor. The context-sensitive help (:kbd:`?`)
|
|||||||
complete usage information.
|
complete usage information.
|
||||||
|
|
||||||
================== =======================
|
================== =======================
|
||||||
command-line ``--replace``,
|
command-line ``--replacements``
|
||||||
``--replace-from-file``
|
|
||||||
mitmproxy shortcut :kbd:`O` then :kbd:`R`
|
mitmproxy shortcut :kbd:`O` then :kbd:`R`
|
||||||
================== =======================
|
================== =======================
|
||||||
|
@ -30,7 +30,6 @@ def default_addons():
|
|||||||
onboarding.Onboarding(),
|
onboarding.Onboarding(),
|
||||||
proxyauth.ProxyAuth(),
|
proxyauth.ProxyAuth(),
|
||||||
replace.Replace(),
|
replace.Replace(),
|
||||||
replace.ReplaceFile(),
|
|
||||||
script.ScriptLoader(),
|
script.ScriptLoader(),
|
||||||
serverplayback.ServerPlayback(),
|
serverplayback.ServerPlayback(),
|
||||||
setheaders.SetHeaders(),
|
setheaders.SetHeaders(),
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from mitmproxy import exceptions
|
from mitmproxy import exceptions
|
||||||
@ -42,7 +43,7 @@ def parse_hook(s):
|
|||||||
return patt, a, b
|
return patt, a, b
|
||||||
|
|
||||||
|
|
||||||
class _ReplaceBase:
|
class Replace:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.lst = []
|
self.lst = []
|
||||||
|
|
||||||
@ -51,12 +52,12 @@ class _ReplaceBase:
|
|||||||
.replacements is a list of tuples (fpat, rex, s):
|
.replacements is a list of tuples (fpat, rex, s):
|
||||||
|
|
||||||
fpatt: a string specifying a filter pattern.
|
fpatt: a string specifying a filter pattern.
|
||||||
rex: a regular expression, as bytes.
|
rex: a regular expression, as string.
|
||||||
s: the replacement string, as bytes
|
s: the replacement string
|
||||||
"""
|
"""
|
||||||
if self.optionName in updated:
|
if "replacements" in updated:
|
||||||
lst = []
|
lst = []
|
||||||
for rep in getattr(options, self.optionName):
|
for rep in options.replacements:
|
||||||
fpatt, rex, s = parse_hook(rep)
|
fpatt, rex, s = parse_hook(rep)
|
||||||
|
|
||||||
flt = flowfilter.parse(fpatt)
|
flt = flowfilter.parse(fpatt)
|
||||||
@ -65,11 +66,16 @@ class _ReplaceBase:
|
|||||||
"Invalid filter pattern: %s" % fpatt
|
"Invalid filter pattern: %s" % fpatt
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
|
# We should ideally escape here before trying to compile
|
||||||
re.compile(rex)
|
re.compile(rex)
|
||||||
except re.error as e:
|
except re.error as e:
|
||||||
raise exceptions.OptionsError(
|
raise exceptions.OptionsError(
|
||||||
"Invalid regular expression: %s - %s" % (rex, str(e))
|
"Invalid regular expression: %s - %s" % (rex, str(e))
|
||||||
)
|
)
|
||||||
|
if s.startswith("@") and not os.path.isfile(s[1:]):
|
||||||
|
raise exceptions.OptionsError(
|
||||||
|
"Invalid file path: {}".format(s[1:])
|
||||||
|
)
|
||||||
lst.append((rex, s, flt))
|
lst.append((rex, s, flt))
|
||||||
self.lst = lst
|
self.lst = lst
|
||||||
|
|
||||||
@ -89,21 +95,13 @@ class _ReplaceBase:
|
|||||||
if not flow.reply.has_message:
|
if not flow.reply.has_message:
|
||||||
self.execute(flow)
|
self.execute(flow)
|
||||||
|
|
||||||
|
|
||||||
class Replace(_ReplaceBase):
|
|
||||||
optionName = "replacements"
|
|
||||||
|
|
||||||
def replace(self, obj, rex, s):
|
def replace(self, obj, rex, s):
|
||||||
|
if s.startswith("@"):
|
||||||
|
s = os.path.expanduser(s[1:])
|
||||||
|
try:
|
||||||
|
with open(s, "rb") as f:
|
||||||
|
s = f.read()
|
||||||
|
except IOError:
|
||||||
|
ctx.log.warn("Could not read replacement file: %s" % s)
|
||||||
|
return
|
||||||
obj.replace(rex, s, flags=re.DOTALL)
|
obj.replace(rex, s, flags=re.DOTALL)
|
||||||
|
|
||||||
|
|
||||||
class ReplaceFile(_ReplaceBase):
|
|
||||||
optionName = "replacement_files"
|
|
||||||
|
|
||||||
def replace(self, obj, rex, s):
|
|
||||||
try:
|
|
||||||
v = open(s, "rb").read()
|
|
||||||
except IOError as e:
|
|
||||||
ctx.log.warn("Could not read replacement file: %s" % s)
|
|
||||||
return
|
|
||||||
obj.replace(rex, v, flags=re.DOTALL)
|
|
||||||
|
@ -120,13 +120,6 @@ class Options(optmanager.OptManager):
|
|||||||
the separator can be any character.
|
the separator can be any character.
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
self.add_option(
|
|
||||||
"replacement_files", Sequence[str], [],
|
|
||||||
"""
|
|
||||||
Replacement pattern, where the replacement clause is a path to a
|
|
||||||
file.
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
self.add_option(
|
self.add_option(
|
||||||
"server_replay_use_headers", Sequence[str], [],
|
"server_replay_use_headers", Sequence[str], [],
|
||||||
"Request headers to be considered during replay."
|
"Request headers to be considered during replay."
|
||||||
|
@ -93,7 +93,6 @@ def common_options(parser, opts):
|
|||||||
# Replacements
|
# Replacements
|
||||||
group = parser.add_argument_group("Replacements")
|
group = parser.add_argument_group("Replacements")
|
||||||
opts.make_parser(group, "replacements", metavar="PATTERN", short="R")
|
opts.make_parser(group, "replacements", metavar="PATTERN", short="R")
|
||||||
opts.make_parser(group, "replacement_files", metavar="PATTERN")
|
|
||||||
|
|
||||||
# Set headers
|
# Set headers
|
||||||
group = parser.add_argument_group("Set Headers")
|
group = parser.add_argument_group("Set Headers")
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import urwid
|
import urwid
|
||||||
|
|
||||||
from mitmproxy import exceptions
|
from mitmproxy import exceptions
|
||||||
from mitmproxy import flowfilter
|
from mitmproxy import flowfilter
|
||||||
from mitmproxy.addons import script
|
from mitmproxy.addons import script
|
||||||
@ -87,6 +90,9 @@ class ReplaceEditor(base.GridEditor):
|
|||||||
re.compile(val)
|
re.compile(val)
|
||||||
except re.error:
|
except re.error:
|
||||||
return "Invalid regular expression."
|
return "Invalid regular expression."
|
||||||
|
elif col == 2:
|
||||||
|
if val.startswith("@") and not os.path.isfile(os.path.expanduser(val[1:])):
|
||||||
|
return "Invalid file path"
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from .. import tservers
|
|
||||||
from mitmproxy.addons import replace
|
from mitmproxy.addons import replace
|
||||||
from mitmproxy.test import taddons
|
from mitmproxy.test import taddons
|
||||||
from mitmproxy.test import tflow
|
from mitmproxy.test import tflow
|
||||||
@ -32,7 +31,7 @@ class TestReplace:
|
|||||||
with taddons.context() as tctx:
|
with taddons.context() as tctx:
|
||||||
tctx.configure(
|
tctx.configure(
|
||||||
r,
|
r,
|
||||||
replacements = [
|
replacements=[
|
||||||
"/~q/foo/bar",
|
"/~q/foo/bar",
|
||||||
"/~s/foo/bar",
|
"/~s/foo/bar",
|
||||||
]
|
]
|
||||||
@ -47,53 +46,57 @@ class TestReplace:
|
|||||||
r.response(f)
|
r.response(f)
|
||||||
assert f.response.content == b"bar"
|
assert f.response.content == b"bar"
|
||||||
|
|
||||||
|
|
||||||
class TestUpstreamProxy(tservers.HTTPUpstreamProxyTest):
|
|
||||||
ssl = False
|
|
||||||
|
|
||||||
def test_order(self):
|
def test_order(self):
|
||||||
sa = replace.Replace()
|
r = replace.Replace()
|
||||||
self.proxy.tmaster.addons.add(sa)
|
with taddons.context() as tctx:
|
||||||
|
tctx.configure(
|
||||||
self.proxy.tmaster.options.replacements = [
|
r,
|
||||||
"/~q/foo/bar",
|
replacements=[
|
||||||
"/~q/bar/baz",
|
"/foo/bar",
|
||||||
"/~q/foo/oh noes!",
|
"/bar/baz",
|
||||||
"/~s/baz/ORLY"
|
"/foo/oh noes!",
|
||||||
]
|
"/bar/oh noes!",
|
||||||
p = self.pathoc()
|
]
|
||||||
with p.connect():
|
)
|
||||||
req = p.request("get:'%s/p/418:b\"foo\"'" % self.server.urlbase)
|
f = tflow.tflow()
|
||||||
assert req.content == b"ORLY"
|
f.request.content = b"foo"
|
||||||
assert req.status_code == 418
|
r.request(f)
|
||||||
|
assert f.request.content == b"baz"
|
||||||
|
|
||||||
|
|
||||||
class TestReplaceFile:
|
class TestReplaceFile:
|
||||||
def test_simple(self, tmpdir):
|
def test_simple(self, tmpdir):
|
||||||
r = replace.ReplaceFile()
|
r = replace.Replace()
|
||||||
rp = tmpdir.join("replacement")
|
|
||||||
rp.write("bar")
|
|
||||||
with taddons.context() as tctx:
|
with taddons.context() as tctx:
|
||||||
|
tmpfile = tmpdir.join("replacement")
|
||||||
|
tmpfile.write("bar")
|
||||||
tctx.configure(
|
tctx.configure(
|
||||||
r,
|
r,
|
||||||
replacement_files = [
|
replacements=["/~q/foo/@" + str(tmpfile)]
|
||||||
"/~q/foo/" + str(rp),
|
|
||||||
"/~s/foo/" + str(rp),
|
|
||||||
"/~b nonexistent/nonexistent/nonexistent",
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
f = tflow.tflow()
|
f = tflow.tflow()
|
||||||
f.request.content = b"foo"
|
f.request.content = b"foo"
|
||||||
r.request(f)
|
r.request(f)
|
||||||
assert f.request.content == b"bar"
|
assert f.request.content == b"bar"
|
||||||
|
|
||||||
f = tflow.tflow(resp=True)
|
def test_nonexistent(self, tmpdir):
|
||||||
f.response.content = b"foo"
|
r = replace.Replace()
|
||||||
r.response(f)
|
with taddons.context() as tctx:
|
||||||
assert f.response.content == b"bar"
|
with pytest.raises(Exception, match="Invalid file path"):
|
||||||
|
tctx.configure(
|
||||||
|
r,
|
||||||
|
replacements=["/~q/foo/@nonexistent"]
|
||||||
|
)
|
||||||
|
|
||||||
|
tmpfile = tmpdir.join("replacement")
|
||||||
|
tmpfile.write("bar")
|
||||||
|
tctx.configure(
|
||||||
|
r,
|
||||||
|
replacements=["/~q/foo/@" + str(tmpfile)]
|
||||||
|
)
|
||||||
|
tmpfile.remove()
|
||||||
f = tflow.tflow()
|
f = tflow.tflow()
|
||||||
f.request.content = b"nonexistent"
|
f.request.content = b"foo"
|
||||||
assert not tctx.master.event_log
|
assert not tctx.master.event_log
|
||||||
r.request(f)
|
r.request(f)
|
||||||
assert tctx.master.event_log
|
assert tctx.master.event_log
|
||||||
|
Loading…
Reference in New Issue
Block a user