diff --git a/.github/workflows/qa-tests.yml b/.github/workflows/qa-tests.yml index 075a75b54..e007be0cc 100644 --- a/.github/workflows/qa-tests.yml +++ b/.github/workflows/qa-tests.yml @@ -52,4 +52,4 @@ jobs: app_port: 8080 sleep_before_test: 30 config_update_delay: 100 - skip_tests: test_bypassed_ip_for_geo_blocking,test_demo_apps_generic_tests,test_path_traversal,test_outbound_domain_blocking,test_bypassed_ip,test_wave_attack,test_block_traffic_by_countries,test_user_rate_limiting_1_minute + skip_tests: test_bypassed_ip_for_geo_blocking,test_demo_apps_generic_tests,test_outbound_domain_blocking,test_bypassed_ip,test_wave_attack,test_block_traffic_by_countries,test_user_rate_limiting_1_minute diff --git a/aikido_zen/context/asgi/build_url_from_asgi.py b/aikido_zen/context/asgi/build_url_from_asgi.py index 1ae6a32f7..80dad7d72 100644 --- a/aikido_zen/context/asgi/build_url_from_asgi.py +++ b/aikido_zen/context/asgi/build_url_from_asgi.py @@ -14,4 +14,9 @@ def build_url_from_asgi(scope): root_path = scope.get("root_path", "") path = scope.get("path", "") uri = path.replace(root_path, "", 1) + + query_string = scope.get("query_string") or b"" + if query_string: + uri = f"{uri}?{query_string.decode('utf-8', errors='replace')}" + return f"{scheme}://{host}{uri}" diff --git a/aikido_zen/context/asgi/build_url_from_asgi_test.py b/aikido_zen/context/asgi/build_url_from_asgi_test.py index 47cd6ce68..ed6cfadf8 100644 --- a/aikido_zen/context/asgi/build_url_from_asgi_test.py +++ b/aikido_zen/context/asgi/build_url_from_asgi_test.py @@ -117,7 +117,32 @@ def test_build_url_from_asgi_with_query_string(): "scheme": "http", "server": ("localhost", 8000), "root_path": "", - "path": "/api/v1/resource?query=1", + "path": "/api/v1/resource", + "query_string": b"query=1", } expected = "http://localhost:8000/api/v1/resource?query=1" assert build_url_from_asgi(scope) == expected + + +def test_build_url_from_asgi_with_empty_query_string(): + scope = { + "scheme": "http", + "server": ("localhost", 8000), + "root_path": "", + "path": "/api/v1/resource", + "query_string": b"", + } + expected = "http://localhost:8000/api/v1/resource" + assert build_url_from_asgi(scope) == expected + + +def test_build_url_from_asgi_path_traversal_query(): + scope = { + "scheme": "http", + "server": ("localhost", 3018), + "root_path": "", + "path": "/api/read", + "query_string": b"path=../secrets/key.txt", + } + expected = "http://localhost:3018/api/read?path=../secrets/key.txt" + assert build_url_from_asgi(scope) == expected diff --git a/aikido_zen/context/asgi/init_test.py b/aikido_zen/context/asgi/init_test.py index ad05d6fe7..118c4f1aa 100644 --- a/aikido_zen/context/asgi/init_test.py +++ b/aikido_zen/context/asgi/init_test.py @@ -36,7 +36,7 @@ def test_asgi_scope_1(): "HEADER1_TEST_2": ["testValue2198&"], } assert context1.cookies == {"a": "b", "c": "d"} - assert context1.url == "https://192.168.0.1:443/a/b/c/d" + assert context1.url == "https://192.168.0.1:443/a/b/c/d?a=b&b=d" # Scope 2 : @@ -63,7 +63,7 @@ def test_asgi_scope_2(): "HEADER2_TEST_1": ["anotherValue"], } assert context2.cookies == {"x": "y", "z": "w"} - assert context2.url == "http://192.168.0.2:80/path/to/resource" + assert context2.url == "http://192.168.0.2:80/path/to/resource?x=y&z=w" # Scope 3 : @@ -90,7 +90,7 @@ def test_asgi_scope_3(): "HEADER3_TEST_3": ["postValue"], } assert context3.cookies == {"session": "abc123"} - assert context3.url == "http://192.168.0.3:8080/v1/resource" + assert context3.url == "http://192.168.0.3:8080/v1/resource?key1=value1&key2=value2" # Scope 4 : diff --git a/aikido_zen/context/init_test.py b/aikido_zen/context/init_test.py index dff7e3632..156f9c93d 100644 --- a/aikido_zen/context/init_test.py +++ b/aikido_zen/context/init_test.py @@ -59,7 +59,7 @@ def test_wsgi_context_1(): "CONTENT_TYPE": ["application/x-www-form-urlencoded"], }, "cookies": {"sessionId": "abc123xyz456"}, - "url": "https://example.com/hello", + "url": "https://example.com/hello?user=JohnDoe&age=30&age=35", "query": {"user": ["JohnDoe"], "age": ["30", "35"]}, "body": 123, "route": "/hello", @@ -91,7 +91,7 @@ def test_wsgi_context_2(): "USER_AGENT": ["Mozilla/5.0"], }, "cookies": {"sessionId": "abc123xyz456"}, - "url": "http://localhost:8080/hello", + "url": "http://localhost:8080/hello?user=JohnDoe&age=30&age=35", "query": {"user": ["JohnDoe"], "age": ["30", "35"]}, "body": {"test": True}, "route": "/hello", @@ -130,7 +130,7 @@ def test_context_is_picklable(mocker): assert unpickled_obj.source == "flask" assert unpickled_obj.method == "GET" assert unpickled_obj.remote_address == "198.51.100.23" - assert unpickled_obj.url == "http://localhost:8080/hello" + assert unpickled_obj.url == "http://localhost:8080/hello?user=JohnDoe&age=30&age=35" assert unpickled_obj.body == 123 assert unpickled_obj.headers == { "HEADER_1": ["header 1 value"], diff --git a/aikido_zen/context/wsgi/build_url_from_wsgi.py b/aikido_zen/context/wsgi/build_url_from_wsgi.py index 68a67ab6b..2cba9525a 100644 --- a/aikido_zen/context/wsgi/build_url_from_wsgi.py +++ b/aikido_zen/context/wsgi/build_url_from_wsgi.py @@ -15,4 +15,8 @@ def build_url_from_wsgi(request): else: host = request["HTTP_HOST"] + query_string = request.get("QUERY_STRING", "") + if query_string: + uri = f"{uri}?{query_string}" + return f"{scheme}://{host}{uri}" diff --git a/aikido_zen/context/wsgi/build_url_from_wsgi_test.py b/aikido_zen/context/wsgi/build_url_from_wsgi_test.py index 5b2f6cb65..7330554a7 100644 --- a/aikido_zen/context/wsgi/build_url_from_wsgi_test.py +++ b/aikido_zen/context/wsgi/build_url_from_wsgi_test.py @@ -52,11 +52,32 @@ def test_build_url_from_wsgi_with_query_string(): "PATH_INFO": "/search", "QUERY_STRING": "q=test", } - # Note: The function does not currently handle query strings, so we won't include it in the expected output + expected = "http://example.com/search?q=test" + assert build_url_from_wsgi(request) == expected + + +def test_build_url_from_wsgi_with_empty_query_string(): + request = { + "wsgi.url_scheme": "http", + "HTTP_HOST": "example.com", + "PATH_INFO": "/search", + "QUERY_STRING": "", + } expected = "http://example.com/search" assert build_url_from_wsgi(request) == expected +def test_build_url_from_wsgi_path_traversal_query(): + request = { + "wsgi.url_scheme": "http", + "HTTP_HOST": "localhost:3018", + "PATH_INFO": "/api/read", + "QUERY_STRING": "path=../secrets/key.txt", + } + expected = "http://localhost:3018/api/read?path=../secrets/key.txt" + assert build_url_from_wsgi(request) == expected + + def test_build_url_from_wsgi_root_path(): request = {"wsgi.url_scheme": "http", "HTTP_HOST": "example.com", "PATH_INFO": "/"} expected = "http://example.com/" diff --git a/aikido_zen/vulnerabilities/init_test.py b/aikido_zen/vulnerabilities/init_test.py index 54e10e89f..36cef6617 100644 --- a/aikido_zen/vulnerabilities/init_test.py +++ b/aikido_zen/vulnerabilities/init_test.py @@ -150,7 +150,7 @@ def test_sql_injection_with_comms(caplog, get_context, monkeypatch): "method": "GET", "route": "/hello", "source": "flask", - "url": "http://localhost:8080/hello", + "url": "http://localhost:8080/hello?user=JohnDoe&age=30&age=35", "userAgent": None, } del call_args[1].event["attack"]["stack"] # Hard to test