diff --git a/sanic/response.py b/sanic/response.py index 62daf91e..2281766e 100644 --- a/sanic/response.py +++ b/sanic/response.py @@ -1,5 +1,6 @@ from mimetypes import guess_type from os import path +from urllib.parse import quote_plus try: from ujson import dumps as json_dumps @@ -360,8 +361,11 @@ def redirect(to, headers=None, status=302, """ headers = headers or {} + # URL Quote the URL before redirecting + safe_to = quote_plus(to, safe=":/#?&=@[]!$&'()*+,;") + # According to RFC 7231, a relative URI is now permitted. - headers['Location'] = to + headers['Location'] = safe_to return HTTPResponse( status=status, diff --git a/tests/test_redirect.py b/tests/test_redirect.py index f5b734e3..5fdec2a6 100644 --- a/tests/test_redirect.py +++ b/tests/test_redirect.py @@ -32,6 +32,10 @@ def redirect_app(): def handler(request): return text('OK') + @app.route('/redirect_with_header_injection') + async def redirect_with_header_injection(request): + return redirect("/unsafe\ntest-header: test-value\n\ntest-body") + return app @@ -92,3 +96,16 @@ def test_chained_redirect(redirect_app): assert response.url.endswith('/3') except AttributeError: assert response.url.path.endswith('/3') + + +def test_redirect_with_header_injection(redirect_app): + """ + Test redirection to a URL with header and body injections. + """ + request, response = redirect_app.test_client.get( + "/redirect_with_header_injection", + allow_redirects=False) + + assert response.status == 302 + assert "test-header" not in response.headers + assert not response.text.startswith('test-body')