mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-23 00:01:36 +00:00
Fix command injection when exporting to curl
The command generated by `export.clip curl @focus` or `export.file curl @focus /path/to/file` wasn't being properly escaped so it could contain a malicious command instead of just a simple curl.
This commit is contained in:
parent
889987aa0a
commit
d027891cec
@ -1,4 +1,5 @@
|
||||
import typing
|
||||
import shlex
|
||||
|
||||
from mitmproxy import ctx
|
||||
from mitmproxy import command
|
||||
@ -18,20 +19,26 @@ def raise_if_missing_request(f: flow.Flow) -> None:
|
||||
|
||||
def curl_command(f: flow.Flow) -> str:
|
||||
raise_if_missing_request(f)
|
||||
data = "curl "
|
||||
args = ["curl"]
|
||||
request = f.request.copy() # type: ignore
|
||||
request.decode(strict=False)
|
||||
for k, v in request.headers.items(multi=True):
|
||||
data += "-H '%s:%s' " % (k, v)
|
||||
args += ["-H", shlex.quote("%s:%s" % (k, v))]
|
||||
if request.method != "GET":
|
||||
data += "-X %s " % request.method
|
||||
data += "'%s'" % request.url
|
||||
args += ["-X", shlex.quote(request.method)]
|
||||
args.append(shlex.quote(request.url))
|
||||
if request.content:
|
||||
data += " --data-binary '%s'" % strutils.bytes_to_escaped_str(
|
||||
request.content,
|
||||
escape_single_quotes=True
|
||||
)
|
||||
return data
|
||||
try:
|
||||
content = strutils.always_str(request.content)
|
||||
except UnicodeDecodeError:
|
||||
# shlex.quote doesn't support a bytes object
|
||||
# see https://github.com/python/cpython/pull/10871
|
||||
raise exceptions.CommandError("Request content must be valid unicode")
|
||||
args += [
|
||||
"--data-binary",
|
||||
shlex.quote(strutils.always_str(request.content))
|
||||
]
|
||||
return ' '.join(args)
|
||||
|
||||
|
||||
def httpie_command(f: flow.Flow) -> str:
|
||||
|
@ -1,4 +1,5 @@
|
||||
import os
|
||||
import shlex
|
||||
|
||||
import pytest
|
||||
import pyperclip
|
||||
@ -47,23 +48,40 @@ def tcp_flow():
|
||||
|
||||
class TestExportCurlCommand:
|
||||
def test_get(self, get_request):
|
||||
result = """curl -H 'header:qvalue' -H 'content-length:0' 'http://address:22/path?a=foo&a=bar&b=baz'"""
|
||||
result = """curl -H header:qvalue -H content-length:0 'http://address:22/path?a=foo&a=bar&b=baz'"""
|
||||
assert export.curl_command(get_request) == result
|
||||
|
||||
def test_post(self, post_request):
|
||||
result = "curl -H 'content-length:256' -X POST 'http://address:22/path' --data-binary '{}'".format(
|
||||
str(bytes(range(256)))[2:-1]
|
||||
)
|
||||
post_request.request.content = b'nobinarysupport'
|
||||
result = "curl -H content-length:15 -X POST http://address:22/path --data-binary nobinarysupport"
|
||||
assert export.curl_command(post_request) == result
|
||||
|
||||
def test_fails_with_binary_data(self, post_request):
|
||||
# shlex.quote doesn't support a bytes object
|
||||
# see https://github.com/python/cpython/pull/10871
|
||||
with pytest.raises(exceptions.CommandError):
|
||||
export.curl_command(post_request)
|
||||
|
||||
def test_patch(self, patch_request):
|
||||
result = """curl -H 'header:qvalue' -H 'content-length:7' -X PATCH 'http://address:22/path?query=param' --data-binary 'content'"""
|
||||
result = """curl -H header:qvalue -H content-length:7 -X PATCH 'http://address:22/path?query=param' --data-binary content"""
|
||||
assert export.curl_command(patch_request) == result
|
||||
|
||||
def test_tcp(self, tcp_flow):
|
||||
with pytest.raises(exceptions.CommandError):
|
||||
export.curl_command(tcp_flow)
|
||||
|
||||
def test_escape_single_quotes_in_body(self):
|
||||
request = tflow.tflow(
|
||||
req=tutils.treq(
|
||||
method=b'POST',
|
||||
headers=(),
|
||||
content=b"'&#"
|
||||
)
|
||||
)
|
||||
command = export.curl_command(request)
|
||||
assert shlex.split(command)[-2] == '--data-binary'
|
||||
assert shlex.split(command)[-1] == "'&#"
|
||||
|
||||
|
||||
class TestExportHttpieCommand:
|
||||
def test_get(self, get_request):
|
||||
|
Loading…
Reference in New Issue
Block a user