@@ -403,6 +403,41 @@ def in_bl(self, value):
403403
404404 return False
405405
406+ def _is_archived (self , event ):
407+ """Check if an event represents archived wayback content."""
408+ return isinstance (event .data , dict ) and "archive_url" in event .data
409+
410+ def _event_host (self , event ):
411+ """Get the effective host from an event.
412+
413+ For archived wayback content, uses data["host"] (the original target hostname).
414+ For regular events, uses event.host.
415+
416+ NOTE: Regular HTTP_RESPONSE events also have data["host"], but it contains the
417+ resolved IP — NOT a hostname override.
418+ """
419+ if self ._is_archived (event ) and event .data .get ("host" ):
420+ return str (event .data ["host" ])
421+ return str (event .host )
422+
423+ def _event_base_url (self , event ):
424+ """Get the effective base URL from an event.
425+
426+ For archived wayback content, reconstructs the URL from explicit fields
427+ (host/scheme/port/path). For regular events, returns event.parsed_url directly.
428+ """
429+ if not self ._is_archived (event ):
430+ return event .parsed_url
431+ scheme = event .data .get ("scheme" , event .parsed_url .scheme )
432+ host = self ._event_host (event )
433+ port = event .data .get ("port" )
434+ if port is not None :
435+ port = int (port )
436+ if not ((scheme == "http" and port == 80 ) or (scheme == "https" and port == 443 )):
437+ host = f"{ host } :{ port } "
438+ path = event .data .get ("path" , event .parsed_url .path )
439+ return urlparse (f"{ scheme } ://{ host } { path } " )
440+
406441 def url_unparse (self , param_type , parsed_url ):
407442 # Reconstructs a URL, optionally omitting the query string based on remove_querystring configuration value.
408443 if param_type == "GETPARAM" :
@@ -742,8 +777,9 @@ async def process(self, yara_results, event, yara_rule_settings, discovery_conte
742777
743778 # The endpoint is usually a form action - we should use it if we have it. If not, default to URL.
744779 else :
745- # Use the original URL as the base and resolve the endpoint correctly in case of relative paths
746- base_url = f"{ event .parsed_url .scheme } ://{ event .parsed_url .netloc } { event .parsed_url .path } "
780+ # Use the effective base URL (which may differ from parsed_url for archived content)
781+ event_base = self .excavate ._event_base_url (event )
782+ base_url = f"{ event_base .scheme } ://{ event_base .netloc } { event_base .path } "
747783 if not self .excavate .remove_querystring and len (event .parsed_url .query ) > 0 :
748784 base_url += f"?{ event .parsed_url .query } "
749785 url = urljoin (base_url , endpoint )
@@ -1113,6 +1149,34 @@ async def process(self, yara_results, event, yara_rule_settings, discovery_conte
11131149 if yara_results :
11141150 event .add_tag ("login-page" )
11151151
1152+ class DirectoryListingExtractor (ExcavateRule ):
1153+ description = "Detects directory listing pages from web servers."
1154+ signatures = {
1155+ "Apache_Nginx" : '"<title>Index of /"' ,
1156+ "IIS" : '"[To Parent Directory]"' ,
1157+ "Python_HTTP_Server" : '"<h1>Directory listing for"' ,
1158+ "Generic_Directory_Listing" : '"<title>Directory Listing"' ,
1159+ }
1160+ yara_rules = {}
1161+
1162+ def __init__ (self , excavate ):
1163+ super ().__init__ (excavate )
1164+ signature_component_list = []
1165+ for signature_name , signature in self .signatures .items ():
1166+ signature_component_list .append (rf"${ signature_name } = { signature } " )
1167+ signature_component = " " .join (signature_component_list )
1168+ self .yara_rules ["directory_listing" ] = (
1169+ f'rule directory_listing {{meta: description = "contains a directory listing" strings: { signature_component } condition: any of them}}'
1170+ )
1171+
1172+ async def process (self , yara_results , event , yara_rule_settings , discovery_context ):
1173+ for identifier in yara_results .keys ():
1174+ for findings in yara_results [identifier ]:
1175+ event_data = {
1176+ "description" : f"{ discovery_context } { yara_rule_settings .description } ({ identifier } )"
1177+ }
1178+ await self .report (event_data , event , yara_rule_settings , discovery_context , event_type = "FINDING" )
1179+
11161180 def add_yara_rule (self , rule_name , rule_content , rule_instance ):
11171181 rule_instance .name = rule_name
11181182 self .yara_rules_dict [rule_name ] = rule_content
@@ -1140,12 +1204,13 @@ async def emit_custom_parameters(self, event, config_key, param_type, descriptio
11401204 # Emits WEB_PARAMETER events for custom headers and cookies from the configuration.
11411205 custom_params = self .scan .web_config .get (config_key , {})
11421206 for param_name , param_value in custom_params .items ():
1207+ event_base = self ._event_base_url (event )
11431208 await self .emit_web_parameter (
1144- host = event . parsed_url . hostname ,
1209+ host = self . _event_host ( event ) ,
11451210 param_type = param_type ,
11461211 name = param_name ,
11471212 original_value = param_value ,
1148- url = self .url_unparse (param_type , event . parsed_url ),
1213+ url = self .url_unparse (param_type , event_base ),
11491214 description = f"HTTP Extracted Parameter [{ param_name } ] ({ description_suffix } )" ,
11501215 additional_params = _exclude_key (custom_params , param_name ),
11511216 event = event ,
@@ -1264,15 +1329,15 @@ async def search(self, data, event, content_type, discovery_context="HTTP respon
12641329 if results :
12651330 for parameter_name , original_value in results :
12661331 await self .emit_web_parameter (
1267- host = str (event . host ),
1332+ host = self . _event_host (event ),
12681333 param_type = "SPECULATIVE" ,
12691334 name = parameter_name ,
12701335 original_value = original_value ,
12711336 url = event .url ,
12721337 description = f"HTTP Extracted Parameter (speculative from { source_type } content) [{ parameter_name } ]" ,
12731338 additional_params = {},
12741339 event = event ,
1275- context = f"excavate's Parameter extractor found a speculative WEB_PARAMETER: { parameter_name } by parsing { source_type } data from { str (event . host )} " ,
1340+ context = f"excavate's Parameter extractor found a speculative WEB_PARAMETER: { parameter_name } by parsing { source_type } data from { self . _event_host (event )} " ,
12761341 )
12771342 return
12781343
@@ -1324,7 +1389,7 @@ async def handle_event(self, event, **kwargs):
13241389 ) in extract_params_url (event .parsed_url ):
13251390 if self .in_bl (parameter_name ) is False :
13261391 await self .emit_web_parameter (
1327- host = parsed_url . hostname ,
1392+ host = self . _event_host ( event ) ,
13281393 param_type = "GETPARAM" ,
13291394 name = parameter_name ,
13301395 original_value = original_value ,
@@ -1358,12 +1423,13 @@ async def handle_event(self, event, **kwargs):
13581423
13591424 if self .in_bl (cookie_name ) is False :
13601425 self .assigned_cookies [cookie_name ] = cookie_value
1426+ event_base = self ._event_base_url (event )
13611427 await self .emit_web_parameter (
1362- host = str (event . host ),
1428+ host = self . _event_host (event ),
13631429 param_type = "COOKIE" ,
13641430 name = cookie_name ,
13651431 original_value = cookie_value ,
1366- url = self .url_unparse ("COOKIE" , event . parsed_url ),
1432+ url = self .url_unparse ("COOKIE" , event_base ),
13671433 description = f"Set-Cookie Assigned Cookie [{ cookie_name } ]" ,
13681434 additional_params = {},
13691435 event = event ,
@@ -1393,7 +1459,7 @@ async def handle_event(self, event, **kwargs):
13931459
13941460 # Try to extract parameters from the redirect URL
13951461 if self .parameter_extraction :
1396- # Don't extract parameters from out-of-scope redirects —
1462+ # Don't extract parameters from out-of-scope redirects --
13971463 # they would inherit in-scope status from the parent event
13981464 # and cause lightfuzz to fuzz external endpoints
13991465 redirect_parsed = urlparse (redirect_location )
@@ -1410,10 +1476,10 @@ async def handle_event(self, event, **kwargs):
14101476 original_value ,
14111477 regex_name ,
14121478 additional_params ,
1413- ) in extract_params_location (header_value , event . parsed_url ):
1479+ ) in extract_params_location (header_value , self . _event_base_url ( event ) ):
14141480 if self .in_bl (parameter_name ) is False :
14151481 await self .emit_web_parameter (
1416- host = parsed_url . hostname ,
1482+ host = self . _event_host ( event ) ,
14171483 param_type = "GETPARAM" ,
14181484 name = parameter_name ,
14191485 original_value = original_value ,
0 commit comments