Run Nuclei vulnerability scanner.
| 12 | |
| 13 | class NucleiScanner(WebScannerModule): |
| 14 | """Run Nuclei vulnerability scanner.""" |
| 15 | |
| 16 | name = "nuclei" |
| 17 | description = "Run Nuclei templates for vulnerability detection" |
| 18 | |
| 19 | SEVERITY_MAP = { |
| 20 | "critical": Severity.CRITICAL, |
| 21 | "high": Severity.HIGH, |
| 22 | "medium": Severity.MEDIUM, |
| 23 | "low": Severity.LOW, |
| 24 | "info": Severity.INFO, |
| 25 | } |
| 26 | |
| 27 | def __init__( |
| 28 | self, |
| 29 | templates: list[str] | None = None, |
| 30 | severity: list[str] | None = None, |
| 31 | tags: list[str] | None = None, |
| 32 | ): |
| 33 | super().__init__() |
| 34 | self.templates = templates |
| 35 | self.severity_filter = severity or ["critical", "high", "medium"] |
| 36 | self.tags = tags |
| 37 | self.nuclei_available = shutil.which("nuclei") is not None |
| 38 | |
| 39 | async def run(self, target: Target) -> ScanResult: |
| 40 | """Run Nuclei scan on target.""" |
| 41 | result = self.create_result(target) |
| 42 | |
| 43 | if not self.nuclei_available: |
| 44 | result.errors.append("Nuclei not found. Install from https://github.com/projectdiscovery/nuclei") |
| 45 | result.success = False |
| 46 | result.complete() |
| 47 | return result |
| 48 | |
| 49 | url = self._build_url(target) |
| 50 | self.logger.info(f"Starting Nuclei scan on {url}") |
| 51 | |
| 52 | with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f: |
| 53 | output_file = f.name |
| 54 | |
| 55 | try: |
| 56 | cmd = [ |
| 57 | "nuclei", |
| 58 | "-u", url, |
| 59 | "-json-export", output_file, |
| 60 | "-silent", |
| 61 | ] |
| 62 | |
| 63 | # Add severity filter |
| 64 | if self.severity_filter: |
| 65 | cmd.extend(["-severity", ",".join(self.severity_filter)]) |
| 66 | |
| 67 | # Add specific templates |
| 68 | if self.templates: |
| 69 | for t in self.templates: |
| 70 | cmd.extend(["-t", t]) |
| 71 |