diff --git a/modules/sfp_tool_nuclei.py b/modules/sfp_tool_nuclei.py
index 58c12b0477..fac7aca2bb 100644
--- a/modules/sfp_tool_nuclei.py
+++ b/modules/sfp_tool_nuclei.py
@@ -155,7 +155,7 @@ def handleEvent(self, event):
args = [
exe,
"-silent",
- "-json",
+ "-jsonl",
"-concurrency",
"100",
"-retries",
@@ -189,15 +189,32 @@ def handleEvent(self, event):
if not content:
return
- try:
- for line in content.split("\n"):
- if not line:
- continue
-
+ for line in content.split("\n"):
+ if not line.strip():
+ continue
+
+ if not line.strip().startswith('{'):
+ continue
+
+ try:
data = json.loads(line)
+
+ if 'matched-at' not in data or 'info' not in data:
+ continue
+
srcevent = event
- host = data['matched-at'].split(":")[0]
- if host != eventData:
+ matched_url = data.get('matched-at', '')
+ if matched_url:
+ if matched_url.startswith('http'):
+ from urllib.parse import urlparse
+ parsed = urlparse(matched_url)
+ host = parsed.hostname
+ else:
+ host = data.get('host', eventData)
+ else:
+ host = data.get('host', eventData)
+
+ if host and host != eventData:
if self.sf.validIP(host):
srctype = "IP_ADDRESS"
else:
@@ -205,37 +222,66 @@ def handleEvent(self, event):
srcevent = SpiderFootEvent(srctype, host, self.__name__, event)
self.notifyListeners(srcevent)
- matches = re.findall(r"CVE-\d{4}-\d{4,7}", line)
- if matches:
- for cve in matches:
- etype, cvetext = self.sf.cveInfo(cve)
- e = SpiderFootEvent(
- etype, cvetext, self.__name__, srcevent
- )
- self.notifyListeners(e)
+
+ template_info = data.get('info', {})
+ cve_list = []
+
+ classification = template_info.get('classification', {})
+ if classification.get('cve-id'):
+ cve_id = classification.get('cve-id')
+ if isinstance(cve_id, list):
+ cve_list.extend(cve_id)
+ elif isinstance(cve_id, str):
+ cve_list.append(cve_id)
+
+ template_id = data.get('template-id', '')
+ if 'CVE-' in template_id:
+ matches = re.findall(r"CVE-\d{4}-\d{4,7}", template_id)
+ cve_list.extend(matches)
+
+ if cve_list:
+ for cve in cve_list:
+ if cve and cve != 'null':
+ etype, cvetext = self.sf.cveInfo(cve)
+ e = SpiderFootEvent(etype, cvetext, self.__name__, srcevent)
+ self.notifyListeners(e)
else:
- if "matcher-name" in data:
+ severity = template_info.get('severity', 'info').lower()
+
+ if severity == "info":
+ etype = "WEBSERVER_TECHNOLOGY"
+ else:
etype = "VULNERABILITY_GENERAL"
- if data['info']['severity'] == "info":
- etype = "WEBSERVER_TECHNOLOGY"
-
- datatext = f"Template: {data['info']['name']}({data['template-id']})\n"
- datatext += f"Matcher: {data['matcher-name']}\n"
- datatext += f"Matched at: {data['matched-at']}\n"
- if data['info'].get('reference'):
- datatext += f"Reference: {data['info']['reference'][0]}"
-
- evt = SpiderFootEvent(
- etype,
- datatext,
- self.__name__,
- srcevent,
- )
- self.notifyListeners(evt)
- except (KeyError, ValueError) as e:
- self.error(f"Couldn't parse the JSON output of Nuclei: {e}")
- self.error(f"Nuclei content: {content}")
- return
-
+
+ datatext = f"Template: {template_info.get('name', 'Unknown')} ({data.get('template-id', 'Unknown')})\n"
+
+ if data.get('matcher-name'):
+ datatext += f"Matcher: {data.get('matcher-name')}\n"
+
+ if data.get('matched-at'):
+ datatext += f"Matched at: {data.get('matched-at')}\n"
+
+ if severity != 'info':
+ datatext += f"Severity: {severity}\n"
+
+ if template_info.get('description'):
+ datatext += f"Description: {template_info.get('description')}\n"
+
+ references = template_info.get('reference', [])
+ if references:
+ if isinstance(references, list) and len(references) > 0:
+ datatext += f"Reference: {references[0]}"
+ elif isinstance(references, str):
+ datatext += f"Reference: {references}"
+
+ evt = SpiderFootEvent(etype, datatext, self.__name__, srcevent)
+ self.notifyListeners(evt)
+
+ except json.JSONDecodeError as e:
+ self.debug(f"Skipping non-JSON line: {line}")
+ continue
+ except KeyError as e:
+ self.debug(f"Missing expected key in Nuclei output: {e}")
+ continue
# End of sfp_tool_nuclei class
diff --git a/modules/sfp_tool_wafw00f.py b/modules/sfp_tool_wafw00f.py
index 8e508af51d..874b81a79f 100644
--- a/modules/sfp_tool_wafw00f.py
+++ b/modules/sfp_tool_wafw00f.py
@@ -13,7 +13,8 @@
import json
import os.path
-from subprocess import PIPE, Popen, TimeoutExpired
+import tempfile
+from subprocess import PIPE, Popen
from spiderfoot import SpiderFootEvent, SpiderFootPlugin, SpiderFootHelpers
@@ -97,23 +98,20 @@ def handleEvent(self, event):
self.error("Invalid input, refusing to run.")
return
+ output_file = tempfile.mktemp(suffix=".json")
+
args = [
self.opts['python_path'],
exe,
'-a',
- '-o-',
- '-f',
- 'json',
+ '-o',
+ output_file,
url
]
+
try:
p = Popen(args, stdout=PIPE, stderr=PIPE)
- stdout, stderr = p.communicate(input=None, timeout=300)
- except TimeoutExpired:
- p.kill()
- stdout, stderr = p.communicate()
- self.debug(f"Timed out waiting for wafw00f to finish on {eventData}")
- return
+ stdout, stderr = p.communicate(input=None)
except Exception as e:
self.error(f"Unable to run wafw00f: {e}")
return
@@ -122,15 +120,15 @@ def handleEvent(self, event):
self.error(f"Unable to read wafw00f output\nstderr: {stderr}\nstdout: {stdout}")
return
- if not stdout:
- self.debug(f"wafw00f returned no output for {eventData}")
- return
-
try:
- result_json = json.loads(stdout)
+ with open(output_file, "r") as f:
+ result_json = json.load(f)
except Exception as e:
- self.error(f"Could not parse wafw00f output as JSON: {e}\nstdout: {stdout}")
+ self.error(f"Could not parse wafw00f output as JSON from file: {e}")
return
+ finally:
+ if os.path.exists(output_file):
+ os.remove(output_file)
if not result_json:
self.debug(f"wafw00f returned no output for {eventData}")
diff --git a/modules/sfp_tool_whatweb.py b/modules/sfp_tool_whatweb.py
index a226c095f2..55d2c1e390 100644
--- a/modules/sfp_tool_whatweb.py
+++ b/modules/sfp_tool_whatweb.py
@@ -8,12 +8,12 @@
#
# Created: 2019-08-31
# Copyright: (c) bcoles 2019
-# Licence: MIT
+# Licence: GPL
# -------------------------------------------------------------------------------
import json
import os.path
-from subprocess import PIPE, Popen, TimeoutExpired
+from subprocess import PIPE, Popen
from spiderfoot import SpiderFootEvent, SpiderFootPlugin, SpiderFootHelpers
@@ -121,6 +121,7 @@ def handleEvent(self, event):
self.opts['ruby_path'],
exe,
"--quiet",
+ "--no-errors",
"--aggression=" + str(aggression),
"--log-json=/dev/stdout",
"--user-agent=Mozilla/5.0",
@@ -128,33 +129,46 @@ def handleEvent(self, event):
eventData
]
try:
- p = Popen(args, stdout=PIPE, stderr=PIPE, timeout=300)
+ p = Popen(args, stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate(input=None)
- except TimeoutExpired:
- p.kill()
- stdout, stderr = p.communicate()
- self.debug(f"Timed out waiting for WhatWeb to finish against {eventData}")
- return
except Exception as e:
self.error(f"Unable to run WhatWeb: {e}")
return
if p.returncode != 0:
self.error("Unable to read WhatWeb output.")
- self.debug("Error running WhatWeb: " + stderr + ", " + stdout)
+ self.debug("Error running WhatWeb: " + stderr.decode('utf-8', errors='ignore') + ", " + stdout.decode('utf-8', errors='ignore'))
return
if not stdout:
self.debug(f"WhatWeb returned no output for {eventData}")
return
+ # Parse JSON output - WhatWeb returns one JSON object per line
+ result_json = []
try:
- result_json = json.loads(stdout)
+ stdout_str = stdout.decode('utf-8', errors='ignore').strip()
+
+ # Debug: mostrar o que foi retornado
+ self.debug(f"WhatWeb output (first 500 chars): {stdout_str[:500]}")
+
+ # WhatWeb retorna mĂșltiplas linhas de JSON, uma por linha
+ for line in stdout_str.split('\n'):
+ line = line.strip()
+ if not line:
+ continue
+ try:
+ result_json.append(json.loads(line))
+ except json.JSONDecodeError as e:
+ self.debug(f"Failed to parse JSON line: {line[:100]} - Error: {e}")
+ continue
+
except Exception as e:
self.error(f"Couldn't parse the JSON output of WhatWeb: {e}")
return
if len(result_json) == 0:
+ self.debug(f"No valid JSON results found for {eventData}")
return
blacklist = [
@@ -173,16 +187,20 @@ def handleEvent(self, event):
continue
if plugin_matches.get('HTTPServer'):
- for w in plugin_matches.get('HTTPServer').get('string'):
- evt = SpiderFootEvent('WEBSERVER_BANNER', w, self.__name__, event)
- self.notifyListeners(evt)
- found = True
+ http_server_data = plugin_matches.get('HTTPServer')
+ if isinstance(http_server_data, dict) and http_server_data.get('string'):
+ for w in http_server_data.get('string'):
+ evt = SpiderFootEvent('WEBSERVER_BANNER', w, self.__name__, event)
+ self.notifyListeners(evt)
+ found = True
if plugin_matches.get('X-Powered-By'):
- for w in plugin_matches.get('X-Powered-By').get('string'):
- evt = SpiderFootEvent('WEBSERVER_TECHNOLOGY', w, self.__name__, event)
- self.notifyListeners(evt)
- found = True
+ x_powered_data = plugin_matches.get('X-Powered-By')
+ if isinstance(x_powered_data, dict) and x_powered_data.get('string'):
+ for w in x_powered_data.get('string'):
+ evt = SpiderFootEvent('WEBSERVER_TECHNOLOGY', w, self.__name__, event)
+ self.notifyListeners(evt)
+ found = True
for plugin in plugin_matches:
if plugin in blacklist:
@@ -195,4 +213,4 @@ def handleEvent(self, event):
evt = SpiderFootEvent('RAW_RIR_DATA', str(result_json), self.__name__, event)
self.notifyListeners(evt)
-# End of sfp_tool_whatweb class
+# End of sfp_tool_whatweb class.