Fix command injection when exporting to httpie

The command generated by `export.clip httpie @focus` or `export.file
httpie @focus /path/to/file` wasn't being properly escaped so it could
contain a malicious command instead of just a simple httpie call.
This commit is contained in:
Matías Lang 2019-01-13 23:45:28 -03:00
parent d027891cec
commit eab4174b87
2 changed files with 34 additions and 13 deletions

View File

@ -44,17 +44,21 @@ def curl_command(f: flow.Flow) -> str:
def httpie_command(f: flow.Flow) -> str:
raise_if_missing_request(f)
request = f.request.copy() # type: ignore
data = "http %s " % request.method
args = ["http"]
args.append(shlex.quote(request.method))
request.decode(strict=False)
data += "%s" % request.url
args.append(shlex.quote(request.url))
for k, v in request.headers.items(multi=True):
data += " '%s:%s'" % (k, v)
args.append(shlex.quote("%s:%s" % (k, v)))
if request.content:
data += " <<< '%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 += ["<<<", shlex.quote(content)]
return ' '.join(args)
def raw(f: flow.Flow) -> bytes:

View File

@ -85,23 +85,40 @@ class TestExportCurlCommand:
class TestExportHttpieCommand:
def test_get(self, get_request):
result = """http GET http://address:22/path?a=foo&a=bar&b=baz 'header:qvalue' 'content-length:0'"""
result = """http GET 'http://address:22/path?a=foo&a=bar&b=baz' header:qvalue content-length:0"""
assert export.httpie_command(get_request) == result
def test_post(self, post_request):
result = "http POST http://address:22/path 'content-length:256' <<< '{}'".format(
str(bytes(range(256)))[2:-1]
)
post_request.request.content = b'nobinarysupport'
result = "http POST http://address:22/path content-length:15 <<< nobinarysupport"
assert export.httpie_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.httpie_command(post_request)
def test_patch(self, patch_request):
result = """http PATCH http://address:22/path?query=param 'header:qvalue' 'content-length:7' <<< 'content'"""
result = """http PATCH 'http://address:22/path?query=param' header:qvalue content-length:7 <<< content"""
assert export.httpie_command(patch_request) == result
def test_tcp(self, tcp_flow):
with pytest.raises(exceptions.CommandError):
export.httpie_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.httpie_command(request)
assert shlex.split(command)[-2] == '<<<'
assert shlex.split(command)[-1] == "'&#"
class TestRaw:
def test_get(self, get_request):