@@ -2853,3 +2853,164 @@ def check(self, module_test, events):
28532853 assert sqli_postparam_converted_finding_emitted , (
28542854 "SQLi POSTPARAM (converted from GETPARAM) FINDING not emitted (try_get_as_post failed)"
28552855 )
2856+
2857+
2858+ # Padding Oracle Jitter Stability Pre-Check
2859+ class Test_Lightfuzz_PaddingOracleDetection_JitterStability (Test_Lightfuzz_PaddingOracleDetection ):
2860+ """Padding oracle negative test: the endpoint produces different response bodies for identical inputs
2861+ (e.g. ADFS with embedded timestamps/nonces). The stability pre-check should detect this and skip."""
2862+
2863+ jitter_counter = 0
2864+
2865+ def request_handler (self , request ):
2866+ encrypted_value = quote (
2867+ "dplyorsu8VUriMW/8DqVDU6kRwL/FDk3Q+4GXVGZbo0CTh9YX1YvzZZJrYe4cHxvAICyliYtp1im4fWoOa54Zg=="
2868+ )
2869+ default_html_response = f"""
2870+ <html>
2871+ <body>
2872+ <form action="/decrypt" method="post">
2873+ <input type="hidden" name="encrypted_data" value="{ encrypted_value } " />
2874+ <button type="submit">Decrypt</button>
2875+ </form>
2876+ </body>
2877+ </html>
2878+ """
2879+
2880+ if "/decrypt" in request .url and request .method == "POST" :
2881+ # Every response is unique, simulating ADFS-style dynamic content
2882+ Test_Lightfuzz_PaddingOracleDetection_JitterStability .jitter_counter += 1
2883+ response_content = f"Error correlation_id={ Test_Lightfuzz_PaddingOracleDetection_JitterStability .jitter_counter } nonce=abc{ Test_Lightfuzz_PaddingOracleDetection_JitterStability .jitter_counter } "
2884+ return Response (response_content , status = 200 )
2885+ else :
2886+ return Response (default_html_response , status = 200 )
2887+
2888+ def check (self , module_test , events ):
2889+ web_parameter_extracted = False
2890+ padding_oracle_detected = False
2891+ for e in events :
2892+ if e .type == "WEB_PARAMETER" :
2893+ if "HTTP Extracted Parameter [encrypted_data] (POST Form" in e .data ["description" ]:
2894+ web_parameter_extracted = True
2895+ if e .type == "FINDING" :
2896+ if "Padding Oracle" in e .data ["description" ]:
2897+ padding_oracle_detected = True
2898+
2899+ assert web_parameter_extracted , "Web parameter was not extracted"
2900+ assert not padding_oracle_detected , (
2901+ "Padding oracle should NOT be detected when endpoint has jittery responses (stability pre-check should abort)"
2902+ )
2903+
2904+
2905+ # XSS Multi-Context Reflection False Positive
2906+ class Test_Lightfuzz_xss_multicontext (Test_Lightfuzz_xss ):
2907+ """XSS negative test: parameter reflected in multiple contexts with different encoding.
2908+ Quote survives in text content but is encoded in tag attribute. Should NOT report Tag Attribute XSS."""
2909+
2910+ def request_handler (self , request ):
2911+ qs = str (request .query_string .decode ())
2912+
2913+ parameter_block = """
2914+ <html>
2915+ <form action="/" method="GET">
2916+ <input type="text" name="path" value="default">
2917+ <button type="submit">Submit</button>
2918+ </form>
2919+ </html>
2920+ """
2921+ if "path=" in qs :
2922+ value = qs .split ("path=" )[1 ]
2923+ if "&" in value :
2924+ value = value .split ("&" )[0 ]
2925+ decoded = unquote (value )
2926+ # Tag attribute context: quotes are URL-encoded (safe)
2927+ attr_value = decoded .replace ('"' , "%22" )
2928+ # Text content: raw reflection (quotes survive but harmless here)
2929+ text_value = decoded
2930+ # JS context: everything URL-encoded (safe)
2931+ js_value = value
2932+
2933+ html = f"""
2934+ <html>
2935+ <form action="/page?path={ attr_value } " method="GET">
2936+ <input type="text" name="path">
2937+ </form>
2938+ <h1>{ text_value } </h1>
2939+ <script>if('{ js_value } ') {{ }}</script>
2940+ </html>
2941+ """
2942+ return Response (html , status = 200 )
2943+ return Response (parameter_block , status = 200 )
2944+
2945+ async def setup_after_prep (self , module_test ):
2946+ module_test .scan .modules ["lightfuzz" ].helpers .rand_string = lambda * args , ** kwargs : "AAAAAAAAAAAAAA"
2947+ expect_args = re .compile ("/" )
2948+ module_test .set_expect_requests_handler (expect_args = expect_args , request_handler = self .request_handler )
2949+
2950+ def check (self , module_test , events ):
2951+ web_parameter_emitted = False
2952+ tag_attribute_xss_emitted = False
2953+ for e in events :
2954+ if e .type == "WEB_PARAMETER" :
2955+ if "HTTP Extracted Parameter [path]" in e .data ["description" ]:
2956+ web_parameter_emitted = True
2957+ if e .type == "FINDING" :
2958+ if "Possible Reflected XSS" in e .data ["description" ] and "Tag Attribute" in e .data ["description" ]:
2959+ tag_attribute_xss_emitted = True
2960+
2961+ assert web_parameter_emitted , "WEB_PARAMETER was not emitted"
2962+ assert not tag_attribute_xss_emitted , (
2963+ "Tag Attribute XSS should NOT be reported when the quote only survives in text content, not in tag attributes"
2964+ )
2965+
2966+
2967+ # SQLi WAF False Positive (Akamai-style 403)
2968+ class Test_Lightfuzz_sqli_waf (Test_Lightfuzz_sqli ):
2969+ """SQLi negative test: endpoint returns 403 with WAF signature when single quote is sent.
2970+ Should NOT report SQL injection."""
2971+
2972+ def request_handler (self , request ):
2973+ qs = str (request .query_string .decode ())
2974+ parameter_block = """
2975+ <section class=search>
2976+ <form action=/ method=GET>
2977+ <input type=text placeholder='Search the blog...' name=search>
2978+ <button type=submit class=button>Search</button>
2979+ </form>
2980+ </section>
2981+ """
2982+ if "search=" in qs :
2983+ value = qs .split ("=" )[1 ]
2984+ if "&" in value :
2985+ value = value .split ("&" )[0 ]
2986+
2987+ if value .endswith ("'" ) and not value .endswith ("''" ):
2988+ # WAF blocks the request with a known WAF string
2989+ waf_response = """
2990+ <html>
2991+ <head><title>Access Denied</title></head>
2992+ <body>
2993+ <h1>Access Denied</h1>
2994+ <p>The requested URL was rejected. Please consult with your administrator.</p>
2995+ </body>
2996+ </html>
2997+ """
2998+ return Response (waf_response , status = 403 )
2999+ return Response (parameter_block , status = 200 )
3000+ return Response (parameter_block , status = 200 )
3001+
3002+ def check (self , module_test , events ):
3003+ web_parameter_emitted = False
3004+ sqli_finding_emitted = False
3005+ for e in events :
3006+ if e .type == "WEB_PARAMETER" :
3007+ if "HTTP Extracted Parameter [search]" in e .data ["description" ]:
3008+ web_parameter_emitted = True
3009+ if e .type == "FINDING" :
3010+ if "Possible SQL Injection" in e .data ["description" ] and "Code Change" in e .data ["description" ]:
3011+ sqli_finding_emitted = True
3012+
3013+ assert web_parameter_emitted , "WEB_PARAMETER was not emitted"
3014+ assert not sqli_finding_emitted , (
3015+ "SQLi should NOT be reported when single quote probe triggers a WAF 403 response"
3016+ )
0 commit comments