map local: minor fixes

This commit is contained in:
Maximilian Hils 2020-07-17 20:25:48 +02:00
parent c98f12c4f3
commit fb743c7da7
2 changed files with 44 additions and 40 deletions

View File

@ -1,7 +1,7 @@
import mimetypes import mimetypes
import re import re
import typing import typing
import urllib import urllib.parse
from pathlib import Path from pathlib import Path
from werkzeug.security import safe_join from werkzeug.security import safe_join
@ -59,19 +59,19 @@ def file_candidates(url: str, spec: MapLocalSpec) -> typing.List[Path]:
else: else:
suffix = re.split(spec.regex, url, maxsplit=1)[1] suffix = re.split(spec.regex, url, maxsplit=1)[1]
suffix = suffix.split("?")[0] # remove query string suffix = suffix.split("?")[0] # remove query string
suffix = suffix.strip("/").replace("\\", "/") suffix = suffix.strip("/")
if suffix: if suffix:
decoded_suffix = urllib.parse.unquote(suffix) decoded_suffix = urllib.parse.unquote(suffix)
simplified_suffix = re.sub(r"[^0-9a-zA-Z\-_.=(),/]", "_", decoded_suffix)
suffix_candidates = [decoded_suffix, f"{decoded_suffix}/index.html"] suffix_candidates = [decoded_suffix, f"{decoded_suffix}/index.html"]
if decoded_suffix != simplified_suffix:
suffix_candidates.append(simplified_suffix) escaped_suffix = re.sub(r"[^0-9a-zA-Z\-_.=(),/]", "_", decoded_suffix)
suffix_candidates.append(f"{simplified_suffix}/index.html") if decoded_suffix != escaped_suffix:
suffix_candidates.extend([escaped_suffix, f"{escaped_suffix}/index.html"])
try: try:
return [ return [
_safe_path_join(spec.local_path, suff) for suff in suffix_candidates _safe_path_join(spec.local_path, x)
for x in suffix_candidates
] ]
except ValueError: except ValueError:
return [] return []
@ -110,40 +110,42 @@ class MapLocal:
url = flow.request.pretty_url url = flow.request.pretty_url
any_spec_matches = False all_candidates = []
for spec in self.replacements: for spec in self.replacements:
if spec.matches(flow) and re.search(spec.regex, url): if spec.matches(flow) and re.search(spec.regex, url):
any_spec_matches = True
local_file: typing.Optional[Path] = None
tested_paths = []
if spec.local_path.is_file(): if spec.local_path.is_file():
local_file = spec.local_path candidates = [spec.local_path]
elif spec.local_path.is_dir(): else:
tested_paths.append(spec.local_path) candidates = file_candidates(url, spec)
for candidate in file_candidates(url, spec): all_candidates.extend(candidates)
tested_paths.append(candidate)
local_file = None
for candidate in candidates:
if candidate.is_file(): if candidate.is_file():
local_file = candidate local_file = candidate
break break
headers = {"Server": version.MITMPROXY} if local_file:
headers = {
"Server": version.MITMPROXY
}
mimetype = mimetypes.guess_type(str(local_file))[0] mimetype = mimetypes.guess_type(str(local_file))[0]
if mimetype: if mimetype:
headers = {"Content-Type": mimetype} headers["Content-Type"] = mimetype
if local_file:
try: try:
flow.response = http.HTTPResponse.make( contents = local_file.read_bytes()
200,
local_file.read_bytes(),
headers
)
except IOError as e: except IOError as e:
ctx.log.warn(f"Could not read file: {e}") ctx.log.warn(f"Could not read file: {e}")
continue continue
flow.response = http.HTTPResponse.make(
200,
contents,
headers
)
# only set flow.response once, for the first matching rule # only set flow.response once, for the first matching rule
return return
if any_spec_matches: if all_candidates:
flow.response = http.HTTPResponse.make(404) flow.response = http.HTTPResponse.make(404)
ctx.log.warn(f"None of the local file candidates exist: {*tested_paths,}") ctx.log.info(f"None of the local file candidates exist: {', '.join(str(x) for x in all_candidates)}")

View File

@ -1,3 +1,4 @@
import sys
from pathlib import Path from pathlib import Path
import pytest import pytest
@ -20,7 +21,8 @@ from mitmproxy.test import tflow
("http://example.com/foo/bar.jpg", ":example.com/foo:/tmp", ["/tmp/bar.jpg", "/tmp/bar.jpg/index.html"]), ("http://example.com/foo/bar.jpg", ":example.com/foo:/tmp", ["/tmp/bar.jpg", "/tmp/bar.jpg/index.html"]),
("https://example.com/foo/bar.jpg", ":example.com/foo:/tmp", ["/tmp/bar.jpg", "/tmp/bar.jpg/index.html"]), ("https://example.com/foo/bar.jpg", ":example.com/foo:/tmp", ["/tmp/bar.jpg", "/tmp/bar.jpg/index.html"]),
("https://example.com/foo/bar.jpg?query", ":example.com/foo:/tmp", ["/tmp/bar.jpg", "/tmp/bar.jpg/index.html"]), ("https://example.com/foo/bar.jpg?query", ":example.com/foo:/tmp", ["/tmp/bar.jpg", "/tmp/bar.jpg/index.html"]),
("https://example.com/foo/bar/baz.jpg", ":example.com/foo:/tmp", ["/tmp/bar/baz.jpg", "/tmp/bar/baz.jpg/index.html"]), ("https://example.com/foo/bar/baz.jpg", ":example.com/foo:/tmp",
["/tmp/bar/baz.jpg", "/tmp/bar/baz.jpg/index.html"]),
("https://example.com/foo/bar.jpg", ":/foo/bar.jpg:/tmp", ["/tmp/index.html"]), ("https://example.com/foo/bar.jpg", ":/foo/bar.jpg:/tmp", ["/tmp/index.html"]),
] + [ ] + [
# URL decode and special characters # URL decode and special characters
@ -61,12 +63,12 @@ from mitmproxy.test import tflow
] + [ ] + [
# test directory traversal detection # test directory traversal detection
("https://example.com/../../../../../../etc/passwd", ":example.com:/tmp", []), ("https://example.com/../../../../../../etc/passwd", ":example.com:/tmp", []),
# those get already sanitized to benign versions before they reach our detection: # this is slightly hacky, but werkzeug's behavior differs per system.
("https://example.com/C:\\foo.txt", ":example.com:/tmp", [ ("https://example.com/C:\\foo.txt", ":example.com:/tmp", [] if sys.platform == "win32" else [
"/tmp/C:/foo.txt", "/tmp/C:\\foo.txt",
"/tmp/C:/foo.txt/index.html", "/tmp/C:\\foo.txt/index.html",
"/tmp/C_/foo.txt", "/tmp/C__foo.txt",
"/tmp/C_/foo.txt/index.html" "/tmp/C__foo.txt/index.html"
]), ]),
("https://example.com//etc/passwd", ":example.com:/tmp", ["/tmp/etc/passwd", "/tmp/etc/passwd/index.html"]), ("https://example.com//etc/passwd", ":example.com:/tmp", ["/tmp/etc/passwd", "/tmp/etc/passwd/index.html"]),
] ]