diff --git a/secator/config.py b/secator/config.py index bea53e099..9de3a7e6b 100644 --- a/secator/config.py +++ b/secator/config.py @@ -168,6 +168,8 @@ class Wordlists(StrictModel): 'combined_subdomains': 'https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/DNS/combined_subdomains.txt', # noqa: E501 'directory_list_small': 'https://gist.githubusercontent.com/sl4v/c087e36164e74233514b/raw/c51a811c70bbdd87f4725521420cc30e7232b36d/directory-list-2.3-small.txt', # noqa: E501 'burp-parameter-names': 'https://raw.githubusercontent.com/danielmiessler/SecLists/refs/heads/master/Discovery/Web-Content/burp-parameter-names.txt', # noqa: E501 + # Assetnote's HTTP Archive API routes dataset (same data kiterunner's -A apiroutes uses), regenerated monthly + 'apiroutes': 'https://wordlists-cdn.assetnote.io/data/automated/httparchive_apiroutes_2026_06_27.txt', # noqa: E501 } lists: Dict[str, List[str]] = {} diff --git a/secator/configs/scans/api.yaml b/secator/configs/scans/api.yaml new file mode 100644 index 000000000..d538a04d0 --- /dev/null +++ b/secator/configs/scans/api.yaml @@ -0,0 +1,24 @@ +type: scan +name: api +description: API security scan +long_description: | + End-to-end security assessment of a web API. + Discovers API endpoints (api_discover), then fuzzes their parameters (url_params_fuzz) and scans the + discovered surface for common web vulnerabilities and exposed secrets (url_vuln). Composes existing + workflows rather than duplicating their logic, so each stage stays independently reusable. +profile: default +tags: [http, api, fuzz, vuln, secrets] +input_types: + - url +workflows: + api_discover: + url_params_fuzz: + targets_: + - type: url + field: url + condition: url.verified + url_vuln: + targets_: + - type: url + field: url + condition: url.verified diff --git a/secator/configs/workflows/api_discover.yaml b/secator/configs/workflows/api_discover.yaml new file mode 100644 index 000000000..9d84d122f --- /dev/null +++ b/secator/configs/workflows/api_discover.yaml @@ -0,0 +1,86 @@ +type: workflow +name: api_discover +alias: apid +description: API endpoint discovery +long_description: | + Discovers API endpoints on a target web application. + Combines active crawling (katana, which parses JavaScript to surface referenced endpoints) with optional + brute-forcing of API routes (ffuf, using Assetnote's HTTP Archive apiroutes wordlist — the same real-world + route dataset kiterunner relied on). Discovered endpoints are probed with httpx to verify they are live and + fingerprint their technologies. The apiroutes wordlist also covers exposed API specification paths + (openapi/swagger); when one is found, the --spec option hands it off to nuclei, which parses the spec + (input-mode openapi) and DAST-fuzzes every documented endpoint with the correct method and parameters — + recovering the contextual testing kiterunner used to provide, using a maintained tool already in secator. +tags: [http, api, crawl, fuzz] +input_types: + - url + +default_options: + follow_redirect: true + +options: + waf: + is_flag: True + help: Fingerprint WAF (wafw00f) + default: False + + fuzz: + is_flag: True + help: Brute-force API routes with the apiroutes wordlist (ffuf) + default: False + short: fuzz + + spec: + is_flag: True + help: Hand off discovered OpenAPI/Swagger specs to nuclei for endpoint fuzzing (combine with --fuzz to find them) + default: False + short: spec + +tasks: + katana: + description: Crawl for API endpoints + + urlparser: + description: Extract origin URL + include: [url_root] + targets_: + - type: target + field: name + if: opts.fuzz + + ffuf: + description: Brute-force API routes + wordlist: apiroutes + auto_calibration: true + if: opts.fuzz + targets_: + # Fuzz from the origin (url_root strips path + trailing slash) with /FUZZ so ffuf's auto-calibration + # probes hit the right host with a clean path. apiroutes entries are absolute (/api/x), so real routes + # come out as host//api — ffuf collapses the redundant slash in its output URLs. + - type: tag + field: '{value}/FUZZ' + condition: item.name == 'url_root' + + wafw00f: + description: Fingerprint WAF + if: opts.waf + + httpx: + description: Probe discovered API endpoints + tech_detect: True + filter_duplicates: True + targets_: + - target.name + - type: url + field: url + condition: not url.verified + + nuclei: + description: Fuzz endpoints from discovered API specs + input_mode: openapi + dast: True + targets_: + - type: url + field: url + condition: "'openapi' in url.url or 'swagger' in url.url or 'api-docs' in url.url" + if: opts.spec diff --git a/secator/tasks/ffuf.py b/secator/tasks/ffuf.py index 89f83d51d..244d47218 100644 --- a/secator/tasks/ffuf.py +++ b/secator/tasks/ffuf.py @@ -1,3 +1,5 @@ +import re + from secator.decorators import task # fmt: off @@ -125,8 +127,11 @@ def on_json_loaded(self, item): has_status_code_3xx = str(status_code).startswith('3') is_redirect = (self.get_opt_value('follow_redirect') and 'redirectlocation' in item) or has_status_code_3xx auto_calibration = self.get_opt_value('auto_calibration') + # Collapse redundant slashes in the path (e.g. host//api from a path-list wordlist whose entries + # start with '/'), while preserving the scheme's '://'. + url = re.sub(r'(?