77from bbot .modules .base import BaseModule
88
99
10- class web_brute (BaseModule ):
10+ class webbrute (BaseModule ):
1111 watched_events = ["URL" ]
1212 produced_events = ["URL_UNVERIFIED" ]
1313 flags = ["active" , "loud" ]
@@ -124,16 +124,6 @@ def _response_metrics(self, response):
124124 "lines" : text .count ("\n " ) + 1 ,
125125 }
126126
127- def _batch_response_metrics (self , response ):
128- """Extract metrics from a raw blasthttp batch response."""
129- body = response .body or ""
130- return {
131- "status" : response .status ,
132- "length" : len (response .body_bytes ),
133- "words" : len (body .split ()),
134- "lines" : body .count ("\n " ) + 1 ,
135- }
136-
137127 def _is_baseline_match (self , metrics , baseline_filter ):
138128 """Return True if the response matches the baseline (i.e. should be filtered OUT)."""
139129 if baseline_filter .get ("abort" ):
@@ -187,7 +177,7 @@ async def baseline_fuzz(self, url, exts=None, prefix="", suffix=""):
187177 self .blast_client .request_batch_stream (canary_configs , 4 , rate_limit = self .rate )
188178 ):
189179 if result .success :
190- canary_results .append (self ._batch_response_metrics (result .response ))
180+ canary_results .append (self ._response_metrics (result .response ))
191181 if await self .helpers .yara .match (self .waf_yara_rules , result .response .body ):
192182 canary_waf_count += 1
193183
@@ -331,7 +321,7 @@ async def execute_fuzz(
331321 continue
332322
333323 response = result .response
334- metrics = self ._batch_response_metrics (response )
324+ metrics = self ._response_metrics (response )
335325
336326 # Check if this matches the baseline (should be filtered out)
337327 if ext_filter and self ._is_baseline_match (metrics , ext_filter ):
@@ -353,7 +343,7 @@ async def execute_fuzz(
353343 # not real findings (e.g. mod_userdir sending ~user to /)
354344 if 300 <= response .status < 400 :
355345 location = ""
356- for hdr_name , hdr_val in response .headers :
346+ for hdr_name , hdr_val in response .headers . items () :
357347 if hdr_name .lower () == "location" :
358348 location = hdr_val
359349 break
@@ -373,12 +363,15 @@ async def execute_fuzz(
373363 self .debug ("Found canary in results, all hits are likely false positives — aborting" )
374364 return
375365
376- # Mid-scan validation: one canary check per extension
366+ # Mid-scan validation: one canary check per extension.
367+ # Single request — use client.request() directly instead of a
368+ # 1-config request_batch_stream loop (the streaming API only
369+ # earns its keep with multiple in-flight requests).
377370 if hits and not baseline and ext_filter :
378371 canary_word = "" .join (random .choice (string .ascii_lowercase ) for _ in range (4 ))
379372 canary_url = f"{ url } { prefix } { canary_word } { suffix } { ext } "
380- canary_configs = [
381- blasthttp . BatchConfig (
373+ try :
374+ canary_response = await self . blast_client . request (
382375 canary_url ,
383376 headers = headers ,
384377 timeout = self .scan .http_timeout ,
@@ -387,14 +380,11 @@ async def execute_fuzz(
387380 follow_redirects = False ,
388381 proxy = proxy ,
389382 )
390- ]
391- canary_result = None
392- async for r in iter_batch_results (
393- self .blast_client .request_batch_stream (canary_configs , 1 , rate_limit = self .rate )
394- ):
395- canary_result = r
396- if canary_result is not None and canary_result .success :
397- canary_metrics = self ._batch_response_metrics (canary_result .response )
383+ except Exception as e :
384+ self .debug (f"Mid-scan canary request failed: { e } " )
385+ canary_response = None
386+ if canary_response is not None :
387+ canary_metrics = self ._response_metrics (canary_response )
398388 if not self ._is_baseline_match (canary_metrics , ext_filter ):
399389 self .verbose (
400390 f"Would have reported { len (hits )} hit(s), but mid-scan baseline check failed. "
0 commit comments