From 96a572a5c4a1e3a25d45d08785b8ebf89343e024 Mon Sep 17 00:00:00 2001 From: Shirsendu Mondal Date: Sun, 8 Mar 2026 19:24:05 -0400 Subject: [PATCH 1/3] fix(cors): resolve regex false negative and add value-based regex coverage --- nettacker/modules/vuln/http_cors.yaml | 2 +- tests/test_yaml_regexes.py | 48 ++++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/nettacker/modules/vuln/http_cors.yaml b/nettacker/modules/vuln/http_cors.yaml index d116f3633..2439a356e 100644 --- a/nettacker/modules/vuln/http_cors.yaml +++ b/nettacker/modules/vuln/http_cors.yaml @@ -334,5 +334,5 @@ payloads: conditions: headers: Access-Control-Allow-Origin: - regex: "(http|https):\\/\\/evil.com " + regex: "(http|https):\\/\\/evil.com" reverse: false diff --git a/tests/test_yaml_regexes.py b/tests/test_yaml_regexes.py index 8627aca62..5c8ac90e6 100644 --- a/tests/test_yaml_regexes.py +++ b/tests/test_yaml_regexes.py @@ -18,7 +18,7 @@ def get_yaml_files(): def load_yaml(file_path): - with open(file_path, "r") as f: + with open(file_path, "r", encoding="utf-8") as f: return yaml.safe_load(f) @@ -28,9 +28,31 @@ def extract_http_regexes(payloads): if payload.get("library") != "http": continue for step in payload.get("steps", []): - conditions = step.get("response", {}).get("conditions", {}) - if "content" in conditions and "regex" in conditions["content"]: - regexes.append(conditions["content"]["regex"]) + response = step.get("response", {}) + regexes.extend(extract_regex_values(response)) + return regexes + + +def extract_regex_values(response): + regexes = [] + conditions = response.get("conditions", {}) + + if not isinstance(conditions, dict): + return regexes + + # Mirror runtime behavior in response_conditions_matched: evaluate top-level + # condition keys and nested header regexes only. + for condition, condition_value in conditions.items(): + if condition in ["reason", "status_code", "content", "url"]: + if isinstance(condition_value, dict) and isinstance(condition_value.get("regex"), str): + regexes.append(condition_value["regex"]) + if condition == "headers" and isinstance(condition_value, dict): + for _, header_condition in condition_value.items(): + if isinstance(header_condition, dict) and isinstance( + header_condition.get("regex"), str + ): + regexes.append(header_condition["regex"]) + return regexes @@ -85,3 +107,21 @@ def test_yaml_regexes_valid(yaml_file): for regex in regexes: assert is_valid_regex(regex), f"Invalid regex in {yaml_file}: `{regex}`" + + +def test_http_cors_reflected_origin_regex_matches_expected_origin(): + data = load_yaml("nettacker/modules/vuln/http_cors.yaml") + payloads = data.get("payloads", []) + + origins = ("http://evil.com", "https://evil.com") + matching_regexes = [] + + for regex in extract_http_regexes(payloads): + compiled_regex = re.compile(regex) + if all(compiled_regex.fullmatch(origin) for origin in origins): + matching_regexes.append(regex) + + assert matching_regexes, ( + "Expected at least one http_cors regex to fully match both reflected origins: " + "`http://evil.com` and `https://evil.com`" + ) From 4a07c5ca8faa1411e241025ee7ae52a7700f5d69 Mon Sep 17 00:00:00 2001 From: Shirsendu Mondal <76588814+Shirshaw64p@users.noreply.github.com> Date: Sun, 8 Mar 2026 19:33:02 -0400 Subject: [PATCH 2/3] Update nettacker/modules/vuln/http_cors.yaml Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Shirsendu Mondal <76588814+Shirshaw64p@users.noreply.github.com> --- nettacker/modules/vuln/http_cors.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nettacker/modules/vuln/http_cors.yaml b/nettacker/modules/vuln/http_cors.yaml index 2439a356e..a2575fde2 100644 --- a/nettacker/modules/vuln/http_cors.yaml +++ b/nettacker/modules/vuln/http_cors.yaml @@ -334,5 +334,5 @@ payloads: conditions: headers: Access-Control-Allow-Origin: - regex: "(http|https):\\/\\/evil.com" + regex: "^(http|https):\\/\\/evil\\.com$" reverse: false From fbeded704473941c6f5c2b18a99dbfad88f7cbd6 Mon Sep 17 00:00:00 2001 From: Shirsendu Mondal <76588814+Shirshaw64p@users.noreply.github.com> Date: Sun, 8 Mar 2026 19:33:28 -0400 Subject: [PATCH 3/3] Update tests/test_yaml_regexes.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Shirsendu Mondal <76588814+Shirshaw64p@users.noreply.github.com> --- tests/test_yaml_regexes.py | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/tests/test_yaml_regexes.py b/tests/test_yaml_regexes.py index 5c8ac90e6..75315d438 100644 --- a/tests/test_yaml_regexes.py +++ b/tests/test_yaml_regexes.py @@ -34,26 +34,20 @@ def extract_http_regexes(payloads): def extract_regex_values(response): - regexes = [] - conditions = response.get("conditions", {}) - - if not isinstance(conditions, dict): + def _collect(node): + regexes = [] + if isinstance(node, dict): + for key, value in node.items(): + if key == "regex" and isinstance(value, str): + regexes.append(value) + else: + regexes.extend(_collect(value)) + elif isinstance(node, list): + for item in node: + regexes.extend(_collect(item)) return regexes - # Mirror runtime behavior in response_conditions_matched: evaluate top-level - # condition keys and nested header regexes only. - for condition, condition_value in conditions.items(): - if condition in ["reason", "status_code", "content", "url"]: - if isinstance(condition_value, dict) and isinstance(condition_value.get("regex"), str): - regexes.append(condition_value["regex"]) - if condition == "headers" and isinstance(condition_value, dict): - for _, header_condition in condition_value.items(): - if isinstance(header_condition, dict) and isinstance( - header_condition.get("regex"), str - ): - regexes.append(header_condition["regex"]) - - return regexes + return _collect(response.get("conditions", {})) def extract_socket_regexes(file_name, payloads):