**Nuclei is a modern, high-performance vulnerability scanner that leverages simple YAML-based templates. It empowers you to design custom vulnerability detection scenarios that mimic real-world conditions, leading to zero false positives.**
- Simple YAML format for creating and customizing vulnerability templates.
- Contributed by thousands of security professionals to tackle trending vulnerabilities.
- Reduce false positives by simulating real-world steps to verify a vulnerability.
- Ultra-fast parallel scan processing and request clustering.
- Integrate into CI/CD pipelines for vulnerability detection and regression testing.
- Supports multiple protocols like TCP, DNS, HTTP, SSL, WHOIS, JavaScript, Code and more.
- Integrate with Jira, Splunk, GitHub, Elastic, GitLab.
## Table of Contents
- [**`Get Started`**](#get-started)
- [_`1. Nuclei CLI`_](#1-nuclei-cli)
- [_`2. Pro and Enterprise Editions`_](#2-pro-and-enterprise-editions)
- [**`Documentation`**](#documentation)
- [_`Command Line Flags`_](#command-line-flags)
- [_`Single target scan`_](#single-target-scan)
- [_`Scanning multiple targets`_](#scanning-multiple-targets)
- [_`Network scan`_](#network-scan)
- [_`Scanning with your custom template`_](#scanning-with-your-custom-template)
- [_`Connect Nuclei to ProjectDiscovery_`_](#connect-nuclei-to-projectdiscovery)
- [**`Nuclei Templates, Community and Rewards`**](#nuclei-templates-community-and-rewards-) 💎
- [**`Our Mission`**](#our-mission)
- [**`Contributors`**](#contributors-heart) ❤
- [**`License`**](#license)
## Get Started
### **1. Nuclei CLI**
_Install Nuclei on your machine. Get started by following the installation guide [**`here`**](https://docs.projectdiscovery.io/tools/nuclei/install?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme). Additionally, We provide [**`a free cloud tier`**](https://cloud.projectdiscovery.io/sign-up) and comes with a generous monthly free limits:_
- Store and visualize your vulnerability findings
- Write and manage your nuclei templates
- Access latest nuclei templates
- Discover and store your targets
> [!Important]
> |**This project is in active development**. Expect breaking changes with releases. Review the release changelog before updating.|
> |:--------------------------------|
> | This project is primarily built to be used as a standalone CLI tool. **Running nuclei as a service may pose security risks.** It's recommended to use with caution and additional security measures. |
### **2. Pro and Enterprise Editions**
_For security teams and enterprises, we provide a cloud-hosted service built on top of Nuclei OSS, fine-tuned to help you continuously run vulnerability scans at scale with your team and existing workflows:_
- 50x faster scans
- Large scale scanning with high accuracy
- Integrations with cloud services (AWS, GCP, Azure, Cloudflare, Fastly, Terraform, Kubernetes)
- Jira, Slack, Linear, APIs and Webhooks
- Executive and compliance reporting
- Plus: Real-time scanning, SAML SSO, SOC 2 compliant platform (with EU and US hosting options), shared team workspaces, and more
- We're constantly [**`adding new features`**](https://feedback.projectdiscovery.io/changelog)!
- **Ideal for:** Pentesters, security teams, and enterprises
[**`Sign up to Pro`**](https://projectdiscovery.io/pricing?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme) or [**`Talk to our team`**](https://projectdiscovery.io/request-demo?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme) if you have large organization and complex requirements.
## Documentation
Browse the full Nuclei [**`documentation here`**](https://docs.projectdiscovery.io/tools/nuclei/running). If you’re new to Nuclei, check out our [**`foundational YouTube series`**](https://www.youtube.com/playlist?list=PLZRbR9aMzTTpItEdeNSulo8bYsvil80Rl).
### Installation
`nuclei` requires **go >= 1.24.2** to install successfully. Run the following command to get the repo:
```sh
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
```
To learn more about installing nuclei, see `https://docs.projectdiscovery.io/tools/nuclei/install`.
### Command Line Flags
To display all the flags for the tool:
```sh
nuclei -h
```
Expand full help flags
```yaml
Nuclei is a fast, template based vulnerability scanner focusing
on extensive configurability, massive extensibility and ease of use.
Usage:
./nuclei [flags]
Flags:
TARGET:
-u, -target string[] target URLs/hosts to scan
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
-eh, -exclude-hosts string[] hosts to exclude to scan from the input list (ip, cidr, hostname)
-resume string resume scan from and save to specified file (clustering will be disabled)
-sa, -scan-all-ips scan all the IP's associated with dns record
-iv, -ip-version string[] IP version to scan of hostname (4,6) - (default 4)
TARGET-FORMAT:
-im, -input-mode string mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default "list")
-ro, -required-only use only required fields in input format when generating requests
-sfv, -skip-format-validation skip format validation (like missing vars) when parsing input file
TEMPLATES:
-nt, -new-templates run only new templates added in latest nuclei-templates release
-ntv, -new-templates-version string[] run new templates added in specific version
-as, -automatic-scan automatic web scan using wappalyzer technology detection to tags mapping
-t, -templates string[] list of template or template directory to run (comma-separated, file)
-turl, -template-url string[] template url or list containing template urls to run (comma-separated, file)
-ai, -prompt string generate and run template using ai prompt
-w, -workflows string[] list of workflow or workflow directory to run (comma-separated, file)
-wurl, -workflow-url string[] workflow url or list containing workflow urls to run (comma-separated, file)
-validate validate the passed templates to nuclei
-nss, -no-strict-syntax disable strict syntax check on templates
-td, -template-display displays the templates content
-tl list all templates matching current filters
-tgl list all available tags
-sign signs the templates with the private key defined in NUCLEI_SIGNATURE_PRIVATE_KEY env variable
-code enable loading code protocol-based templates
-dut, -disable-unsigned-templates disable running unsigned templates or templates with mismatched signature
-esc, -enable-self-contained enable loading self-contained templates
-egm, -enable-global-matchers enable loading global matchers templates
-file enable loading file templates
FILTERING:
-a, -author string[] templates to run based on authors (comma-separated, file)
-tags string[] templates to run based on tags (comma-separated, file)
-etags, -exclude-tags string[] templates to exclude based on tags (comma-separated, file)
-itags, -include-tags string[] tags to be executed even if they are excluded either by default or configuration
-id, -template-id string[] templates to run based on template ids (comma-separated, file, allow-wildcard)
-eid, -exclude-id string[] templates to exclude based on template ids (comma-separated, file)
-it, -include-templates string[] path to template file or directory to be executed even if they are excluded either by default or configuration
-et, -exclude-templates string[] path to template file or directory to exclude (comma-separated, file)
-em, -exclude-matchers string[] template matchers to exclude in result
-s, -severity value[] templates to run based on severity. Possible values: info, low, medium, high, critical, unknown
-es, -exclude-severity value[] templates to exclude based on severity. Possible values: info, low, medium, high, critical, unknown
-pt, -type value[] templates to run based on protocol type. Possible values: dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code, javascript
-ept, -exclude-type value[] templates to exclude based on protocol type. Possible values: dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code, javascript
-tc, -template-condition string[] templates to run based on expression condition
OUTPUT:
-o, -output string output file to write found issues/vulnerabilities
-sresp, -store-resp store all request/response passed through nuclei to output directory
-srd, -store-resp-dir string store all request/response passed through nuclei to custom directory (default "output")
-silent display findings only
-nc, -no-color disable output content coloring (ANSI escape codes)
-j, -jsonl write output in JSONL(ines) format
-irr, -include-rr -omit-raw include request/response pairs in the JSON, JSONL, and Markdown outputs (for findings only) [DEPRECATED use -omit-raw] (default true)
-or, -omit-raw omit request/response pairs in the JSON, JSONL, and Markdown outputs (for findings only)
-ot, -omit-template omit encoded template in the JSON, JSONL output
-nm, -no-meta disable printing result metadata in cli output
-ts, -timestamp enables printing timestamp in cli output
-rdb, -report-db string nuclei reporting database (always use this to persist report data)
-ms, -matcher-status display match failure status
-me, -markdown-export string directory to export results in markdown format
-se, -sarif-export string file to export results in SARIF format
-je, -json-export string file to export results in JSON format
-jle, -jsonl-export string file to export results in JSONL(ine) format
-rd, -redact string[] redact given list of keys from query parameter, request header and body
CONFIGURATIONS:
-config string path to the nuclei configuration file
-tp, -profile string template profile config file to run
-tpl, -profile-list list community template profiles
-fr, -follow-redirects enable following redirects for http templates
-fhr, -follow-host-redirects follow redirects on the same host
-mr, -max-redirects int max number of redirects to follow for http templates (default 10)
-dr, -disable-redirects disable redirects for http templates
-rc, -report-config string nuclei reporting module configuration file
-H, -header string[] custom header/cookie to include in all http request in header:value format (cli, file)
-V, -var value custom vars in key=value format
-r, -resolvers string file containing resolver list for nuclei
-sr, -system-resolvers use system DNS resolving as error fallback
-dc, -disable-clustering disable clustering of requests
-passive enable passive HTTP response processing mode
-fh2, -force-http2 force http2 connection on requests
-ev, -env-vars enable environment variables to be used in template
-cc, -client-cert string client certificate file (PEM-encoded) used for authenticating against scanned hosts
-ck, -client-key string client key file (PEM-encoded) used for authenticating against scanned hosts
-ca, -client-ca string client certificate authority file (PEM-encoded) used for authenticating against scanned hosts
-sml, -show-match-line show match lines for file templates, works with extractors only
-ztls use ztls library with autofallback to standard one for tls13 [Deprecated] autofallback to ztls is enabled by default
-sni string tls sni hostname to use (default: input domain name)
-dka, -dialer-keep-alive value keep-alive duration for network requests.
-lfa, -allow-local-file-access allows file (payload) access anywhere on the system
-lna, -restrict-local-network-access blocks connections to the local / private network
-i, -interface string network interface to use for network scan
-at, -attack-type string type of payload combinations to perform (batteringram,pitchfork,clusterbomb)
-sip, -source-ip string source ip address to use for network scan
-rsr, -response-size-read int max response size to read in bytes
-rss, -response-size-save int max response size to read in bytes (default 1048576)
-reset reset removes all nuclei configuration and data files (including nuclei-templates)
-tlsi, -tls-impersonate enable experimental client hello (ja3) tls randomization
-hae, -http-api-endpoint string experimental http api endpoint
INTERACTSH:
-iserver, -interactsh-server string interactsh server url for self-hosted instance (default: oast.pro,oast.live,oast.site,oast.online,oast.fun,oast.me)
-itoken, -interactsh-token string authentication token for self-hosted interactsh server
-interactions-cache-size int number of requests to keep in the interactions cache (default 5000)
-interactions-eviction int number of seconds to wait before evicting requests from cache (default 60)
-interactions-poll-duration int number of seconds to wait before each interaction poll request (default 5)
-interactions-cooldown-period int extra time for interaction polling before exiting (default 5)
-ni, -no-interactsh disable interactsh server for OAST testing, exclude OAST based templates
FUZZING:
-ft, -fuzzing-type string overrides fuzzing type set in template (replace, prefix, postfix, infix)
-fm, -fuzzing-mode string overrides fuzzing mode set in template (multiple, single)
-fuzz enable loading fuzzing templates (Deprecated: use -dast instead)
-dast enable / run dast (fuzz) nuclei templates
-dts, -dast-server enable dast server mode (live fuzzing)
-dtr, -dast-report write dast scan report to file
-dtst, -dast-server-token string dast server token (optional)
-dtsa, -dast-server-address string dast server address (default "localhost:9055")
-dfp, -display-fuzz-points display fuzz points in the output for debugging
-fuzz-param-frequency int frequency of uninteresting parameters for fuzzing before skipping (default 10)
-fa, -fuzz-aggression string fuzzing aggression level controls payload count for fuzz (low, medium, high) (default "low")
-cs, -fuzz-scope string[] in scope url regex to be followed by fuzzer
-cos, -fuzz-out-scope string[] out of scope url regex to be excluded by fuzzer
UNCOVER:
-uc, -uncover enable uncover engine
-uq, -uncover-query string[] uncover search query
-ue, -uncover-engine string[] uncover search engine (shodan,censys,fofa,shodan-idb,quake,hunter,zoomeye,netlas,criminalip,publicwww,hunterhow,google) (default shodan)
-uf, -uncover-field string uncover fields to return (ip,port,host) (default "ip:port")
-ul, -uncover-limit int uncover results to return (default 100)
-ur, -uncover-ratelimit int override ratelimit of engines with unknown ratelimit (default 60 req/min) (default 60)
RATE-LIMIT:
-rl, -rate-limit int maximum number of requests to send per second (default 150)
-rld, -rate-limit-duration value maximum number of requests to send per second (default 1s)
-rlm, -rate-limit-minute int maximum number of requests to send per minute (DEPRECATED)
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
-hbs, -headless-bulk-size int maximum number of headless hosts to be analyzed in parallel per template (default 10)
-headc, -headless-concurrency int maximum number of headless templates to be executed in parallel (default 10)
-jsc, -js-concurrency int maximum number of javascript runtimes to be executed in parallel (default 120)
-pc, -payload-concurrency int max payload concurrency for each template (default 25)
-prc, -probe-concurrency int http probe concurrency with httpx (default 50)
-tlc, -template-loading-concurrency int maximum number of concurrent template loading operations (default 50)
OPTIMIZATIONS:
-timeout int time to wait in seconds before timeout (default 10)
-retries int number of times to retry a failed request (default 1)
-ldp, -leave-default-ports leave default HTTP/HTTPS ports (eg. host:80,host:443)
-mhe, -max-host-error int max errors for a host before skipping from scan (default 30)
-te, -track-error string[] adds given error to max-host-error watchlist (standard, file)
-nmhe, -no-mhe disable skipping host from scan based on errors
-project use a project folder to avoid sending same request multiple times
-project-path string set a specific project path (default "/tmp")
-spm, -stop-at-first-match stop processing HTTP requests after the first match (may break template/workflow logic)
-stream stream mode - start elaborating without sorting the input
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
-irt, -input-read-timeout value timeout on input read (default 3m0s)
-nh, -no-httpx disable httpx probing for non-url input
-no-stdin disable stdin processing
HEADLESS:
-headless enable templates that require headless browser support (root user on Linux will disable sandbox)
-page-timeout int seconds to wait for each page in headless mode (default 20)
-sb, -show-browser show the browser on the screen when running templates with headless mode
-ho, -headless-options string[] start headless chrome with additional options
-sc, -system-chrome use local installed Chrome browser instead of nuclei installed
-cdpe, -cdp-endpoint string use remote browser via Chrome DevTools Protocol (CDP) endpoint
-lha, -list-headless-action list available headless actions
DEBUG:
-debug show all requests and responses
-dreq, -debug-req show all sent requests
-dresp, -debug-resp show all received responses
-p, -proxy string[] list of http/socks5 proxy to use (comma separated or file input)
-pi, -proxy-internal proxy all internal requests
-ldf, -list-dsl-function list all supported DSL function signatures
-tlog, -trace-log string file to write sent requests trace log
-elog, -error-log string file to write sent requests error log
-version show nuclei version
-hm, -hang-monitor enable nuclei hang monitoring
-v, -verbose show verbose output
-profile-mem string generate memory (heap) profile & trace files
-vv display templates loaded for scan
-svd, -show-var-dump show variables dump for debugging
-vdl, -var-dump-limit int limit the number of characters displayed in var dump (default 255)
-ep, -enable-pprof enable pprof debugging server
-tv, -templates-version shows the version of the installed nuclei-templates
-hc, -health-check run diagnostic check up
UPDATE:
-up, -update update nuclei engine to the latest released version
-ut, -update-templates update nuclei-templates to latest released version
-ud, -update-template-dir string custom directory to install / update nuclei-templates
-duc, -disable-update-check disable automatic nuclei/templates update check
STATISTICS:
-stats display statistics about the running scan
-sj, -stats-json display statistics in JSONL(ines) format
-si, -stats-interval int number of seconds to wait between showing a statistics update (default 5)
-mp, -metrics-port int port to expose nuclei metrics on (default 9092)
-hps, -http-stats enable http status capturing (experimental)
CLOUD:
-auth configure projectdiscovery cloud (pdcp) api key (default true)
-tid, -team-id string upload scan results to given team id (optional) (default "none")
-cup, -cloud-upload upload scan results to pdcp dashboard [DEPRECATED use -dashboard]
-sid, -scan-id string upload scan results to existing scan id (optional)
-sname, -scan-name string scan name to set (optional)
-pd, -dashboard upload / view nuclei results in projectdiscovery cloud (pdcp) UI dashboard
-pdu, -dashboard-upload string upload / view nuclei results file (jsonl) in projectdiscovery cloud (pdcp) UI dashboard
AUTHENTICATION:
-sf, -secret-file string[] path to config file containing secrets for nuclei authenticated scan
-ps, -prefetch-secrets prefetch secrets from the secrets file
# NOTE: Headers in secrets files preserve exact casing (useful for case-sensitive APIs)
EXAMPLES:
Run nuclei on single host:
$ nuclei -target example.com
Run nuclei with specific template directories:
$ nuclei -target example.com -t http/cves/ -t ssl
Run nuclei against a list of hosts:
$ nuclei -list hosts.txt
Run nuclei with a JSON output:
$ nuclei -target example.com -json-export output.json
Run nuclei with sorted Markdown outputs (with environment variables):
$ MARKDOWN_EXPORT_SORT_MODE=template nuclei -target example.com -markdown-export nuclei_report/
Additional documentation is available at: https://docs.projectdiscovery.io/getting-started/running
```
Additional documentation is available at: [**`docs.projectdiscovery.io/getting-started/running`**](https://docs.projectdiscovery.io/getting-started/running?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme)
### Single target scan
To perform a quick scan on web-application:
```sh
nuclei -target https://example.com
```
### Scanning multiple targets
Nuclei can handle bulk scanning by providing a list of targets. You can use a file containing multiple URLs.
```sh
nuclei -list urls.txt
```
### Network scan
This will scan the entire subnet for network-related issues, such as open ports or misconfigured services.
```sh
nuclei -target 192.168.1.0/24
```
### Scanning with your custom template
To write and use your own template, create a `.yaml` file with specific rules, then use it as follows.
```sh
nuclei -u https://example.com -t /path/to/your-template.yaml
```
### Connect Nuclei to ProjectDiscovery
You can run the scans on your machine and upload the results to the cloud platform for further analysis and remediation.
```sh
nuclei -target https://example.com -dashboard
```
> [!NOTE]
> This feature is absolutely free and does not require any subscription. For a detailed guide, refer to the [**`documentation`**](https://docs.projectdiscovery.io/cloud/scanning/nuclei-scan?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme).
## Nuclei Templates, Community and Rewards 💎
[**Nuclei templates**](https://github.com/projectdiscovery/nuclei-templates) are based on the concepts of YAML based template files that define how the requests will be sent and processed. This allows easy extensibility capabilities to nuclei. The templates are written in YAML which specifies a simple human-readable format to quickly define the execution process.
**Try it online with our free AI powered Nuclei Templates Editor by** [**`clicking here`**](https://cloud.projectdiscovery.io/templates).
Nuclei Templates offer a streamlined way to identify and communicate vulnerabilities, combining essential details like severity ratings and detection methods. This open-source, community-developed tool accelerates threat response and is widely recognized in the cybersecurity world. Nuclei templates are actively contributed by thousands of security researchers globally. We run two programs for our contributors: [**`Pioneers`**](https://projectdiscovery.io/pioneers) and [**`💎 bounties`**](https://github.com/projectdiscovery/nuclei-templates/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22%F0%9F%92%8E%20Bounty%22).
## Our Mission
Traditional vulnerability scanners were built decades ago. They are closed-source, incredibly slow, and vendor-driven. Today's attackers are mass exploiting newly released CVEs across the internet within days, unlike the years it used to take. This shift requires a completely different approach to tackling trending exploits on the internet.
We built Nuclei to solve this challenge. We made the entire scanning engine framework open and customizable—allowing the global security community to collaborate and tackle the trending attack vectors and vulnerabilities on the internet. Nuclei is now used and contributed by Fortune 500 enterprises, government agencies, universities.
You can participate by contributing to our code, [**`templates library`**](https://github.com/projectdiscovery/nuclei-templates), or [**`joining our team`**](https://projectdiscovery.io/).
## Contributors :heart:
Thanks to all the amazing [**`community contributors for sending PRs`**](https://github.com/projectdiscovery/nuclei/graphs/contributors) and keeping this project updated. :heart:
**`nuclei`** is distributed under [**MIT License**](https://github.com/projectdiscovery/nuclei/blob/main/LICENSE.md)
---
Nuclei se utiliza para enviar peticiones a múltiples objetivos basándose en una plantilla, lo que resulta en cero falsos positivos y proporciona un escaneo rápido en un gran número de hosts. Nuclei ofrece escaneos para una variedad de protocolos, incluyendo TCP, DNS, HTTP, SSL, File, Whois, Websocket, Headless, Code, etc. Con plantillas potentes y flexibles, Nuclei puede utilizarse para modelar todo tipo de comprobaciones de seguridad.
Tenemos un [repositorio dedicado](https://github.com/projectdiscovery/nuclei-templates) que alberga varios tipos de plantillas de vulnerabilidades, contribuidas por **más de 300** investigadores y ingenieros de seguridad.
## Cómo funciona
| :exclamation: **Descargo de responsabilidad** |
|---------------------------------|
| **Este proyecto está en desarrollo activo**. Es de esperar que se produzcan cambios importantes con las nuevas versiones. Consulte el registro de cambios de la versión antes de actualizar. |
| Este proyecto fue principalmente desarrollado para ser utilizado como una herramienta CLI independiente. **Ejecutar nuclei como un servicio puede suponer riesgos de seguridad.** Se recomienda utilizarlo con precaución y tomar medidas de seguridad adicionales. |
# Instalación de Nuclei
Nuclei requiere **go1.24.2** para instalarse correctamente. Ejecute el siguiente comando para instalar la última versión -
```sh
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
```
Brew
```sh
brew install nuclei
```
Docker
```sh
docker pull projectdiscovery/nuclei:latest
```
**Más métodos de instalación [pueden encontrarse aquí](https://docs.projectdiscovery.io/tools/nuclei/install).**
### Plantillas de Nuclei
Nuclei cuenta con soporte incorporado para la descarga/actualización automática de plantillas desde la versión [v2.5.2](https://github.com/projectdiscovery/nuclei/releases/tag/v2.5.2) en adelante. El proyecto [**Nuclei-Templates**](https://github.com/projectdiscovery/nuclei-templates) proporciona una lista de plantillas listas para usar, aportadas por la comunidad, y que se actualizan constantemente.
También puedes utilizar la bandera `update-templates` para actualizar las plantillas de Nuclei en cualquier momento; puedes escribir tus propias pruebas para tu flujo de trabajo y necesidades individuales siguiendo la [guía de plantillas](https://docs.projectdiscovery.io/templates/) de Nuclei.
La sintaxis de referencia YAML DSL está disponible [aquí](SYNTAX-REFERENCE.md).
### Uso
```sh
nuclei -h
```
Esto mostrará ayuda sobre la herramienta. Aquí están todas las opciones que soporta.
```console
Nuclei es un escáner de vulnerabilidades rápido y basado en plantillas
que se centra en su amplia configurabilidad, extensibilidad y facilidad de uso.
Usage:
./nuclei [flags]
Flags:
TARGET:
-u, -target string[] URLs/hosts a escanear
-l, -list string ruta al archivo que contiene la lista de URLs/hosts a escanear (uno por línea)
-eh, -exclude-hosts string[] hosts a excluir para escanear de la lista de entrada (ip, cidr, hostname)
-resume string reanudar el escaneo desde y guardar en el archivo especificado (la clusterización quedará inhabilitada)
-sa, -scan-all-ips escanear todas las IP asociadas al registro dns
-iv, -ip-version string[] versión IP a escanear del nombre de host (4,6) - (por defecto 4)
TARGET-FORMAT:
-im, -input-mode string modo del archivo de entrada (list, burp, jsonl, yaml, openapi, swagger) (por defecto "list")
-ro, -required-only utilizar solo campos requeridos en el formato de entrada al generar peticiones
-sfv, -skip-format-validation saltar la validación de formato (como variables faltantes) al procesar el archivo de entrada
TEMPLATES:
-nt, -new-templates ejecutar sólo las nuevas plantillas añadidas en la última versión de nuclei-templates
-ntv, -new-templates-version string[] ejecutar las nuevas plantillas añadidas en la versión especificada
-as, -automatic-scan escaneo web automático utilizando la detección de tecnología de wappalyzer para mapeo de etiquetas
-t, -templates string[] lista de plantillas o directorio de plantillas a ejecutar (separadas por comas, file)
-turl, -template-url string[] url de plantilla o lista que contiene urls de plantillas a ejecutar (separadas por comas, file)
-w, -workflows string[] lista de flujos de trabajo o directorio de flujos de trabajo a ejecutar (separadas por comas, file)
-wurl, -workflow-url string[] url de flujo de trabajo o lista que contiene urls de flujo de trabajo para ejecutar (separadas por comas, file)
-validate valida las plantillas pasadas a nuclei
-nss, -no-strict-syntax deshabilita la comprobación de sintaxis estricta en las plantillas
-td, -template-display muestra el contenido de las plantillas
-tl lista todas las plantillas disponibles
-tgl lista todas las etiquetas disponibles
-sign firma las plantillas con la clave privada definida en la variable de entorno NUCLEI_SIGNATURE_PRIVATE_KEY
-code habilita la carga de plantillas basadas en protocolos de código
-dut, -disable-unsigned-templates deshabilita la ejecución de plantillas no firmadas o plantillas con firma no coincidente
FILTERING:
-a, -author string[] plantillas a ejecutar basadas en autores (separadas por comas, file)
-tags string[] plantillas a ejecutar basadas en etiquetas (separadas por comas, file)
-etags, -exclude-tags string[] plantillas a excluir basadas en etiquetas (separadas por comas, file)
-itags, -include-tags string[] etiquetas a ejecutar incluso si están excluidas ya sea por defecto o por configuración
-id, -template-id string[] plantillas a ejecutar basadas en IDs de plantilla (comma-separated, file, allow-wildcard)
-eid, -exclude-id string[] plantillas a excluir basadas en IDs de plantilla (separadas por comas, file)
-it, -include-templates string[] ruta al archivo de plantilla o directorio a ejecutar incluso si están excluidas ya sea por defecto o por configuración
-et, -exclude-templates string[] ruta al archivo de plantilla o directorio a excluir (separadas por comas, file)
-em, -exclude-matchers string[] matchers de plantilla a excluir en el resultado
-s, -severity value[] plantillas a ejecutar basadas en criticidad. Valores posibles: info, bajo, medio, alto, crítico, desconocido
-es, -exclude-severity value[] plantillas a excluir basadas en criticidad. Valores posibles: info, bajo, medio, alto, crítico, desconocido
-pt, -type value[] plantillas a ejecutar basadas en tipo de protocolo. Valores posibles: dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code, javascript
-ept, -exclude-type value[] plantillas a excluir basadas en tipo de protocolo. Valores posibles: dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code, javascript
-tc, -template-condition string[] plantillas a ejecutar basadas en condición de expresión
OUTPUT:
-o, -output string archivo de salida donde guardar las incidencias/vulnerabilidades detectadas
-sresp, -store-resp almacenar todas las peticiones/respuestas enviadas por nuclei en el directorio de salida
-srd, -store-resp-dir string almacenar todas las peticiones/respuestas enviadas por nuclei en un directorio personalizado (por defecto "output")
-silent mostrar resultados únicamente
-nc, -no-color deshabilitar la coloración del contenido de salida (códigos de escape ANSI)
-j, -jsonl escribir la salida en formato JSONL(ines)
-irr, -include-rr -omit-raw incluir pares peticiones/respuesta en las salidas JSON, JSONL y Markdown (sólo para hallazgos) [OBSOLETO usar -omit-raw] (por defecto true)
-or, -omit-raw omitir los pares peticiones/respuesta en las salidas JSON, JSONL y Markdown (sólo para hallazgos)
-ot, -omit-template omitir plantilla codificada en la salida JSON, JSONL
-nm, -no-meta deshabilitar la impresión de metadatos de resultados en la salida cli
-ts, -timestamp habilitar la impresión de la marca de tiempo en la salida cli
-rdb, -report-db string base de datos de informes de nuclei (utilizarla siempre para persistir los datos de los informes)
-ms, -matcher-status mostrar el estado de fallo de coincidencia
-me, -markdown-export string directorio para exportar resultados en formato markdown
-se, -sarif-export string archivo para exportar resultados en formato SARIF
-je, -json-export string archivo para exportar resultados en formato JSON
-jle, -jsonl-export string archivo para exportar resultados en formato JSONL(ines)
CONFIGURATIONS:
-config string ruta al archivo de configuración de nuclei
-fr, -follow-redirects habilitar el seguimiento de redirecciones para plantillas http
-fhr, -follow-host-redirects seguir redirecciones en el mismo host
-mr, -max-redirects int número máximo de redirecciones a seguir para plantillas http (por defecto 10)
-dr, -disable-redirects deshabilitar redirecciones para plantillas http
-rc, -report-config string archivo de configuración del módulo de informes de nuclei
-H, -header string[] encabezado/cookie personalizado a incluir en todas las peticiones http en formato header:value (cli, file)
-V, -var value variables personalizadas en formato key=value
-r, -resolvers string archivo que contiene lista de resolutores para nuclei
-sr, -system-resolvers utilizar resolución de DNS del sistema como fallback de error
-dc, -disable-clustering deshabilitar la clusterización de peticiones
-passive habilitar el modo de procesamiento pasivo de respuestas HTTP
-fh2, -force-http2 forzar la conexión http2 en las peticiones
-ev, -env-vars habilitar el uso de variables de entorno en la plantilla
-cc, -client-cert string archivo de certificado de cliente (codificado en PEM) utilizado para autenticarse contra los hosts escaneados
-ck, -client-key string archivo de clave de cliente (codificado en PEM) utilizado para autenticarse contra los hosts escaneados
-ca, -client-ca string archivo de autoridad de certificación de cliente (codificado en PEM) utilizado para autenticarse contra los hosts escaneados
-sml, -show-match-line mostrar líneas de coincidencia para plantillas de archivo, funciona solo con extractores
-ztls utilizar la biblioteca ztls con autofallback a estándar para tls13 [Obsoleto] autofallback a ztls está habilitado por defecto
-sni string nombre de host tls sni a usar (por defecto: nombre de dominio de entrada)
-dt, -dialer-timeout value tiempo de espera para peticiones de red
-dka, -dialer-keep-alive value duración de keep-alive para peticiones de red
-lfa, -allow-local-file-access permite el acceso a archivos (carga útil) en cualquier lugar del sistema
-lna, -restrict-local-network-access bloquea conexiones a la red local / privada
-i, -interface string interfaz de red a usar para el escaneo de red
-at, -attack-type string tipo de combinaciones de carga útil a realizar (batteringram, pitchfork, clusterbomb)
-sip, -source-ip string dirección ip de origen a usar para el escaneo de red
-rsr, -response-size-read int tamaño máximo de respuesta a leer en bytes (por defecto 10485760)
-rss, -response-size-save int tamaño máximo de respuesta a guardar en bytes (por defecto 1048576)
-reset reset elimina todos los archivos de configuración y datos de nuclei (incluidas las nuclei-templates)
-tlsi, -tls-impersonate habilitar client hello (ja3) tls randomization experimental
INTERACTSH:
-iserver, -interactsh-server string url del servidor interactsh para instancia autoalojada (por defecto: oast.pro,oast.live,oast.site,oast.online,oast.fun,oast.me)
-itoken, -interactsh-token string token de autenticación del servidor interactsh autoalojado
-interactions-cache-size int número de peticiones a mantener en la caché de interacciones (por defecto 5000)
-interactions-eviction int número de segundos a esperar antes de eliminar las solicitudes de la caché (por defecto 60)
-interactions-poll-duration int número de segundos a esperar antes de cada solicitud de polling de interacciones (por defecto 5)
-interactions-cooldown-period int tiempo adicional para el polling de interacciones antes de salir (por defecto 5)
-ni, -no-interactsh desactivar el servidor interactsh para pruebas OAST, excluir plantillas basadas en OAST
FUZZING:
-ft, -fuzzing-type string sobrescribe el tipo de fuzzing establecido en la plantilla (replace, prefix, postfix, infix)
-fm, -fuzzing-mode string sobrescribe el modo de fuzzing establecido en la plantilla (multiple, single)
-fuzz habilita la carga de plantillas de fuzzing (Obsoleto: usar -dast en su lugar)
-dast solo ejecuta plantillas DAST
UNCOVER:
-uc, -uncover habilita el motor uncover
-uq, -uncover-query string[] consulta de búsqueda uncover
-ue, -uncover-engine string[] motor de búsqueda uncover (shodan,censys,fofa,shodan-idb,quake,hunter,zoomeye,netlas,criminalip,publicwww,hunterhow) (por defecto shodan)
-uf, -uncover-field string campos uncover a devolver (ip,port,host) (por defecto "ip:port")
-ul, -uncover-limit int resultados uncover a devolver (por defecto 100)
-ur, -uncover-ratelimit int sobrescribe el límite de velocidad de los motores con el límite de velocidad del motor uncover (por defecto 60 req/min) (por defecto 60)
RATE-LIMIT:
-rl, -rate-limit int número máximo de peticiones a enviar por segundo (por defecto 150)
-rlm, -rate-limit-minute int número máximo de peticiones a enviar por minuto
-bs, -bulk-size int número máximo de hosts a ser analizados en paralelo por plantilla (por defecto 25)
-c, -concurrency int número máximo de plantillas a ejecutar en paralelo (por defecto 25)
-hbs, -headless-bulk-size int número máximo de hosts headless a ser analizados en paralelo por plantilla (por defecto 10)
-headc, -headless-concurrency int número máximo de plantillas headless a ejecutar en paralelo (por defecto 10)
-jsc, -js-concurrency int número máximo de entornos de ejecución de JavaScript a ejecutar en paralelo (por defecto 120)
-pc, -payload-concurrency int concurrencia máxima de carga útil para cada plantilla (por defecto 25)
-tlc, -template-loading-concurrency int número máximo de operaciones de carga de plantillas concurrentes (por defecto 50)
OPTIMIZATIONS:
-timeout int tiempo de espera en segundos (por defecto 10)
-retries int número de veces que se reintenta una petición fallida (por defecto 1)
-ldp, -leave-default-ports dejar puertos HTTP/HTTPS predeterminados (por ejemplo, host:80,host:443)
-mhe, -max-host-error int errores máximos para un host antes de omitirlo del escaneo (por defecto 30)
-te, -track-error string[] agrega el error dado a la lista de seguimiento de errores máximos por host (standard, file)
-nmhe, -no-mhe deshabilita la omisión del host del escaneo basado en errores
-project utiliza una carpeta de proyecto para evitar enviar la misma petición varias veces
-project-path string establece una ruta de proyecto específica (por defecto "/tmp")
-spm, -stop-at-first-match detiene el procesamiento de las peticiones HTTP después de la primera coincidencia (puede romper la lógica de la plantilla/flujo de trabajo)
-stream modo transmisión - comienza a trabajar sin ordenar la entrada
-ss, -scan-strategy value estrategia a utilizar mientras se escanea (auto/host-spray/template-spray) (por defecto auto)
-irt, -input-read-timeout value tiempo de espera en la lectura de entrada (por defecto 3m0s)
-nh, -no-httpx deshabilita análisis httpx para entradas que no son URL
-no-stdin deshabilita el procesamiento de la entrada estándar
HEADLESS:
-headless habilita las plantillas que requieren soporte de navegadores sin interfaz gráfica (headless browser) (el usuario root en Linux deshabilitará el sandbox)
-page-timeout int segundos para esperar cada página en modo sin interfaz (por defecto 20)
-sb, -show-browser muestra el navegador en la pantalla al ejecutar plantillas con modo sin interfaz
-ho, -headless-options string[] inicia Chrome en modo sin interfaz con opciones adicionales
-sc, -system-chrome utiliza el navegador Chrome instalado localmente en lugar del instalado por nuclei
-cdpe, -cdp-endpoint string usar navegador remoto a través del endpoint del Protocolo de Herramientas de Desarrollador de Chrome (CDP)
-lha, -list-headless-action lista de acciones sin interfaz disponibles
DEBUG:
-debug muestra todas las peticiones y respuestas
-dreq, -debug-req muestra todas las peticiones enviadas
-dresp, -debug-resp muestra todas las respuestas recibidas
-p, -proxy string[] lista de proxies http/socks5 a utilizar (separados por comas o archivo de entrada)
-pi, -proxy-internal proxy para todas las peticiones internas
-ldf, -list-dsl-function lista todas las firmas de función DSL admitidas
-tlog, -trace-log string archivo a escribir el registro de traza de peticiones enviadas
-elog, -error-log string archivo a escribir el registro de error de peticiones enviadas
-version muestra la versión de nuclei
-hm, -hang-monitor habilita la monitorización de bloqueos de nuclei
-v, -verbose muestra salida detallada
-profile-mem string archivo opcional de volcado de memoria de nuclei
-vv muestra las plantillas cargadas para el escaneo
-svd, -show-var-dump muestra el volcado de variables para depuración
-ep, -enable-pprof habilita el servidor de depuración pprof
-tv, -templates-version muestra la versión de las plantillas nuclei (nuclei-templates) instaladas
-hc, -health-check ejecuta comprobación de diagnóstico
UPDATE:
-up, -update actualiza el motor de nuclei a la última versión lanzada
-ut, -update-templates actualiza nuclei-templates a la última versión lanzada
-ud, -update-template-dir string directorio personalizado para instalar/actualizar nuclei-templates
-duc, -disable-update-check deshabilita la comprobación automática de actualizaciones de nuclei/templates
STATISTICS:
-stats muestra estadísticas sobre el escaneo en ejecución
-sj, -stats-json muestra estadísticas en formato JSONL(ines)
-si, -stats-interval int número de segundos a esperar entre mostrar una actualización de estadísticas (por defecto 5)
-mp, -metrics-port int puerto para exponer métricas de nuclei (por defecto 9092)
CLOUD:
-auth configura la clave de API del cloud de projectdiscovery (pdcp)
-cup, -cloud-upload sube los resultados del escaneo al dashboard de pdcp
-sid, -scan-id string sube los resultados del escaneo al ID de escaneo dado
AUTHENTICATION:
-sf, -secret-file string[] ruta al archivo de configuración que contiene los secrets para el escaneo autenticado de nuclei
-ps, -prefetch-secrets precarga los secrets del archivo de secrets
EXAMPLES:
Ejecutar nuclei en un solo host:
$ nuclei -target example.com
Ejecutar nuclei con directorios de plantillas específicos:
$ nuclei -target example.com -t http/cves/ -t ssl
Ejecutar nuclei contra una lista de hosts:
$ nuclei -list hosts.txt
Ejecutar nuclei con una salida JSON:
$ nuclei -target example.com -json-export output.json
Ejecutar nuclei con salidas Markdown ordenadas (con variables de entorno):
$ MARKDOWN_EXPORT_SORT_MODE=template nuclei -target example.com -markdown-export nuclei_report/
Documentación adicional disponible en: https://docs.projectdiscovery.io/getting-started/running
```
### Ejecutando Nuclei
Consulta https://docs.projectdiscovery.io/tools/nuclei/running para obtener detalles sobre cómo ejecutar Nuclei.
### Uso de Nuclei desde código Go
La guía completa sobre cómo usar Nuclei como biblioteca/SDK está disponible en [godoc](https://pkg.go.dev/github.com/projectdiscovery/nuclei/v3/lib#section-readme).
### Recursos
Puedes acceder a la documentación principal de Nuclei en https://docs.projectdiscovery.io/tools/nuclei/, y obtener más información sobre Nuclei en la nube con [ProjectDiscovery Cloud Platform](https://cloud.projectdiscovery.io).
¡Consulta https://docs.projectdiscovery.io/tools/nuclei/resources para obtener más recursos y videos sobre Nuclei!
### Créditos
Gracias a todos los increíbles [contribuyentes de la comunidad que enviaron PRs](https://github.com/projectdiscovery/nuclei/graphs/contributors) y mantienen este proyecto actualizado. :heart:
Si tienes una idea o algún tipo de mejora, eres bienvenido a contribuir y participar en el Proyecto, siéntete libre de enviar tu PR.
También echa un vistazo a los siguientes proyectos de código abierto similares que pueden adaptarse a tu flujo de trabajo:
[FFuF](https://github.com/ffuf/ffuf), [Qsfuzz](https://github.com/ameenmaali/qsfuzz), [Inception](https://github.com/proabiral/inception), [Snallygaster](https://github.com/hannob/snallygaster), [Gofingerprint](https://github.com/Static-Flow/gofingerprint), [Sn1per](https://github.com/1N3/Sn1per/tree/master/templates), [Google tsunami](https://github.com/google/tsunami-security-scanner), [Jaeles](https://github.com/jaeles-project/jaeles), [ChopChop](https://github.com/michelin/ChopChop)
### Licencia
Nuclei se distribuye bajo la [Licencia MIT](https://github.com/projectdiscovery/nuclei/blob/main/LICENSE.md)
---
Nuclei digunakan untuk mengirim permintaan lintas target berdasarkan templat, yang menghasilkan nol positif palsu dan menyediakan pemindaian yang cepat pada banyak host. Nuclei menawarkan pemindaian untuk berbagai protokol, termasuk TCP, DNS, HTTP, SSL, File, Whois, Websocket, Headless, dll. Dengan templating yang kuat dan fleksibel, Nuclei dapat digunakan untuk memodelkan semua jenis pemeriksaan keamanan.
Kami memiliki [repositori khusus](https://github.com/projectdiscovery/nuclei-templates) yang menampung berbagai jenis templat kerentanan yang disumbangkan oleh **lebih dari 300** peneliti dan teknisi keamanan.
## Cara Kerja
# Instalasi Nuclei
Nuclei membutuhkan **go1.24.2** agar dapat diinstall. Jalankan perintah berikut untuk menginstal versi terbaru -
```sh
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
```
**Metode [instalasi lain dapat ditemukan di sini](https://nuclei.projectdiscovery.io/nuclei/get-started/).**
### Nuclei Templates
Nuclei memiliki dukungan untuk unduhan/pembaruan templat otomatis sebagai bawaan sejak versi [v2.5.2](https://github.com/projectdiscovery/nuclei/releases/tag/v2.5.2). Proyek [**Nuclei-Templates**](https://github.com/projectdiscovery/nuclei-templates) menyediakan daftar template siap pakai yang dibuat oleh komunitas yang terus diperbarui.
Anda dapat menggunakan flag `-update-templates` untuk memperbarui templat inti kapan saja; Anda juga dapat menulis pemeriksaan Anda sendiri untuk alur kerja individu dan untuk kebutuhan Anda sendiri dengan mengikuti [panduan pembuatan templat Nuclei](https://nuclei.projectdiscovery.io/templating-guide/).
Untuk referensi penulisan sintaks DSL berbasis YAML tersedia [di sini](SYNTAX-REFERENCE.md).
### Cara Pakai
```sh
nuclei -h
```
Ini akan menampilkan bantuan untuk alat tersebut. Berikut adalah semua flag yang didukungnya.
```console
Nuclei is a fast, template based vulnerability scanner focusing
on extensive configurability, massive extensibility and ease of use.
Usage:
./nuclei [flags]
Flags:
TARGET:
-u, -target string[] target URLs/hosts to scan
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
-resume string resume scan from and save to specified file (clustering will be disabled)
-sa, -scan-all-ips scan all the IP's associated with dns record
-iv, -ip-version string[] IP version to scan of hostname (4,6) - (default 4)
TEMPLATES:
-nt, -new-templates run only new templates added in latest nuclei-templates release
-ntv, -new-templates-version string[] run new templates added in specific version
-as, -automatic-scan automatic web scan using wappalyzer technology detection to tags mapping
-t, -templates string[] list of template or template directory to run (comma-separated, file)
-turl, -template-url string[] template url or list containing template urls to run (comma-separated, file)
-w, -workflows string[] list of workflow or workflow directory to run (comma-separated, file)
-wurl, -workflow-url string[] workflow url or list containing workflow urls to run (comma-separated, file)
-validate validate the passed templates to nuclei
-nss, -no-strict-syntax disable strict syntax check on templates
-td, -template-display displays the templates content
-tl list all available templates
-sign signs the templates with the private key defined in NUCLEI_SIGNATURE_PRIVATE_KEY env variable
-code enable loading code protocol-based templates
FILTERING:
-a, -author string[] templates to run based on authors (comma-separated, file)
-tags string[] templates to run based on tags (comma-separated, file)
-etags, -exclude-tags string[] templates to exclude based on tags (comma-separated, file)
-itags, -include-tags string[] tags to be executed even if they are excluded either by default or configuration
-id, -template-id string[] templates to run based on template ids (comma-separated, file)
-eid, -exclude-id string[] templates to exclude based on template ids (comma-separated, file)
-it, -include-templates string[] templates to be executed even if they are excluded either by default or configuration
-et, -exclude-templates string[] template or template directory to exclude (comma-separated, file)
-em, -exclude-matchers string[] template matchers to exclude in result
-s, -severity value[] templates to run based on severity. Possible values: info, low, medium, high, critical, unknown
-es, -exclude-severity value[] templates to exclude based on severity. Possible values: info, low, medium, high, critical, unknown
-pt, -type value[] templates to run based on protocol type. Possible values: dns, file, http, headless, tcp, workflow, ssl, websocket, whois
-ept, -exclude-type value[] templates to exclude based on protocol type. Possible values: dns, file, http, headless, tcp, workflow, ssl, websocket, whois
-tc, -template-condition string[] templates to run based on expression condition
OUTPUT:
-o, -output string output file to write found issues/vulnerabilities
-sresp, -store-resp store all request/response passed through nuclei to output directory
-srd, -store-resp-dir string store all request/response passed through nuclei to custom directory (default "output")
-silent display findings only
-nc, -no-color disable output content coloring (ANSI escape codes)
-j, -jsonl write output in JSONL(ines) format
-irr, -include-rr include request/response pairs in the JSON, JSONL, and Markdown outputs (for findings only) [DEPRECATED use -omit-raw] (default true)
-or, -omit-raw omit request/response pairs in the JSON, JSONL, and Markdown outputs (for findings only)
-nm, -no-meta disable printing result metadata in cli output
-ts, -timestamp enables printing timestamp in cli output
-rdb, -report-db string nuclei reporting database (always use this to persist report data)
-ms, -matcher-status display match failure status
-me, -markdown-export string directory to export results in markdown format
-se, -sarif-export string file to export results in SARIF format
-je, -json-export string file to export results in JSON format
-jle, -jsonl-export string file to export results in JSONL(ine) format
CONFIGURATIONS:
-config string path to the nuclei configuration file
-fr, -follow-redirects enable following redirects for http templates
-fhr, -follow-host-redirects follow redirects on the same host
-mr, -max-redirects int max number of redirects to follow for http templates (default 10)
-dr, -disable-redirects disable redirects for http templates
-rc, -report-config string nuclei reporting module configuration file
-H, -header string[] custom header/cookie to include in all http request in header:value format (cli, file)
-V, -var value custom vars in key=value format
-r, -resolvers string file containing resolver list for nuclei
-sr, -system-resolvers use system DNS resolving as error fallback
-dc, -disable-clustering disable clustering of requests
-passive enable passive HTTP response processing mode
-fh2, -force-http2 force http2 connection on requests
-ev, -env-vars enable environment variables to be used in template
-cc, -client-cert string client certificate file (PEM-encoded) used for authenticating against scanned hosts
-ck, -client-key string client key file (PEM-encoded) used for authenticating against scanned hosts
-ca, -client-ca string client certificate authority file (PEM-encoded) used for authenticating against scanned hosts
-sml, -show-match-line show match lines for file templates, works with extractors only
-ztls use ztls library with autofallback to standard one for tls13 [Deprecated] autofallback to ztls is enabled by default
-sni string tls sni hostname to use (default: input domain name)
-lfa, -allow-local-file-access allows file (payload) access anywhere on the system
-lna, -restrict-local-network-access blocks connections to the local / private network
-i, -interface string network interface to use for network scan
-at, -attack-type string type of payload combinations to perform (batteringram,pitchfork,clusterbomb)
-sip, -source-ip string source ip address to use for network scan
-config-directory string override the default config path ($home/.config)
-rsr, -response-size-read int max response size to read in bytes (default 10485760)
-rss, -response-size-save int max response size to read in bytes (default 1048576)
-reset reset removes all nuclei configuration and data files (including nuclei-templates)
-tlsi, -tls-impersonate enable experimental client hello (ja3) tls randomization
INTERACTSH:
-iserver, -interactsh-server string interactsh server url for self-hosted instance (default: oast.pro,oast.live,oast.site,oast.online,oast.fun,oast.me)
-itoken, -interactsh-token string authentication token for self-hosted interactsh server
-interactions-cache-size int number of requests to keep in the interactions cache (default 5000)
-interactions-eviction int number of seconds to wait before evicting requests from cache (default 60)
-interactions-poll-duration int number of seconds to wait before each interaction poll request (default 5)
-interactions-cooldown-period int extra time for interaction polling before exiting (default 5)
-ni, -no-interactsh disable interactsh server for OAST testing, exclude OAST based templates
FUZZING:
-ft, -fuzzing-type string overrides fuzzing type set in template (replace, prefix, postfix, infix)
-fm, -fuzzing-mode string overrides fuzzing mode set in template (multiple, single)
UNCOVER:
-uc, -uncover enable uncover engine
-uq, -uncover-query string[] uncover search query
-ue, -uncover-engine string[] uncover search engine (shodan,censys,fofa,shodan-idb,quake,hunter,zoomeye,netlas,criminalip,publicwww,hunterhow) (default shodan)
-uf, -uncover-field string uncover fields to return (ip,port,host) (default "ip:port")
-ul, -uncover-limit int uncover results to return (default 100)
-ur, -uncover-ratelimit int override ratelimit of engines with unknown ratelimit (default 60 req/min) (default 60)
RATE-LIMIT:
-rl, -rate-limit int maximum number of requests to send per second (default 150)
-rlm, -rate-limit-minute int maximum number of requests to send per minute
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
-c, -concurrency int maximum number of templates to be executed in parallel (default 25)
-hbs, -headless-bulk-size int maximum number of headless hosts to be analyzed in parallel per template (default 10)
-headc, -headless-concurrency int maximum number of headless templates to be executed in parallel (default 10)
-tlc, -template-loading-concurrency int maximum number of concurrent template loading operations (default 50)
OPTIMIZATIONS:
-timeout int time to wait in seconds before timeout (default 10)
-retries int number of times to retry a failed request (default 1)
-ldp, -leave-default-ports leave default HTTP/HTTPS ports (eg. host:80,host:443)
-mhe, -max-host-error int max errors for a host before skipping from scan (default 30)
-te, -track-error string[] adds given error to max-host-error watchlist (standard, file)
-nmhe, -no-mhe disable skipping host from scan based on errors
-project use a project folder to avoid sending same request multiple times
-project-path string set a specific project path (default "/tmp")
-spm, -stop-at-first-match stop processing HTTP requests after the first match (may break template/workflow logic)
-stream stream mode - start elaborating without sorting the input
-ss, -scan-strategy value strategy to use while scanning(auto/host-spray/template-spray) (default auto)
-irt, -input-read-timeout duration timeout on input read (default 3m0s)
-nh, -no-httpx disable httpx probing for non-url input
-no-stdin disable stdin processing
HEADLESS:
-headless enable templates that require headless browser support (root user on Linux will disable sandbox)
-page-timeout int seconds to wait for each page in headless mode (default 20)
-sb, -show-browser show the browser on the screen when running templates with headless mode
-sc, -system-chrome use local installed Chrome browser instead of nuclei installed
-cdpe, -cdp-endpoint string use remote browser via Chrome DevTools Protocol (CDP) endpoint
-lha, -list-headless-action list available headless actions
DEBUG:
-debug show all requests and responses
-dreq, -debug-req show all sent requests
-dresp, -debug-resp show all received responses
-p, -proxy string[] list of http/socks5 proxy to use (comma separated or file input)
-pi, -proxy-internal proxy all internal requests
-ldf, -list-dsl-function list all supported DSL function signatures
-tlog, -trace-log string file to write sent requests trace log
-elog, -error-log string file to write sent requests error log
-version show nuclei version
-hm, -hang-monitor enable nuclei hang monitoring
-v, -verbose show verbose output
-profile-mem string optional nuclei memory profile dump file
-vv display templates loaded for scan
-svd, -show-var-dump show variables dump for debugging
-ep, -enable-pprof enable pprof debugging server
-tv, -templates-version shows the version of the installed nuclei-templates
-hc, -health-check run diagnostic check up
UPDATE:
-up, -update update nuclei engine to the latest released version
-ut, -update-templates update nuclei-templates to latest released version
-ud, -update-template-dir string custom directory to install / update nuclei-templates
-duc, -disable-update-check disable automatic nuclei/templates update check
STATISTICS:
-stats display statistics about the running scan
-sj, -stats-json display statistics in JSONL(ines) format
-si, -stats-interval int number of seconds to wait between showing a statistics update (default 5)
-m, -metrics expose nuclei metrics on a port
-mp, -metrics-port int port to expose nuclei metrics on (default 9092)
CLOUD:
-auth configure projectdiscovery cloud (pdcp) api key
-cup, -cloud-upload upload scan results to pdcp dashboard
-sid, -scan-id string upload scan results to given scan id
EXAMPLES:
Run nuclei on single host:
$ nuclei -target example.com
Run nuclei with specific template directories:
$ nuclei -target example.com -t http/cves/ -t ssl
Run nuclei against a list of hosts:
$ nuclei -list hosts.txt
Run nuclei with a JSON output:
$ nuclei -target example.com -json-export output.json
Run nuclei with sorted Markdown outputs (with environment variables):
$ MARKDOWN_EXPORT_SORT_MODE=template nuclei -target example.com -markdown-export nuclei_report/
Additional documentation is available at: https://docs.projectdiscovery.io/getting-started/running
```
### Menjalankan Nuclei
Memindai domain target dengan templat Nuclei yang [dikurasi oleh komunitas](https://github.com/projectdiscovery/nuclei-templates).
```sh
nuclei -u https://example.com
```
Memindai URL target dengan templat Nuclei yang [dikurasi oleh komunitas](https://github.com/projectdiscovery/nuclei-templates).
```sh
nuclei -list urls.txt
```
Contoh dari berkas `urls.txt`:
```yaml
http://example.com
http://app.example.com
http://test.example.com
http://uat.example.com
```
**Contoh lebih detil tentang menjalankan Nuclei dapat ditemukan [di sini](https://nuclei.projectdiscovery.io/nuclei/get-started/#running-nuclei).**
# Untuk Teknisi Keamanan
Nuclei menawarkan sejumlah besar fitur yang berguna bagi teknisi keamanan untuk menyesuaikan alur kerja di organisasi mereka. Dengan berbagai kemampuan pemindaian (seperti misalnya DNS, HTTP, TCP), teknisi keamanan dapat dengan mudah membuat rangkaian pemeriksaan khusus mereka dengan Nuclei.
- Berbagai protokol yang didukung: TCP, DNS, HTTP, File, dll
- Mencapai langkah-langkah kerentanan yang kompleks dengan alur kerja dan [permintaan dinamis](https://blog.projectdiscovery.io/nuclei-unleashed-quickly-write-complex-exploits/).
- Mudah diintegrasikan ke dalam CI/CD, dirancang agar mudah diintegrasikan ke dalam siklus regresi untuk secara aktif memeriksa perbaikan dan kemunculan kerentanan kembali.
**Untuk Pemburu Celah Berhadiah:**
Nuclei memungkinkan Anda untuk menyesuaikan pendekatan pengujian Anda dengan rangkaian pemeriksaan Anda sendiri dan dengan mudah menjalankan program celah berhadiah Anda. Selain itu, Nuclei dapat dengan mudah diintegrasikan ke dalam alur kerja pemindaian berkelanjutan.
- Dirancang agar mudah diintegrasikan ke dalam alur kerja alat lainnya.
- Dapat memproses ribuan host hanya dalam beberapa menit.
- Mudah mengotomatiskan pendekatan pengujian khusus Anda dengan sintaks DSL berbasis YAML sederhana kami.
Silakan periksa proyek sumber terbuka kami yang lain yang mungkin cocok dengan alur kerja celah berhadiah Anda: [github.com/projectdiscovery](https://github.com/projectdiscovery), kami juga menyediakan [penyegaran data DNS di Chaos setiap hari](https://chaos.projectdiscovery.io).
**Untuk Penguji Penetrasi:**
Nuclei sangat meningkatkan cara Anda mendekati penilaian keamanan dengan menambah proses manual yang berulang. Para konsultan sudah mengonversi langkah penilaian manual mereka dengan Nuclei, ini memungkinkan mereka untuk menjalankan serangkaian pendekatan penilaian khusus mereka di ribuan host secara otomatis.
Para penguji penetrasi mendapatkan kekuatan penuh dari templat publik dan kemampuan penyesuaian kami untuk mempercepat proses penilaian mereka, dan khususnya dengan siklus regresi di mana Anda dapat dengan mudah memverifikasi perbaikannya.
- Mudah untuk membuat daftar pemeriksa kepatuhan Anda, sederet standar (mis., OWASP 10 Teratas).
- Dengan kemampuan seperti [fuzz](https://nuclei.projectdiscovery.io/templating-guide/protocols/http-fuzzing/) dan [alur kerja](https://nuclei.projectdiscovery.io/templating-guide/workflows/), langkah manual yang rumit dan penilaian berulang dapat dengan mudah diotomatisasi dengan Nuclei.
- Mudah untuk menguji ulang perbaikan kerentanan hanya dengan menjalankan ulang template.
# Untuk Pengembang dan Organisasi
Nuclei dibangun dengan kesederhanaan dalam pemikiran, dengan templat yang didukung komunitas oleh ratusan peneliti keamanan, memungkinkan Anda untuk tidak tertinggal dengan ancaman keamanan terbaru menggunakan pemindaian Nuclei terus menerus pada host. Ini dirancang agar mudah diintegrasikan ke dalam siklus pengujian regresi, untuk memverifikasi perbaikan dan menghilangkan kerentanan agar tidak terjadi di masa mendatang.
- **CI/CD:** Pengembang sudah memanfaatkan Nuclei dalam aliran CI/CD mereka, ini memungkinkan mereka untuk terus memantau lingkungan pementasan dan produksi mereka dengan templat yang disesuaikan.
- **Siklus Regresi Berkelanjutan:** Dengan Nuclei, Anda dapat membuat templat khusus pada setiap kerentanan baru yang teridentifikasi dan dimasukkan ke dalam mesin Nuclei untuk dihilangkan dalam siklus regresi berkelanjutan.
Kami memiliki [utas diskusi tentang ini](https://github.com/projectdiscovery/nuclei-templates/discussions/693), sudah ada beberapa program celah berhadiah yang memberikan insentif kepada peretas untuk menulis templat inti dengan setiap pengiriman, yang membantu mereka untuk menghilangkan kerentanan di semua aset mereka, serta untuk menghilangkan risiko masa depan yang muncul kembali pada lingkungan produksi. Jika Anda tertarik untuk menerapkannya di organisasi Anda, jangan ragu untuk [menghubungi kami](mailto:contact@projectdiscovery.io). Kami akan dengan senang hati membantu Anda dalam proses memulai, atau Anda juga dapat memposting ke [utas diskusi](https://github.com/projectdiscovery/nuclei-templates/discussions/693) untuk bantuan apapun.
### Sumber Daya
- [Menemukan bug dengan menggunakan Nuclei dengan PinkDraconian (Robbe Van Roey)](https://www.youtube.com/watch?v=ewP0xVPW-Pk) oleh **[@PinkDraconian](https://twitter.com/PinkDraconian)**
- [Nuclei: Mengemas Pukulan dengan Pemindaian Kerentanan](https://bishopfox.com/blog/nuclei-vulnerability-scan) oleh **Bishopfox**
- [Kerangka kemanjuran WAF](https://www.fastly.com/blog/the-waf-efficacy-framework-measuring-the-effectiveness-of-your-waf) oleh **Fastly**
- [Memindai Aplikasi Web Langsung dengan Nuclei di Aliran CI/CD](https://blog.escape.tech/devsecops-part-iii-scanning-live-web-applications/) oleh **[@TristanKalos](https://twitter.com/TristanKalos)**
- [Pemindaian Bertenaga Komunitas dengan Nuclei](https://blog.projectdiscovery.io/community-powered-scanning-with-nuclei/)
- [Nuclei Unleashed - Menulis eksploitasi kompleks dengan cepat](https://blog.projectdiscovery.io/nuclei-unleashed-quickly-write-complex-exploits/)
- [Nuclei - Fuzz semua hal](https://blog.projectdiscovery.io/nuclei-fuzz-all-the-things/)
- [Integrasi Nuclei + Interactsh untuk Mengotomatiskan Pengujian OOB](https://blog.projectdiscovery.io/nuclei-interactsh-integration/)
- [Mempersenjatai Alur Kerja Nuclei untuk Menghancurkan Semua Hal](https://medium.com/@dwisiswant0/weaponizes-nuclei-workflows-to-pwn-all-the-things-cd01223feb77) oleh **[@dwisiswant0](https://github.com/dwisiswant0)**
- [Bagaimana Memindai Terus-menerus dengan Nuclei?](https://medium.com/@dwisiswant0/how-to-scan-continuously-with-nuclei-fcb7e9d8b8b9) oleh **[@dwisiswant0](https://github.com/dwisiswant0)**
- [Retas dengan Otomatisasi !!!](https://dhiyaneshgeek.github.io/web/security/2021/07/19/hack-with-automation/) oleh **[@DhiyaneshGeek](https://github.com/DhiyaneshGeek)**
### Kredit
Terima kasih kepada semua komunitas yang luar biasa yang [berkontribusi untuk mengirimkan PR](https://github.com/projectdiscovery/nuclei/graphs/contributors). Lihat juga proyek sumber-terbuka serupa di bawah ini yang mungkin sesuai dengan alur kerja Anda:
[FFuF](https://github.com/ffuf/ffuf), [Qsfuzz](https://github.com/ameenmaali/qsfuzz), [Inception](https://github.com/proabiral/inception), [Snallygaster](https://github.com/hannob/snallygaster), [Gofingerprint](https://github.com/Static-Flow/gofingerprint), [Sn1per](https://github.com/1N3/Sn1per/tree/master/templates), [Google tsunami](https://github.com/google/tsunami-security-scanner), [Jaeles](https://github.com/jaeles-project/jaeles), [ChopChop](https://github.com/michelin/ChopChop)
### Lisensi
Nuclei didistribusikan di bawah [Lisensi MIT](https://github.com/projectdiscovery/nuclei/blob/main/LICENSE.md)
---
Nuclei는 템플릿을 기반으로 대상 간에 요청을 보내기 위해 사용되며 긍정 오류(false positives)가 0이고 다수의 호스트에서 빠른 스캔을 제공합니다. Nuclei는 TCP, DNS, HTTP, SSL, File, Whois, Websocket, Headless 등을 포함한 다양한 프로토콜의 스캔을 제공합니다. 강력하고 유연한 템플릿을 통해 Nuclei는 모든 종류의 보안 검사를 모델링 할 수 있습니다.
**300명 이상의** 보안 연구원과 엔지니어가 제공한 다양한 유형의 취약점 템플릿을 보관하는 [전용 저장소](https://github.com/projectdiscovery/nuclei-templates)를 보유하고 있습니다.
## 작동 방식
# 설치
Nuclei를 성공적으로 설치하기 위해서 **go1.24.2**가 필요합니다. 다음 명령을 실행하여 최신 버전을 설치합니다.
```sh
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
```
**자세한 설치 방법은 [여기](https://nuclei.projectdiscovery.io/nuclei/get-started/)에서 찾을 수 있습니다.**
### Nuclei 템플릿
Nuclei는 [v2.5.2](https://github.com/projectdiscovery/nuclei/releases/tag/v2.5.2)부터 자동 템플릿 다운로드/업데이트를 기본으로 지원합니다.
[**Nuclei-Templates**](https://github.com/projectdiscovery/nuclei-templates) 프로젝트는 지속적으로 업데이트되는 즉시 사용 가능한 템플릿 목록을 제공합니다.
`update-templates` 플래그를 사용하여 언제든 템플릿을 업데이트할 수 있습니다. Nuclei의 [템플릿 가이드](https://nuclei.projectdiscovery.io/templating-guide/)에 따라 개별 워크플로 및 요구 사항에 대한 자체 검사를 작성할 수 있습니다.
YAML DSL의 참조 구문은 [여기](SYNTAX-REFERENCE.md)에서 확인할 수 있습니다.
### 사용 방법
```sh
nuclei -h
```
도구에 대한 도움말이 표시됩니다. 다음은 지원하는 모든 스위치들입니다.
```console
Nuclei는 빠르고, 템플릿 기반의 취약점 스캐너로
넓은 설정 가능성, 대규모 확장성 및 사용 편의성에 중점을 두고 있습니다.
사용법:
./nuclei [flags]
TARGET:
-u, -target string[] 스캔할 대상 URL/호스트
-l, -list string 스캔할 대상 URL/호스트 목록이 있는 파일 경로 (한 줄에 하나씩)
-resume string 지정된 파일에서 스캔을 재개하고 지정된 파일에 저장 (클러스터링은 비활성화됨)
-sa, -scan-all-ips dns 레코드와 관련된 모든 IP 스캔
-iv, -ip-version string[] 스캔할 호스트의 IP 버전 (4,6) - (기본값 4)
TEMPLATES:
-nt, -new-templates 최신 nuclei-templates 릴리스에 추가된 새 템플릿만 실행
-ntv, -new-templates-version string[] 특정 버전에 추가된 새 템플릿 실행
-as, -automatic-scan wappalyzer 기술 감지를 사용하여 태그 매핑으로 자동 웹 스캔
-t, -templates string[] 실행할 템플릿 또는 템플릿 디렉토리 목록 (쉼표로 구분, 파일)
-turl, -template-url string[] 실행할 템플릿 url 또는 템플릿 url 목록 (쉼표로 구분, 파일)
-w, -workflows string[] 실행할 워크플로우 또는 워크플로우 디렉토리 목록 (쉼표로 구분, 파일)
-wurl, -workflow-url string[] 실행할 워크플로우 url 또는 워크플로우 url 목록 (쉼표로 구분, 파일)
-validate nuclei에 전달된 템플릿 검증
-nss, -no-strict-syntax 템플릿에서 엄격한 구문 검사 비활성화
-td, -template-display 템플릿 내용 표시
-tl 사용 가능한 모든 템플릿 목록
-sign NUCLEI_SIGNATURE_PRIVATE_KEY 환경 변수에서 정의된 개인 키로 템플릿에 서명
-code 코드 프로토콜 기반 템플릿 로딩 활성화
FILTERING:
-a, -author string[] 저자를 기반으로 실행할 템플릿 (쉼표로 구분, 파일)
-tags string[] 태그를 기반으로 실행할 템플릿 (쉼표로 구분, 파일)
-etags, -exclude-tags string[] 태그를 기반으로 제외할 템플릿 (쉼표로 구분, 파일)
-itags, -include-tags string[] 기본값 또는 구성에 의해 제외되더라도 실행되어야 하는 태그
-id, -template-id string[] 템플릿 id를 기반으로 실행할 템플릿 (쉼표로 구분, 파일, 와일드카드 허용)
-eid, -exclude-id string[] 템플릿 id를 기반으로 제외할 템플릿 (쉼표로 구분, 파일)
-it, -include-templates string[] 기본값 또는 구성에 의해 제외되더라도 실행되어야 하는 템플릿
-et, -exclude-templates string[] 제외할 템플릿 또는 템플릿 디렉토리 (쉼표로 구분, 파일)
-em, -exclude-matchers string[] 결과에서 제외할 템플릿 매처
-s, -severity value[] 심각도를 기반으로 실행할 템플릿. 가능한 값: info, low, medium, high, critical, unknown
-es, -exclude-severity value[] 심각도를 기반으로 제외할 템플릿. 가능한 값: info, low, medium, high, critical, unknown
-pt, -type value[] 프로토콜 유형을 기반으로 실행할 템플릿. 가능한 값: dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code, javascript
-ept, -exclude-type value[] 프로토콜 유형을 기반으로 제외할 템플릿. 가능한 값: dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code, javascript
-tc, -template-condition string[] 표현식 조건을 기반으로 실행할 템플릿
OUTPUT:
-o, -output string 발견된 문제/취약점을 작성할 출력 파일
-sresp, -store-resp 모든 요청/응답을 nuclei를 통해 출력 디렉토리에 저장
-srd, -store-resp-dir string 모든 요청/응답을 nuclei를 통해 사용자 정의 디렉토리에 저장 (기본값 "output")
-silent 결과만 표시
-nc, -no-color 출력 내용 색상 비활성화 (ANSI 이스케이프 코드)
-j, -jsonl JSONL(ines) 형식으로 출력 작성
-irr, -include-rr -omit-raw JSON, JSONL, Markdown 출력에 요청/응답 쌍 포함 (결과만 해당) [사용 중단 -omit-raw 사용] (기본값 true)
-or, -omit-raw JSON, JSONL, Markdown 출력에서 요청/응답 쌍 생략 (결과만 해당)
-ot, -omit-template JSON, JSONL 출력에서 인코딩된 템플릿 생략
-nm, -no-meta CLI 출력에서 결과 메타데이터 인쇄 비활성화
-ts, -timestamp CLI 출력에 타임스탬프 인쇄 활성화
-rdb, -report-db string nuclei 보고 데이터베이스 (보고 데이터를 유지하려면 항상 이것을 사용)
-ms, -matcher-status 매치 실패 상태 표시
-me, -markdown-export string Markdown 형식으로 결과를 내보낼 디렉토리
-se, -sarif-export string SARIF 형식으로 결과를 내보낼 파일
-je, -json-export string JSON 형식으로 결과를 내보낼 파일
-jle, -jsonl-export string JSONL(ine) 형식으로 결과를 내보낼 파일
CONFIGURATIONS:
-config string nuclei 구성 파일 경로
-fr, -follow-redirects http 템플릿에 대한 리디렉션 따라가기 활성화
-fhr, -follow-host-redirects 같은 호스트에서 리디렉션 따라가기
-mr, -max-redirects int http 템플릿에 대해 따라갈 최대 리디렉션 수 (기본값 10)
-dr, -disable-redirects http 템플릿에 대한 리디렉션 비활성화
-rc, -report-config string nuclei 보고 모듈 구성 파일
-H, -header string[] 모든 http 요청에 포함할 사용자 정의 헤더/쿠키 (header:value 형식) (cli, file)
-V, -var value key=value 형식의 사용자 정의 변수
-r, -resolvers string nuclei에 대한 리졸버 목록이 있는 파일
-sr, -system-resolvers 오류 대체로 시스템 DNS 해결 사용
-dc, -disable-clustering 요청 클러스터링 비활성화
-passive 수동 HTTP 응답 처리 모드 활성화
-fh2, -force-http2 요청에 http2 연결 강제
-ev, -env-vars 템플릿에서 환경 변수 사용 활성화
-cc, -client-cert string 스캔 대상 호스트에 대한 인증에 사용되는 클라이언트 인증서 파일 (PEM 인코딩)
-ck, -client-key string 스캔 대상 호스트에 대한 인증에 사용되는 클라이언트 키 파일 (PEM 인코딩)
-ca, -client-ca string 스캔 대상 호스트에 대한 인증에 사용되는 클라이언트 인증서 기관 파일 (PEM 인코딩)
-sml, -show-match-line 파일 템플릿에 대한 매치 라인 표시, 추출기만 작동
-ztls ztls 라이브러리 사용, tls13에 대한 표준 하나로 자동 대체 [사용 중단] 자동 대체는 기본적으로 ztls로 활성화됨
-sni string 사용할 tls sni 호스트 이름 (기본값: 입력 도메인 이름)
-lfa, -allow-local-file-access 시스템 어디에서나 파일 (페이로드) 액세스 허용
-lna, -restrict-local-network-access 로컬 / 개인 네트워크로의 연결 차단
-i, -interface string 네트워크 스캔에 사용할 네트워크 인터페이스
-at, -attack-type string 수행할 페이로드 조합 유형 (batteringram,pitchfork,clusterbomb)
-sip, -source-ip string 네트워크 스캔에 사용할 소스 IP 주소
-rsr, -response-size-read int 바이트 단위로 읽을 최대 응답 크기 (기본값 10485760)
-rss, -response-size-save int 바이트 단위로 읽을 최대 응답 크기 (기본값 1048576)
-reset reset은 모든 nuclei 구성 및 데이터 파일을 제거합니다 (nuclei-templates 포함)
-tlsi, -tls-impersonate 실험적인 클라이언트 hello (ja3) tls 무작위화 활성화
INTERACTSH:
-iserver, -interactsh-server string 자체 호스팅 인스턴스를 위한 interactsh 서버 url (기본값: oast.pro,oast.live,oast.site,oast.online,oast.fun,oast.me)
-itoken, -interactsh-token string 자체 호스팅 interactsh 서버를 위한 인증 토큰
-interactions-cache-size int 상호 작용 캐시에 유지할 요청 수 (기본값 5000)
-interactions-eviction int 캐시에서 요청을 제거하기 전에 기다릴 초 수 (기본값 60)
-interactions-poll-duration int 각 상호 작용 폴 요청 사이에 기다릴 초 수 (기본값 5)
-interactions-cooldown-period int 종료 전에 상호 작용 폴링에 추가 시간 (기본값 5)
-ni, -no-interactsh OAST 테스트를 위한 interactsh 서버 비활성화, OAST 기반 템플릿 제외
FUZZING:
-ft, -fuzzing-type string 템플릿에 설정된 퍼징 유형 재정의 (replace, prefix, postfix, infix)
-fm, -fuzzing-mode string 템플릿에 설정된 퍼징 모드 재정의 (multiple, single)
UNCOVER:
-uc, -uncover uncover 엔진 활성화
-uq, -uncover-query string[] uncover 검색 쿼리
-ue, -uncover-engine string[] uncover 검색 엔진 (shodan,censys,fofa,shodan-idb,quake,hunter,zoomeye,netlas,criminalip,publicwww,hunterhow) (기본값 shodan)
-uf, -uncover-field string 반환할 uncover 필드 (ip,port,host) (기본값 "ip:port")
-ul, -uncover-limit int 반환할 uncover 결과 (기본값 100)
-ur, -uncover-ratelimit int 알려지지 않은 ratelimit의 엔진을 재정의하는 ratelimit (기본값 60 req/min) (기본값 60)
RATE-LIMIT:
-rl, -rate-limit int 초당 보낼 최대 요청 수 (기본값 150)
-rlm, -rate-limit-minute int 분당 보낼 최대 요청 수
-bs, -bulk-size int 템플릿당 병렬로 분석할 최대 호스트 수 (기본값 25)
-c, -concurrency int 병렬로 실행할 최대 템플릿 수 (기본값 25)
-hbs, -headless-bulk-size int 템플릿당 병렬로 분석할 최대 headless 호스트 수 (기본값 10)
-headc, -headless-concurrency int 병렬로 실행할 최대 headless 템플릿 수 (기본값 10)
-tlc, -template-loading-concurrency int 최대 동시 템플릿 로딩 작업 수 (기본값 50)
OPTIMIZATIONS:
-timeout int 타임아웃 전에 기다릴 초 수 (기본값 10)
-retries int 실패한 요청을 재시도하는 횟수 (기본값 1)
-ldp, -leave-default-ports 기본 HTTP/HTTPS 포트 남겨두기 (예: host:80,host:443)
-mhe, -max-host-error int 스캔에서 건너뛰기 전에 호스트에서 허용되는 최대 오류 수 (기본값 30)
-te, -track-error string[] 최대 호스트 오류 감시 목록에 주어진 오류 추가 (표준, 파일)
-nmhe, -no-mhe 오류를 기반으로 스캔에서 호스트 건너뛰기 비활성화
-project 동일한 요청을 여러 번 보내는 것을 피하기 위해 프로젝트 폴더 사용
-project-path string 특정 프로젝트 경로 설정 (기본값 "/tmp")
-spm, -stop-at-first-match 첫 번째 매치 후 HTTP 요청 처리 중지 (템플릿/워크플로우 로직이 깨질 수 있음)
-stream 스트림 모드 - 입력 정렬 없이 시작
-ss, -scan-strategy value 스캔하는 동안 사용할 전략(auto/host-spray/template-spray) (기본값 auto)
-irt, -input-read-timeout value 입력 읽기 시간 초과 (기본값 3m0s)
-nh, -no-httpx 비 URL 입력에 대한 httpx 프로브 비활성화
-no-stdin stdin 처리를 비활성화합니다
HEADLESS:
-headless headless 브라우저 지원이 필요한 템플릿 활성화 (Linux의 root 사용자는 샌드박스 비활성화)
-page-timeout int headless 모드에서 각 페이지를 기다리는 시간(초) (기본값 20)
-sb, -show-browser headless 모드로 실행하는 템플릿에서 브라우저 화면 표시
-ho, -headless-options string[] 추가 옵션으로 headless chrome 시작
-sc, -system-chrome nuclei가 설치한 Chrome 대신 로컬에 설치된 Chrome 브라우저 사용
-cdpe, -cdp-endpoint string Chrome DevTools Protocol (CDP) 엔드포인트를 통한 원격 브라우저 사용
-lha, -list-headless-action 사용 가능한 headless 액션 목록 표시
DEBUG:
-debug 모든 요청과 응답 표시
-dreq, -debug-req 보낸 모든 요청 표시
-dresp, -debug-resp 받은 모든 응답 표시
-p, -proxy string[] 사용할 http/socks5 프록시 목록 (쉼표로 구분하거나 파일 입력)
-pi, -proxy-internal 모든 내부 요청을 프록시를 통해 전송
-ldf, -list-dsl-function 지원되는 모든 DSL 함수 시그니처 목록 표시
-tlog, -trace-log string 보낸 요청 추적 로그를 기록할 파일
-elog, -error-log string 보낸 요청 오류 로그를 기록할 파일
-version nuclei 버전 표시
-hm, -hang-monitor nuclei 멈춤 모니터링 활성화
-v, -verbose 자세한 출력 표시
-profile-mem string 선택적인 nuclei 메모리 프로필 덤프 파일
-vv 스캔에 로드된 템플릿 표시
-svd, -show-var-dump 디버깅을 위한 변수 덤프 표시
-ep, -enable-pprof pprof 디버깅 서버 활성화
-tv, -templates-version 설치된 nuclei-templates의 버전 표시
-hc, -health-check 진단 검사 실행
UPDATE:
-up, -update 최신 릴리스 버전으로 nuclei 엔진 업데이트
-ut, -update-templates 최신 릴리스 버전으로 nuclei-templates 업데이트
-ud, -update-template-dir string nuclei-templates를 설치/업데이트할 사용자 지정 디렉토리
-duc, -disable-update-check 자동 nuclei/templates 업데이트 확인 비활성화
STATISTICS:
-stats 실행 중인 스캔에 대한 통계 표시
-sj, -stats-json JSONL(ines) 형식으로 통계 표시
-si, -stats-interval int 통계 업데이트를 표시하기까지 기다릴 초 수 (기본값 5)
-mp, -metrics-port int nuclei 메트릭스를 노출할 포트 (기본값 9092)
CLOUD:
-auth projectdiscovery 클라우드 (pdcp) API 키 구성
-cup, -cloud-upload 스캔 결과를 pdcp 대시보드에 업로드
-sid, -scan-id string 주어진 스캔 ID에 스캔 결과 업로드
예시:
단일 호스트에서 nuclei 실행:
$ nuclei -target example.com
특정 템플릿 디렉토리로 nuclei 실행:
$ nuclei -target example.com -t http/cves/ -t ssl
호스트 목록에 대해 nuclei 실행:
$ nuclei -list hosts.txt
JSON 출력으로 nuclei 실행:
$ nuclei -target example.com -json-export output.json
정렬된 Markdown 출력으로 nuclei 실행 (환경 변수 사용):
$ MARKDOWN_EXPORT_SORT_MODE=template nuclei -target example.com -markdown-export nuclei_report/
추가 문서는 여기에서 확인할 수 있습니다: https://docs.projectdiscovery.io/getting-started/running
```
### Nuclei 실행
[community-curated](https://github.com/projectdiscovery/nuclei-templates) nuclei 템플릿으로 대상 도메인을 스캔합니다.
```sh
nuclei -u https://example.com
```
[community-curated](https://github.com/projectdiscovery/nuclei-templates) nuclei 템플릿으로 대상 URL들을 스캔합니다.
```sh
nuclei -list urls.txt
```
`urls.txt`의 예시:
```yaml
http://example.com
http://app.example.com
http://test.example.com
http://uat.example.com
```
**nuclei를 실행하는 자세한 예는 [여기](https://nuclei.projectdiscovery.io/nuclei/get-started/#running-nuclei)에서 찾을 수 있습니다.**
# 보안 엔지니어를 위한
Nuclei는 보안 엔지니어가 조직에서 워크플로를 커스텀하는 데 도움이 되는 많은 기능을 제공합니다.
다양한 스캔 기능(DNS, HTTP, TCP 등)을 통해 보안 엔지니어는 Nuclei를 사용하여 맞춤형 검사 세트를 쉽게 만들 수 있습니다.
- 다양한 프로토콜 지원: TCP, DNS, HTTP, File, etc
- 워크플로 및 [동적 요청](https://blog.projectdiscovery.io/nuclei-unleashed-quickly-write-complex-exploits/)을 통한 복잡한 취약점 탐색 달성
- CI/CD에 쉽게 통합할 수 있으며, 회귀 주기에 쉽게 통합되어 취약점의 수정 및 재출현을 능동적으로 확인할 수 있도록 설계됨.
**Bug Bounty hunter들을 위해:**
Nuclei를 사용하면 자체 검사 모음으로 테스트 접근 방식을 사용자 정의하고 버그 바운티 프로그램에서 쉽게 실행할 수 있습니다.
또한 Nuclei는 모든 연속 스캔 워크플로에 쉽게 통합될 수 있습니다.
- 다른 도구 워크플로에 쉽게 통합되도록 설계됨.
- 몇 분 안에 수천 개의 호스트를 처리할 수 있음.
- 간단한 YAML DSL로 사용자 지정 테스트 접근 방식을 쉽게 자동화할 수 있음.
버그 바운티 워크플로에 맞는 다른 오픈 소스 프로젝트를 확인할 수 있습니다.: [github.com/projectdiscovery](https://github.com/projectdiscovery), 또한, 우리는 매일 [Chaos에서 DNS 데이터를 갱신해 호스팅합니다](https://chaos.projectdiscovery.io).
**침투 테스터들을 위해:**
Nuclei는 수동적이고 반복적인 프로세스를 보강하여 보안 평가에 접근하는 방식을 크게 개선합니다.
컨설턴트들은 이미 Nuclei를 사용해 수동 평가 단계를 전환하고 있으며 이를 통해 수천 개의 호스트에서 자동화된 방식으로 맞춤형 평가 접근 방식을 실행할 수 있습니다.
침투 테스터는 평가 프로세스, 특히 수정 사항을 쉽게 확인할 수 있는 회귀 주기를 통해 공개 템플릿 및 사용자 지정 기능을 최대한 활용할 수 있습니다.
- 규정 준수, 표준 제품군(예: OWASP Top 10) 체크리스트 쉽게 생성.
- Nuclei의 [fuzz](https://nuclei.projectdiscovery.io/templating-guide/protocols/http-fuzzing/) 및 [workflows](https://nuclei.projectdiscovery.io/templating-guide/workflows/) 같은 기능으로 복잡한 수동 단계와 반복 평가를 쉽게 자동화할 수 있음.
- 템플릿 재실행으로 취약점 수정 재테스트 용이.
# 개발자를 위한
Nuclei는 단순성을 염두에 두고 구축되었으며 수백 명의 보안 연구원들이 지원하는 커뮤니티 템플릿을 사용하여 호스트에서 지속적인 Nuclei 스캔을 사용하여 최신 보안 위협에 대한 업데이트를 유지할 수 있습니다.
수정 사항을 검증하고 향후 발생하는 취약점을 제거하기 위해 회귀 테스트 주기에 쉽게 통합되도록 설계되었습니다.
- **CI/CD:** 엔지니어들은 이미 CI/CD 파이프라인 내에서 Nuclei를 활용하고 있으며 이를 통해 맞춤형 템플릿으로 스테이징 및 프로덕션 환경을 지속적으로 모니터링할 수 있습니다.
- **Continuous Regression Cycle:** Nuclei를 사용하면 새로 식별된 모든 취약점에 대한 사용자 지정 템플릿을 만들고 Nuclei 엔진에 넣어 지속적인 회귀 주기에서 제거할 수 있습니다.
[이 문제에 대한 논의 스레드](https://github.com/projectdiscovery/nuclei-templates/discussions/693)가 있으며, Nuclei 템플릿을 작성해 제출할 때마다 해커에게 인센티브를 제공하는 버그 바운티 프로그램들이 존재합니다. 이 프로그램은 모든 자산에서 취약점을 제거할 뿐만 아니라 미래에 프로덕션에 다시 등장할 위험을 제거할 수 있도록 도와줍니다.
이것을 당신의 조직에서 구현하는 것에 관심이 있다면 언제든지 [저희에게 연락하십시오](mailto:contact@projectdiscovery.io).
시작하는 과정에서 기꺼이 도와드리거나 [도움이 필요한 경우 논의 스레드](https://github.com/projectdiscovery/nuclei-templates/discussions/693)에 게시할 수도 있습니다.
### Resources
- [Finding bugs with Nuclei with PinkDraconian (Robbe Van Roey)](https://www.youtube.com/watch?v=ewP0xVPW-Pk) by **[@PinkDraconian](https://twitter.com/PinkDraconian)**
- [Nuclei: Packing a Punch with Vulnerability Scanning](https://bishopfox.com/blog/nuclei-vulnerability-scan) by **Bishopfox**
- [The WAF efficacy framework](https://www.fastly.com/blog/the-waf-efficacy-framework-measuring-the-effectiveness-of-your-waf) by **Fastly**
- [Scanning Live Web Applications with Nuclei in CI/CD Pipeline](https://blog.escape.tech/devsecops-part-iii-scanning-live-web-applications/) by **[@TristanKalos](https://twitter.com/TristanKalos)**
- [Community Powered Scanning with Nuclei](https://blog.projectdiscovery.io/community-powered-scanning-with-nuclei/)
- [Nuclei Unleashed - Quickly write complex exploits](https://blog.projectdiscovery.io/nuclei-unleashed-quickly-write-complex-exploits/)
- [Nuclei - Fuzz all the things](https://blog.projectdiscovery.io/nuclei-fuzz-all-the-things/)
- [Nuclei + Interactsh Integration for Automating OOB Testing](https://blog.projectdiscovery.io/nuclei-interactsh-integration/)
- [Weaponizes nuclei Workflows to Pwn All the Things](https://medium.com/@dwisiswant0/weaponizes-nuclei-workflows-to-pwn-all-the-things-cd01223feb77) by **[@dwisiswant0](https://github.com/dwisiswant0)**
- [How to Scan Continuously with Nuclei?](https://medium.com/@dwisiswant0/how-to-scan-continuously-with-nuclei-fcb7e9d8b8b9) by **[@dwisiswant0](https://github.com/dwisiswant0)**
- [Hack with Automation !!!](https://dhiyaneshgeek.github.io/web/security/2021/07/19/hack-with-automation/) by **[@DhiyaneshGeek](https://github.com/DhiyaneshGeek)**
### Credits
Thanks to all the amazing community [contributors for sending PRs](https://github.com/projectdiscovery/nuclei/graphs/contributors). Do also check out the below similar open-source projects that may fit in your workflow:
[FFuF](https://github.com/ffuf/ffuf), [Qsfuzz](https://github.com/ameenmaali/qsfuzz), [Inception](https://github.com/proabiral/inception), [Snallygaster](https://github.com/hannob/snallygaster), [Gofingerprint](https://github.com/Static-Flow/gofingerprint), [Sn1per](https://github.com/1N3/Sn1per/tree/master/templates), [Google tsunami](https://github.com/google/tsunami-security-scanner), [Jaeles](https://github.com/jaeles-project/jaeles), [ChopChop](https://github.com/michelin/ChopChop)
### License
Nuclei is distributed under [MIT License](https://github.com/projectdiscovery/nuclei/blob/main/LICENSE.md)
---
O Nuclei é utilizado para enviar solicitações para vários alvos baseados em um modelo, resultando em zero falsos positivos e proporcionando uma varredura rápida em um grande número de hosts. O Nuclei oferece suporte a uma variedade de protocolos, incluindo TCP, DNS, HTTP, SSL, Arquivo, Whois, Websocket, Headless, Código, entre outros. Com modelos poderosos e flexíveis, o Nuclei pode ser usado para modelar todos os tipos de verificações de segurança.
Temos um [repositório dedicado](https://github.com/projectdiscovery/nuclei-templates) que abriga vários tipos de modelos de vulnerabilidades, contribuídos por **mais de 300** pesquisadores e engenheiros de segurança.
## Como funciona
| :exclamation: **Aviso** |
|---------------------------------|
| **Este projeto está em desenvolvimento ativo**. Alterações significativas são esperadas em versões futuras. Consulte o changelog antes de atualizar. |
| Este projeto foi desenvolvido principalmente para ser usado como uma ferramenta CLI independente. **Executar o Nuclei como um serviço pode implicar riscos de segurança.** É recomendável utilizá-lo com precaução e medidas de segurança adicionais. |
# Instalação do Nuclei
O Nuclei requer **go1.24.2** para ser instalado corretamente. Execute o seguinte comando para instalar a versão mais recente:
```sh
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
```
Brew
```sh
brew install nuclei
```
Docker
```sh
docker pull projectdiscovery/nuclei:latest
```
**Mais métodos de instalação [podem ser encontrados aqui](https://docs.projectdiscovery.io/tools/nuclei/install).**
### Modelos do Nuclei
O Nuclei possui suporte integrado para download/atualização automática de modelos a partir da versão [v2.5.2](https://github.com/projectdiscovery/nuclei/releases/tag/v2.5.2). O projeto [**Nuclei-Templates**](https://github.com/projectdiscovery/nuclei-templates) fornece uma lista de modelos prontos para uso, atualizados constantemente pela comunidade.
Você também pode usar a flag `update-templates` para atualizar os modelos do Nuclei a qualquer momento; também pode criar seus próprios testes para seu fluxo de trabalho e necessidades específicas seguindo o [guia de modelos](https://docs.projectdiscovery.io/templates/) do Nuclei.
A referência de sintaxe YAML DSL está disponível [aqui](SYNTAX-REFERENCE.md).
### Uso
```sh
nuclei -h
```
Isso mostrará ajuda sobre a ferramenta. Aqui estão todas as opções que ela suporta.
```console
Nuclei é um scanner de vulnerabilidades rápido e baseado em templates
que se concentra em sua ampla configurabilidade, extensibilidade e facilidade de uso.
Usage:
./nuclei [flags]
Flags:
TARGET:
-u, -target string[] URLs/hosts a serem escaneados
-l, -list string caminho do arquivo contendo a lista de URLs/hosts a serem escaneados (um por linha)
-eh, -exclude-hosts string[] hosts a serem excluídos do escaneamento na lista de entrada (ip, cidr, hostname)
-resume string retomar o escaneamento a partir de e salvar no arquivo especificado (a clusterização será desabilitada)
-sa, -scan-all-ips escanear todos os IPs associados ao registro DNS
-iv, -ip-version string[] versão de IP a escanear do nome do host (4,6) - (padrão 4)
TARGET-FORMAT:
-im, -input-mode string modo do arquivo de entrada (list, burp, jsonl, yaml, openapi, swagger) (padrão "list")
-ro, -required-only usar apenas campos obrigatórios no formato de entrada ao gerar requisições
-sfv, -skip-format-validation pular a validação de formato (como variáveis ausentes) ao processar o arquivo de entrada
TEMPLATES:
-nt, -new-templates executar apenas os novos templates adicionados na última versão de nuclei-templates
-ntv, -new-templates-version string[] executar os novos templates adicionados na versão especificada
-as, -automatic-scan escaneamento da web automático utilizando a detecção de tecnologia do Wappalyzer para mapeamento de tags
-t, -templates string[] lista de templates ou diretório de templates a executar (separados por vírgulas, arquivo)
-turl, -template-url string[] URL de template ou lista contendo URLs de templates a executar (separados por vírgulas, arquivo)
-w, -workflows string[] lista de fluxos de trabalho ou diretório de fluxos de trabalho a executar (separados por vírgulas, arquivo)
-wurl, -workflow-url string[] URL de fluxo de trabalho ou lista contendo URLs de fluxos de trabalho para executar (separados por vírgulas, arquivo)
-validate valida os templates passados para o nuclei
-nss, -no-strict-syntax desativa a verificação de sintaxe estrita nos templates
-td, -template-display exibe o conteúdo dos templates
-tl lista todos os templates disponíveis
-tgl lista todas as tags disponíveis
-sign assina os templates com a chave privada definida na variável de ambiente NUCLEI_SIGNATURE_PRIVATE_KEY
-code habilita o carregamento de templates baseados em protocolos de código
-dut, -disable-unsigned-templates desativa a execução de templates não assinados ou com assinatura incompatível
FILTERING:
-a, -author string[] templates a serem executados com base nos autores (separados por vírgulas, arquivo)
-tags string[] templates a serem executados com base em tags (separados por vírgulas, arquivo)
-etags, -exclude-tags string[] templates a excluir com base em tags (separados por vírgulas, arquivo)
-itags, -include-tags string[] tags a executar mesmo que estejam excluídas por padrão ou configuração
-id, -template-id string[] templates a serem executados com base em IDs de template (separados por vírgulas, arquivo, permitem curingas)
-eid, -exclude-id string[] templates a excluir com base em IDs de template (separados por vírgulas, arquivo)
-it, -include-templates string[] caminho do arquivo de template ou diretório a executar mesmo que estejam excluídos por padrão ou configuração
-et, -exclude-templates string[] caminho do arquivo de template ou diretório a excluir (separados por vírgulas, arquivo)
-em, -exclude-matchers string[] matchers de template a excluir no resultado
-s, -severity value[] templates a executar com base na criticidade. Valores possíveis: info, baixo, médio, alto, crítico, desconhecido
-es, -exclude-severity value[] templates a excluir com base na criticidade. Valores possíveis: info, baixo, médio, alto, crítico, desconhecido
-pt, -type value[] templates a executar com base no tipo de protocolo. Valores possíveis: dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code, javascript
-ept, -exclude-type value[] templates a excluir com base no tipo de protocolo. Valores possíveis: dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code, javascript
-tc, -template-condition string[] templates a executar com base em condição de expressão
OUTPUT:
-o, -output string arquivo de saída para salvar as ocorrências/vulnerabilidades detectadas
-sresp, -store-resp armazenar todas as solicitações/respostas enviadas pelo nuclei no diretório de saída
-srd, -store-resp-dir string armazenar todas as solicitações/respostas enviadas pelo nuclei em um diretório personalizado (padrão "output")
-silent exibir apenas os resultados
-nc, -no-color desativar a coloração do conteúdo de saída (códigos de escape ANSI)
-j, -jsonl salvar a saída no formato JSONL(ines)
-irr, -include-rr -omit-raw incluir pares solicitação/resposta nas saídas JSON, JSONL e Markdown (apenas para achados) [OBSOLETO usar -omit-raw] (padrão true)
-or, -omit-raw omitir os pares solicitação/resposta nas saídas JSON, JSONL e Markdown (apenas para achados)
-ot, -omit-template omitir o template codificado na saída JSON, JSONL
-nm, -no-meta desativar a exibição de metadados dos resultados na saída CLI
-ts, -timestamp ativar a exibição do carimbo de data/hora na saída CLI
-rdb, -report-db string banco de dados de relatórios do nuclei (usar sempre para persistir os dados dos relatórios)
-ms, -matcher-status exibir o estado de falha de correspondência
-me, -markdown-export string diretório para exportar resultados no formato Markdown
-se, -sarif-export string arquivo para exportar resultados no formato SARIF
-je, -json-export string arquivo para exportar resultados no formato JSON
-jle, -jsonl-export string arquivo para exportar resultados no formato JSONL(ines)
CONFIGURATIONS:
-config string caminho do arquivo de configuração do nuclei
-fr, -follow-redirects ativar o acompanhamento de redirecionamentos para templates HTTP
-fhr, -follow-host-redirects seguir redirecionamentos no mesmo host
-mr, -max-redirects int número máximo de redirecionamentos a seguir para templates HTTP (padrão 10)
-dr, -disable-redirects desativar redirecionamentos para templates HTTP
-rc, -report-config string arquivo de configuração do módulo de relatórios do nuclei
-H, -header string[] cabeçalho/cookie personalizado a incluir em todas as solicitações HTTP no formato header:value (CLI, arquivo)
-V, -var value variáveis personalizadas no formato key=value
-r, -resolvers string arquivo contendo uma lista de resolvers para o nuclei
-sr, -system-resolvers usar resolução DNS do sistema como fallback em caso de erro
-dc, -disable-clustering desativar o agrupamento de solicitações
-passive ativar o modo de processamento passivo de respostas HTTP
-fh2, -force-http2 forçar conexões HTTP2 nas solicitações
-ev, -env-vars ativar o uso de variáveis de ambiente no template
-cc, -client-cert string arquivo de certificado de cliente (codificado em PEM) usado para autenticar-se contra os hosts escaneados
-ck, -client-key string arquivo de chave de cliente (codificado em PEM) usado para autenticar-se contra os hosts escaneados
-ca, -client-ca string arquivo de autoridade de certificação de cliente (codificado em PEM) usado para autenticar-se contra os hosts escaneados
-sml, -show-match-line exibir linhas de correspondência para templates de arquivo, funciona apenas com extratores
-ztls usar a biblioteca ztls com fallback automático para padrão no tls13 [OBSOLETO] fallback automático para ztls já está ativado por padrão
-sni string nome de host tls sni a ser usado (padrão: nome de domínio de entrada)
-dt, -dialer-timeout value tempo limite para solicitações de rede
-dka, -dialer-keep-alive value duração do keep-alive para solicitações de rede
-lfa, -allow-local-file-access permitir acesso a arquivos (payload) em qualquer lugar do sistema
-lna, -restrict-local-network-access bloquear conexões à rede local/privada
-i, -interface string interface de rede a ser usada para o escaneamento de rede
-at, -attack-type string tipo de combinações de payload a realizar (batteringram, pitchfork, clusterbomb)
-sip, -source-ip string endereço IP de origem a ser usado para o escaneamento de rede
-rsr, -response-size-read int tamanho máximo de resposta a ser lido em bytes (padrão 10485760)
-rss, -response-size-save int tamanho máximo de resposta a ser salvo em bytes (padrão 1048576)
-reset remove todos os arquivos de configuração e dados do nuclei (incluindo os nuclei-templates)
-tlsi, -tls-impersonate ativar randomização experimental do client hello (ja3) tls
INTERACTSH:
-iserver, -interactsh-server string URL do servidor interactsh para instância auto-hospedada (padrão: oast.pro,oast.live,oast.site,oast.online,oast.fun,oast.me)
-itoken, -interactsh-token string token de autenticação para o servidor interactsh auto-hospedado
-interactions-cache-size int número de solicitações a serem mantidas no cache de interações (padrão 5000)
-interactions-eviction int número de segundos a esperar antes de remover solicitações do cache (padrão 60)
-interactions-poll-duration int número de segundos a esperar antes de cada solicitação de polling de interações (padrão 5)
-interactions-cooldown-period int tempo adicional para o polling de interações antes de encerrar (padrão 5)
-ni, -no-interactsh desativar o servidor interactsh para testes OAST, excluir templates baseados em OAST
FUZZING:
-ft, -fuzzing-type string sobrescreve o tipo de fuzzing definido no template (replace, prefix, postfix, infix)
-fm, -fuzzing-mode string sobrescreve o modo de fuzzing definido no template (multiple, single)
-fuzz habilita o carregamento de templates de fuzzing (Obsoleto: usar -dast em vez disso)
-dast executa apenas templates DAST
UNCOVER:
-uc, -uncover ativa o motor uncover
-uq, -uncover-query string[] consulta de busca uncover
-ue, -uncover-engine string[] motor de busca uncover (shodan,censys,fofa,shodan-idb,quake,hunter,zoomeye,netlas,criminalip,publicwww,hunterhow) (padrão shodan)
-uf, -uncover-field string campos uncover a serem retornados (ip,port,host) (padrão "ip:port")
-ul, -uncover-limit int resultados uncover a serem retornados (padrão 100)
-ur, -uncover-ratelimit int sobrescreve o limite de taxa dos motores com o limite de taxa do motor uncover (padrão 60 req/min)
RATE-LIMIT:
-rl, -rate-limit int número máximo de solicitações a serem enviadas por segundo (padrão 150)
-rlm, -rate-limit-minute int número máximo de solicitações a serem enviadas por minuto
-bs, -bulk-size int número máximo de hosts a serem analisados em paralelo por template (padrão 25)
-c, -concurrency int número máximo de templates a serem executados em paralelo (padrão 25)
-hbs, -headless-bulk-size int número máximo de hosts headless a serem analisados em paralelo por template (padrão 10)
-headc, -headless-concurrency int número máximo de templates headless a serem executados em paralelo (padrão 10)
-jsc, -js-concurrency int número máximo de ambientes de execução de JavaScript a serem executados em paralelo (padrão 120)
-pc, -payload-concurrency int concorrência máxima de payload para cada template (padrão 25)
-tlc, -template-loading-concurrency int número máximo de operações de carregamento de templates concorrentes (padrão 50)
OPTIMIZATIONS:
-timeout int tempo limite em segundos (padrão 10)
-retries int número de tentativas para solicitações com falha (padrão 1)
-ldp, -leave-default-ports manter as portas HTTP/HTTPS padrão (exemplo: host:80, host:443)
-mhe, -max-host-error int número máximo de erros para um host antes de ignorá-lo no scan (padrão 30)
-te, -track-error string[] adiciona o erro especificado à lista de rastreamento de erros máximos por host (standard, file)
-nmhe, -no-mhe desativa a exclusão de hosts do scan com base em erros
-project utiliza uma pasta de projeto para evitar enviar a mesma solicitação várias vezes
-project-path string define um caminho específico para o projeto (padrão "/tmp")
-spm, -stop-at-first-match interrompe o processamento de solicitações HTTP após a primeira correspondência (pode quebrar a lógica de templates/fluxos de trabalho)
-stream modo de transmissão - começa a trabalhar sem ordenar a entrada
-ss, -scan-strategy value estratégia a ser usada durante o scan (auto/host-spray/template-spray) (padrão auto)
-irt, -input-read-timeout value tempo limite para leitura da entrada (padrão 3m0s)
-nh, -no-httpx desativa a análise httpx para entradas que não sejam URLs
-no-stdin desativa o processamento de entrada padrão
HEADLESS:
-headless habilita templates que requerem suporte para navegadores sem interface gráfica (headless browser) (o usuário root no Linux desativará o sandbox)
-page-timeout int segundos para esperar cada página no modo headless (padrão 20)
-sb, -show-browser exibe o navegador na tela ao executar templates no modo headless
-ho, -headless-options string[] inicia o Chrome no modo headless com opções adicionais
-sc, -system-chrome utiliza o navegador Chrome instalado localmente em vez do instalado pelo nuclei
-cdpe, -cdp-endpoint string usar navegador remoto via endpoint do Protocolo de Ferramentas de Desenvolvedor do Chrome (CDP)
-lha, -list-headless-action lista ações disponíveis para o modo headless
DEBUG:
-debug exibe todas as solicitações e respostas
-dreq, -debug-req exibe todas as solicitações enviadas
-dresp, -debug-resp exibe todas as respostas recebidas
-p, -proxy string[] lista de proxies HTTP/SOCKS5 a serem usados (separados por vírgulas ou arquivo de entrada)
-pi, -proxy-internal proxy para todas as solicitações internas
-ldf, -list-dsl-function lista todas as assinaturas de funções DSL suportadas
-tlog, -trace-log string arquivo para gravar o log de rastreamento de solicitações enviadas
-elog, -error-log string arquivo para gravar o log de erros de solicitações enviadas
-version exibe a versão do nuclei
-hm, -hang-monitor ativa o monitoramento de travamentos do nuclei
-v, -verbose exibe saída detalhada
-profile-mem string arquivo opcional para despejo de memória do nuclei
-vv exibe os templates carregados para o scan
-svd, -show-var-dump exibe o dump de variáveis para depuração
-ep, -enable-pprof ativa o servidor de depuração pprof
-tv, -templates-version exibe a versão dos templates do nuclei (nuclei-templates) instalados
-hc, -health-check executa verificações de diagnóstico
UPDATE:
-up, -update atualiza o mecanismo do nuclei para a última versão lançada
-ut, -update-templates atualiza os nuclei-templates para a última versão lançada
-ud, -update-template-dir string diretório personalizado para instalar/atualizar os nuclei-templates
-duc, -disable-update-check desativa a verificação automática de atualizações do nuclei/templates
STATISTICS:
-stats exibe estatísticas sobre o scan em execução
-sj, -stats-json exibe estatísticas no formato JSONL(ines)
-si, -stats-interval int número de segundos a esperar entre as atualizações de estatísticas (padrão 5)
-mp, -metrics-port int porta para expor métricas do nuclei (padrão 9092)
CLOUD:
-auth configura a chave de API do cloud do ProjectDiscovery (pdcp)
-cup, -cloud-upload faz upload dos resultados do scan para o dashboard do pdcp
-sid, -scan-id string faz upload dos resultados do scan para o ID de scan fornecido
AUTHENTICATION:
-sf, -secret-file string[] caminho para o arquivo de configuração contendo os secrets para o scan autenticado do nuclei
-ps, -prefetch-secrets pré-carrega os secrets do arquivo de secrets
EXAMPLES:
Executar nuclei em um único host:
$ nuclei -target example.com
Executar nuclei com diretórios específicos de templates:
$ nuclei -target example.com -t http/cves/ -t ssl
Executar nuclei contra uma lista de hosts:
$ nuclei -list hosts.txt
Executar nuclei com saída JSON:
$ nuclei -target example.com -json-export output.json
Executar nuclei com saídas Markdown organizadas (com variáveis de ambiente):
$ MARKDOWN_EXPORT_SORT_MODE=template nuclei -target example.com -markdown-export nuclei_report/
Documentação adicional disponível em: https://docs.projectdiscovery.io/getting-started/running
```
### Executando Nuclei
Consulte https://docs.projectdiscovery.io/tools/nuclei/running para obter detalhes sobre como executar o Nuclei.
### Uso de Nuclei com código Go
O guia completo sobre como usar o Nuclei como biblioteca/SDK está disponível em [godoc](https://pkg.go.dev/github.com/projectdiscovery/nuclei/v3/lib#section-readme).
### Recursos
Você pode acessar a documentação principal do Nuclei em https://docs.projectdiscovery.io/tools/nuclei/ e obter mais informações sobre o Nuclei na nuvem com a [ProjectDiscovery Cloud Platform](https://cloud.projectdiscovery.io).
Consulte https://docs.projectdiscovery.io/tools/nuclei/resources para acessaar mais recursos e vídeos sobre o Nuclei!
### Créditos
Obrigado a todos os incríveis [contribuidores da comunidade que enviaram em PRs](https://github.com/projectdiscovery/nuclei/graphs/contributors) e mantêm este projeto atualizado. :heart:
Se você tem uma ideia ou algum tipo de melhoria, sinta-se à vontade para contribuir e participar do projeto. Envie seu PR!
Confira também os seguintes projetos de código aberto que podem se adequar ao seu fluxo de trabalho:
[FFuF](https://github.com/ffuf/ffuf), [Qsfuzz](https://github.com/ameenmaali/qsfuzz), [Inception](https://github.com/proabiral/inception), [Snallygaster](https://github.com/hannob/snallygaster), [Gofingerprint](https://github.com/Static-Flow/gofingerprint), [Sn1per](https://github.com/1N3/Sn1per/tree/master/templates), [Google tsunami](https://github.com/google/tsunami-security-scanner), [Jaeles](https://github.com/jaeles-project/jaeles), [ChopChop](https://github.com/michelin/ChopChop)
### Licença
O Nuclei é distribuído sob a [Licença MIT](https://github.com/projectdiscovery/nuclei/blob/main/LICENSE.md)
**Nuclei, basit YAML tabanlı şablonlardan yararlanan modern, yüksek performanslı bir zafiyet tarayıcısıdır. Gerçek dünya koşullarını taklit eden özel zafiyet tespit senaryoları tasarlamanıza olanak tanıyarak sıfır hatalı pozitif sonuç sağlar.**
- Güvenlik açığı şablonları oluşturmak ve özelleştirmek için basit YAML formatı.
- Trend olan güvenlik açıklarını ele almak için binlerce güvenlik uzmanı tarafından katkıda bulunulmuştur.
- Bir güvenlik açığını doğrulamak için gerçek dünya adımlarını simüle ederek hatalı pozitifleri azaltır.
- Ultra hızlı paralel tarama işleme ve istek kümeleme.
- Zafiyet tespiti ve regresyon testi için CI/CD hatlarına entegre edilebilir.
- TCP, DNS, HTTP, SSL, WHOIS, JavaScript, Code ve daha fazlası gibi birçok protokolü destekler.
- Jira, Splunk, GitHub, Elastic, GitLab ile entegre olur.
## Başlarken
### **1. Nuclei CLI**
_Nuclei'yi makinenize kurun. [**`Buradaki`**](https://docs.projectdiscovery.io/tools/nuclei/install?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme) kurulum kılavuzunu takip ederek başlayın. Ayrıca, [**`ücretsiz bir bulut katmanı`**](https://cloud.projectdiscovery.io/sign-up) sağlıyoruz ve cömert aylık ücretsiz limitlerle birlikte geliyor:_
- Zafiyet bulgularınızı saklayın ve görselleştirin
- nuclei şablonlarınızı yazın ve yönetin
- En son nuclei şablonlarına erişin
- Hedeflerinizi keşfedin ve saklayın
> [!Important]
> |**Bu proje aktif geliştirme aşamasındadır**. Sürümlerle birlikte kırılma değişiklikleri bekleyin. Güncellemeden önce sürüm değişiklik günlüğünü inceleyin.|
> |:--------------------------------|
> | Bu proje öncelikle bağımsız bir CLI aracı olarak kullanılmak üzere oluşturulmuştur. **Nuclei'yi bir servis olarak çalıştırmak güvenlik riskleri oluşturabilir.** Dikkatli kullanılması ve ek güvenlik önlemleri alınması önerilir. |
### **2. Pro ve Kurumsal Sürümler**
_Güvenlik ekipleri ve kuruluşlar için, ekibiniz ve mevcut iş akışlarınızla ölçekli olarak sürekli zafiyet taramaları yapmanıza yardımcı olmak üzere ince ayarlanmış, Nuclei OSS üzerine inşa edilmiş bulut tabanlı bir hizmet sunuyoruz:_
- 50x daha hızlı taramalar
- Yüksek doğrulukla büyük ölçekli tarama
- Bulut hizmetleri ile entegrasyonlar (AWS, GCP, Azure, Cloudflare, Fastly, Terraform, Kubernetes)
- Jira, Slack, Linear, API'ler ve Webhook'lar
- Yönetici ve uyumluluk raporlaması
- Artı: Gerçek zamanlı tarama, SAML SSO, SOC 2 uyumlu platform (AB ve ABD barındırma seçenekleri ile), paylaşılan ekip çalışma alanları ve daha fazlası
- Sürekli olarak [**`yeni özellikler ekliyoruz`**](https://feedback.projectdiscovery.io/changelog)!
- **Şunlar için ideal:** Sızma testi yapanlar, güvenlik ekipleri ve kuruluşlar
Büyük bir organizasyonunuz ve karmaşık gereksinimleriniz varsa [**`Pro'ya kaydolun`**](https://projectdiscovery.io/pricing?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme) veya [**`ekibimizle konuşun`**](https://projectdiscovery.io/request-demo?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme).
## Dokümantasyon
Nuclei'nin tam [**`dokümantasyonuna buradan`**](https://docs.projectdiscovery.io/tools/nuclei/running) göz atın. Nuclei'de yeniyseniz, [**`temel YouTube serimize`**](https://www.youtube.com/playlist?list=PLZRbR9aMzTTpItEdeNSulo8bYsvil80Rl) göz atın.
### Kurulum
`nuclei` yüklemek için **go >= 1.24.2** gerektirir. Repoyu almak için aşağıdaki komutu çalıştırın:
```sh
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
```
Nuclei kurulumu hakkında daha fazla bilgi edinmek için `https://docs.projectdiscovery.io/tools/nuclei/install` adresine bakın.
### Komut Satırı Bayrakları
Aracın tüm bayraklarını görüntülemek için:
```sh
nuclei -h
```
Tüm yardım bayraklarını genişlet
```yaml
Nuclei, kapsamlı yapılandırılabilirlik, devasa genişletilebilirlik ve kullanım kolaylığına odaklanan hızlı, şablon tabanlı bir zafiyet tarayıcısıdır.
Kullanım:
./nuclei [bayraklar]
Bayraklar:
TARGET:
-u, -target string[] taranacak hedef URL'ler/hostlar
-l, -list string taranacak hedef URL'leri/hostları içeren dosya yolu (her satırda bir tane)
-eh, -exclude-hosts string[] girilen listeden tarama dışında tutulacak hostlar (ip, cidr, hostname)
-resume string taramayı belirtilen dosyadan devam ettir ve kaydet (kümeleme devre dışı bırakılır)
-sa, -scan-all-ips dns kaydı ile ilişkili tüm IP'leri tara
-iv, -ip-version string[] taranacak hostun IP versiyonu (4,6) - (varsayılan 4)
TARGET-FORMAT:
-im, -input-mode string girdi dosyasının modu (list, burp, jsonl, yaml, openapi, swagger) (varsayılan "list")
-ro, -required-only istekler oluşturulurken girdi formatındaki sadece zorunlu alanları kullan
-sfv, -skip-format-validation girdi dosyasını ayrıştırırken format doğrulamasını atla (eksik değişkenler gibi)
TEMPLATES:
-nt, -new-templates sadece en son nuclei-templates sürümünde eklenen yeni şablonları çalıştır
-ntv, -new-templates-version string[] belirli bir sürümde eklenen yeni şablonları çalıştır
-as, -automatic-scan wappalyzer teknoloji tespiti ile etiket eşlemesini kullanarak otomatik web taraması
-t, -templates string[] çalıştırılacak şablon veya şablon dizini listesi (virgülle ayrılmış, dosya)
-turl, -template-url string[] çalıştırılacak şablon url'si veya şablon url'lerini içeren liste (virgülle ayrılmış, dosya)
-ai, -prompt string yapay zeka istemi kullanarak şablon oluştur ve çalıştır
-w, -workflows string[] çalıştırılacak iş akışı veya iş akışı dizini listesi (virgülle ayrılmış, dosya)
-wurl, -workflow-url string[] çalıştırılacak iş akışı url'si veya iş akışı url'lerini içeren liste (virgülle ayrılmış, dosya)
-validate nuclei'ye iletilen şablonları doğrula
-nss, -no-strict-syntax şablonlarda katı sözdizimi kontrolünü devre dışı bırak
-td, -template-display şablon içeriğini görüntüler
-tl mevcut filtrelerle eşleşen tüm şablonları listele
-tgl tüm mevcut etiketleri listele
-sign şablonları NUCLEI_SIGNATURE_PRIVATE_KEY ortam değişkeninde tanımlanan özel anahtarla imzala
-code kod protokolü tabanlı şablonların yüklenmesini etkinleştir
-dut, -disable-unsigned-templates imzasız şablonların veya imzası eşleşmeyen şablonların çalıştırılmasını devre dışı bırak
-esc, -enable-self-contained kendi kendine yeten (self-contained) şablonların yüklenmesini etkinleştir
-egm, -enable-global-matchers global eşleştirici şablonların yüklenmesini etkinleştir
-file dosya şablonlarının yüklenmesini etkinleştir
... (Diğer bayraklar orijinalindeki gibi, tam çeviri için çok uzun olabilir, ancak bağlam için yeterli)
```
Ek dokümantasyon şu adreste mevcuttur: [**`docs.projectdiscovery.io/getting-started/running`**](https://docs.projectdiscovery.io/getting-started/running?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme)
### Tek hedef tarama
Web uygulamasında hızlı bir tarama yapmak için:
```sh
nuclei -target https://example.com
```
### Çoklu hedef tarama
Nuclei, bir hedef listesi sağlayarak toplu taramayı gerçekleştirebilir. Birden fazla URL içeren bir dosya kullanabilirsiniz.
```sh
nuclei -list urls.txt
```
### Ağ taraması
Bu, açık portlar veya yanlış yapılandırılmış servisler gibi ağla ilgili sorunlar için tüm alt ağı tarayacaktır.
```sh
nuclei -target 192.168.1.0/24
```
### Özel şablonunuzla tarama
Kendi şablonunuzu yazmak ve kullanmak için, belirli kurallara sahip bir `.yaml` dosyası oluşturun ve ardından aşağıdaki gibi kullanın.
```sh
nuclei -u https://example.com -t /path/to/your-template.yaml
```
### Nuclei'yi ProjectDiscovery'ye Bağlayın
Taramaları makinenizde çalıştırabilir ve sonuçları daha fazla analiz ve düzeltme için bulut platformuna yükleyebilirsiniz.
```sh
nuclei -target https://example.com -dashboard
```
> [!NOTE]
> Bu özellik tamamen ücretsizdir ve herhangi bir abonelik gerektirmez. Ayrıntılı bir kılavuz için [**`dokümantasyona`**](https://docs.projectdiscovery.io/cloud/scanning/nuclei-scan?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme) bakın.
## Nuclei Şablonları, Topluluk ve Ödüller 💎
[**Nuclei şablonları**](https://github.com/projectdiscovery/nuclei-templates), isteklerin nasıl gönderileceğini ve işleneceğini tanımlayan YAML tabanlı şablon dosyaları kavramına dayanır. Bu, nuclei'ye kolay genişletilebilirlik yetenekleri sağlar. Şablonlar, yürütme sürecini hızlı bir şekilde tanımlamak için insan tarafından okunabilir basit bir format belirten YAML ile yazılmıştır.
**[**`Buraya tıklayarak`**](https://cloud.projectdiscovery.io/templates) ücretsiz yapay zeka destekli Nuclei Şablon Editörü ile çevrimiçi deneyin.**
Nuclei Şablonları, önem dereceleri ve tespit yöntemleri gibi temel ayrıntıları birleştirerek güvenlik açıklarını tanımlamak ve iletmek için akıcı bir yol sunar. Bu açık kaynaklı, topluluk tarafından geliştirilen araç, tehdit yanıtını hızlandırır ve siber güvenlik dünyasında geniş çapta tanınmaktadır. Nuclei şablonları, dünya çapında binlerce güvenlik araştırmacısı tarafından aktif olarak katkıda bulunulmaktadır. Katılımcılarımız için iki program yürütüyoruz: [**`Öncüler (Pioneers)`**](https://projectdiscovery.io/pioneers) ve [**`💎 ödüller`**](https://github.com/projectdiscovery/nuclei-templates/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22%F0%9F%92%8E%20Bounty%22).
#### Örnekler
Kullanım durumları ve fikirler için [**dokümantasyonumuzu**](https://docs.projectdiscovery.io/templates/introduction) ziyaret edin.
| Kullanım durumu | Nuclei şablonu |
| :----------------------------------- | :------------------------------------------------- |
| Bilinen CVE'leri tespit et | **[CVE-2021-44228 (Log4Shell)](https://cloud.projectdiscovery.io/public/CVE-2021-45046)** |
| Bant Dışı (Out-of-Band) zafiyetlerini belirle | **[Blind SQL Injection via OOB](https://cloud.projectdiscovery.io/public/CVE-2024-22120)** |
| SQL Injection tespiti | **[Generic SQL Injection](https://cloud.projectdiscovery.io/public/CVE-2022-34265)** |
| Siteler Arası Komut Dosyası Çalıştırma (XSS) | **[Reflected XSS Detection](https://cloud.projectdiscovery.io/public/CVE-2023-4173)** |
| Varsayılan veya zayıf şifreler | **[Default Credentials Check](https://cloud.projectdiscovery.io/public/airflow-default-login)** |
| Gizli dosyalar veya veri ifşası | **[Sensitive File Disclosure](https://cloud.projectdiscovery.io/public/airflow-configuration-exposure)** |
| Açık yönlendirmeleri (open redirects) belirle | **[Open Redirect Detection](https://cloud.projectdiscovery.io/public/open-redirect)** |
| Alt alan adı devralmalarını (takeover) tespit et | **[Subdomain Takeover Templates](https://cloud.projectdiscovery.io/public/azure-takeover-detection)** |
| Güvenlik yanlış yapılandırmaları | **[Unprotected Jenkins Console](https://cloud.projectdiscovery.io/public/unauthenticated-jenkins)** |
| Zayıf SSL/TLS yapılandırmaları | **[SSL Certificate Expiry](https://cloud.projectdiscovery.io/public/expired-ssl)** |
| Yanlış yapılandırılmış bulut hizmetleri | **[Open S3 Bucket Detection](https://cloud.projectdiscovery.io/public/s3-public-read-acp)** |
| Uzaktan kod yürütme zafiyetleri | **[RCE Detection Templates](https://cloud.projectdiscovery.io/public/CVE-2024-29824)** |
| Dizin geçiş (path traversal) saldırıları | **[Path Traversal Detection](https://cloud.projectdiscovery.io/public/oracle-fatwire-lfi)** |
| Dosya dahil etme (file inclusion) zafiyetleri | **[Local/Remote File Inclusion](https://cloud.projectdiscovery.io/public/CVE-2023-6977)** |
## Misyonumuz
Geleneksel zafiyet tarayıcıları on yıllar önce inşa edildi. Kapalı kaynaklıdırlar, inanılmaz derecede yavaştırlar ve satıcı odaklıdırlar. Günümüzün saldırganları, eskiden yıllar süren süreçlerin aksine, yeni yayınlanan CVE'leri günler içinde internet genelinde kitlesel olarak istismar ediyor. Bu değişim, internetteki trend olan istismarlarla mücadele etmek için tamamen farklı bir yaklaşım gerektiriyor.
Bu zorluğu çözmek için Nuclei'yi inşa ettik. Tüm tarama motoru çerçevesini açık ve özelleştirilebilir hale getirdik; bu sayede küresel güvenlik topluluğunun işbirliği yapmasına ve internet üzerindeki trend saldırı vektörlerini ve zafiyetlerini ele almasına olanak tanıdık. Nuclei artık Fortune 500 şirketleri, devlet kurumları ve üniversiteler tarafından kullanılmakta ve katkıda bulunulmaktadır.
Kodumuza, [**`şablon kitaplığımıza`**](https://github.com/projectdiscovery/nuclei-templates) katkıda bulunarak veya [**`ekibimize katılarak`**](https://projectdiscovery.io/) siz de yer alabilirsiniz.
## Katkıda Bulunanlar :heart:
Projeyi güncel tuttukları ve [**`PR gönderdikleri için harika topluluk katkıda bulunanlara`**](https://github.com/projectdiscovery/nuclei/graphs/contributors) teşekkür ederiz. :heart:
(Katkıda bulunanların listesi orijinalindeki gibi korunmuştur)
...
**`nuclei`** [**MIT Lisansı**](https://github.com/projectdiscovery/nuclei/blob/main/LICENSE.md) altında dağıtılmaktadır.
================================================
FILE: SYNTAX-REFERENCE.md
================================================
## Template
Template is a YAML input file which defines all the requests and
other metadata for a template.
idstring
ID is the unique id for the template.
#### Good IDs
A good ID uniquely identifies what the requests in the template
are doing. Let's say you have a template that identifies a git-config
file on the webservers, a good name would be `git-config-exposure`. Another
example name is `azure-apps-nxdomain-takeover`.
Examples:
```yaml
# ID Example
id: CVE-2021-19520
```
Info contains metadata information about the template.
Examples:
```yaml
info:
name: Argument Injection in Ruby Dragonfly
author: 0xspara
tags: cve,cve2021,rce,ruby
reference: https://zxsecurity.co.nz/research/argunment-injection-ruby-dragonfly/
severity: high
```
flowstring
description: |
Flow contains the execution flow for the template.
examples:
- flow: |
for region in regions {
http(0)
}
for vpc in vpcs {
http(1)
}
Requests contains the http request to make in the template.
WARNING: 'requests' will be deprecated and will be removed in a future release. Please use 'http' instead.
Examples:
```yaml
requests:
matchers:
- type: word
words:
- '[core]'
- type: dsl
condition: and
dsl:
- '!contains(tolower(body), ''
description: |
HTTP contains the http request to make in the template.
examples:
- value: exampleNormalHTTPRequest
RequestsWithHTTP is placeholder(internal) only, and should not be used instead use RequestsHTTP
Deprecated: Use RequestsHTTP instead.
DNS contains the dns request to make in the template
Examples:
```yaml
dns:
extractors:
- type: regex
regex:
- ec2-[-\d]+\.compute[-\d]*\.amazonaws\.com
- ec2-[-\d]+\.[\w\d\-]+\.compute[-\d]*\.amazonaws\.com
name: '{{FQDN}}'
type: CNAME
class: inet
retries: 2
recursion: false
```
File contains the file request to make in the template
Examples:
```yaml
file:
extractors:
- type: regex
regex:
- amzn\.mws\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
extensions:
- all
```
Network contains the network request to make in the template
WARNING: 'network' will be deprecated and will be removed in a future release. Please use 'tcp' instead.
Examples:
```yaml
network:
host:
- '{{Hostname}}'
- '{{Hostname}}:2181'
inputs:
- data: "envi\r\nquit\r\n"
read-size: 2048
matchers:
- type: word
words:
- zookeeper.version
```
description: |
TCP contains the network request to make in the template
examples:
- value: exampleNormalNetworkRequest
RequestsWithTCP is placeholder(internal) only, and should not be used instead use RequestsNetwork
Deprecated: Use RequestsNetwork instead.
Signature is the request signature method
WARNING: 'signature' will be deprecated and will be removed in a future release. Prefer using 'code' protocol for writing cloud checks
Valid values:
- AWS
Variables contains any variables for the current request.
constantsmap[string]interface{}
Constants contains any scalar constant for the current template
## model.Info
Info contains metadata information about a template
Appears in:
- Template.info
```yaml
name: Argument Injection in Ruby Dragonfly
author: 0xspara
tags: cve,cve2021,rce,ruby
reference: https://zxsecurity.co.nz/research/argunment-injection-ruby-dragonfly/
severity: high
```
namestring
Name should be good short summary that identifies what the template does.
Examples:
```yaml
name: bower.json file disclosure
```
```yaml
name: Nagios Default Credentials Check
```
Any tags for the template.
Multiple values can also be specified separated by commas.
Examples:
```yaml
# Example tags
tags: cve,cve2019,grafana,auth-bypass,dos
```
descriptionstring
Description of the template.
You can go in-depth here on what the template actually does.
Examples:
```yaml
description: Bower is a package manager which stores package information in the bower.json file
```
```yaml
description: Subversion ALM for the enterprise before 8.8.2 allows reflected XSS at multiple locations
```
impactstring
Impact of the template.
You can go in-depth here on impact of the template.
Examples:
```yaml
impact: Successful exploitation of this vulnerability could allow an attacker to execute arbitrary SQL queries, potentially leading to unauthorized access, data leakage, or data manipulation.
```
```yaml
impact: Successful exploitation of this vulnerability could allow an attacker to execute arbitrary script code in the context of the victim's browser, potentially leading to session hijacking, defacement, or theft of sensitive information.
```
References for the template.
This should contain links relevant to the template.
Examples:
```yaml
reference:
- https://github.com/strapi/strapi
- https://github.com/getgrav/grav
```
Classification contains classification information about the template.
remediationstring
Remediation steps for the template.
You can go in-depth here on how to mitigate the problem found by this template.
Examples:
```yaml
remediation: Change the default administrative username and password of Apache ActiveMQ by editing the file jetty-realm.properties
```
## stringslice.StringSlice
StringSlice represents a single (in-lined) or multiple string value(s).
The unmarshaller does not automatically convert in-lined strings to []string, hence the interface{} type is required.
Appears in:
- model.Info.author
- model.Info.tags
- model.Classification.cve-id
- model.Classification.cwe-id
```yaml
```
```yaml
# Example tags
cve,cve2019,grafana,auth-bypass,dos
```
```yaml
CVE-2020-14420
```
```yaml
CWE-22
```
## stringslice.RawStringSlice
Appears in:
- model.Info.reference
```yaml
- https://github.com/strapi/strapi
- https://github.com/getgrav/grav
```
## severity.Holder
Holder holds a Severity type. Required for un/marshalling purposes
Appears in:
- model.Info.severity
Severity
Enum Values:
- undefined
- info
- low
- medium
- high
- critical
- unknown
CWE ID for the template.
Examples:
```yaml
cwe-id: CWE-22
```
cvss-metricsstring
CVSS Metrics for the template.
Examples:
```yaml
cvss-metrics: 3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
```
cvss-scorefloat64
CVSS Score for the template.
Examples:
```yaml
cvss-score: "9.8"
```
epss-scorefloat64
EPSS Score for the template.
Examples:
```yaml
epss-score: "0.42509"
```
epss-percentilefloat64
EPSS Percentile for the template.
Examples:
```yaml
epss-percentile: "0.42509"
```
cpestring
CPE for the template.
Examples:
```yaml
cpe: cpe:/a:vendor:product:version
```
## http.Request
Request contains a http request to be made from a template
Appears in:
- Template.requests
- Template.http
```yaml
matchers:
- type: word
words:
- '[core]'
- type: dsl
condition: and
dsl:
- '!contains(tolower(body), ''template-id - ID of the template executed
- template-info - Info Block of the template executed
- template-path - Path of the template executed
- host - Host is the input to the template
- matched - Matched is the input which was matched upon
- type - Type is the type of request made
- request - HTTP request made from the client
- response - HTTP response received from server
- status_code - Status Code received from the Server
- body - HTTP response body received from server (default)
- content_length - HTTP Response content length
- header,all_headers - HTTP response headers
- duration - HTTP request time duration
- all - HTTP response body + headers
- cookies_from_response - HTTP response cookies in name:value format
- headers_from_response - HTTP response headers in name:value format
path[]string
Path contains the path/s for the HTTP requests. It supports variables
as placeholders.
Examples:
```yaml
# Some example path values
path:
- '{{BaseURL}}'
- '{{BaseURL}}/+CSCOU+/../+CSCOE+/files/file_list.json?path=/sessions'
```
raw[]string
Raw contains HTTP Requests in Raw format.
Examples:
```yaml
# Some example raw requests
raw:
- |-
GET /etc/passwd HTTP/1.1
Host:
Content-Length: 4
- |-
POST /.%0d./.%0d./.%0d./.%0d./bin/sh HTTP/1.1
Host: {{Hostname}}
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0
Content-Length: 1
Connection: close
echo
echo
cat /etc/passwd 2>&1
```
idstring
ID is the optional id of the request
namestring
Name is the optional name of the request.
If a name is specified, all the named request in a template can be matched upon
in a combined manner allowing multi-request based matchers.
Attack is the type of payload combinations to perform.
batteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates
permutations and combinations for all payloads.
Valid values:
- batteringram
- pitchfork
- clusterbomb
Body is an optional parameter which contains HTTP Request body.
Examples:
```yaml
# Same Body for a Login POST request
body: username=test&password=test
```
payloadsmap[string]interface{}
Payloads contains any payloads for the current request.
Payloads support both key-values combinations where a list
of payloads is provided, or optionally a single file can also
be provided as payload which will be read on run-time.
headersmap[string]string
Headers contains HTTP Headers to send with the request.
Examples:
```yaml
headers:
Any-Header: Any-Value
Content-Length: "1"
Content-Type: application/x-www-form-urlencoded
```
race_countint
RaceCount is the number of times to send a request in Race Condition Attack.
Examples:
```yaml
# Send a request 5 times
race_count: 5
```
max-redirectsint
MaxRedirects is the maximum number of redirects that should be followed.
Examples:
```yaml
# Follow up to 5 redirects
max-redirects: 5
```
pipeline-concurrent-connectionsint
PipelineConcurrentConnections is number of connections to create during pipelining.
Examples:
```yaml
# Create 40 concurrent connections
pipeline-concurrent-connections: 40
```
pipeline-requests-per-connectionint
PipelineRequestsPerConnection is number of requests to send per connection when pipelining.
Examples:
```yaml
# Send 100 requests per pipeline connection
pipeline-requests-per-connection: 100
```
threadsint
Threads specifies number of threads to use sending requests. This enables Connection Pooling.
Connection: Close attribute must not be used in request while using threads flag, otherwise
pooling will fail and engine will continue to close connections after requests.
Examples:
```yaml
# Send requests using 10 concurrent threads
threads: 10
```
max-sizeint
MaxSize is the maximum size of http response body to read in bytes.
Examples:
```yaml
# Read max 2048 bytes of the response
max-size: 2048
```
Signature is the request signature method
Valid values:
- AWS
skip-secret-filebool
SkipSecretFile skips the authentication or authorization configured in the secret file.
cookie-reusebool
CookieReuse is an optional setting that enables cookie reuse for
all requests defined in raw section.
disable-cookiebool
DisableCookie is an optional setting that disables cookie reuse
read-allbool
Enables force reading of the entire raw unsafe request body ignoring
any specified content length headers.
redirectsbool
Redirects specifies whether redirects should be followed by the HTTP Client.
This can be used in conjunction with `max-redirects` to control the HTTP request redirects.
host-redirectsbool
Redirects specifies whether only redirects to the same host should be followed by the HTTP Client.
This can be used in conjunction with `max-redirects` to control the HTTP request redirects.
pipelinebool
Pipeline defines if the attack should be performed with HTTP 1.1 Pipelining
All requests must be idempotent (GET/POST). This can be used for race conditions/billions requests.
unsafebool
Unsafe specifies whether to use rawhttp engine for sending Non RFC-Compliant requests.
This uses the [rawhttp](https://github.com/projectdiscovery/rawhttp) engine to achieve complete
control over the request, with no normalization performed by the client.
racebool
Race determines if all the request have to be attempted at the same time (Race Condition)
The actual number of requests that will be sent is determined by the `race_count` field.
req-conditionbool
ReqCondition automatically assigns numbers to requests and preserves their history.
This allows matching on them later for multi-request conditions.
stop-at-first-matchbool
StopAtFirstMatch stops the execution of the requests and template as soon as a match is found.
skip-variables-checkbool
SkipVariablesCheck skips the check for unresolved variables in request
iterate-allbool
IterateAll iterates all the values extracted from internal extractors
digest-usernamestring
DigestAuthUsername specifies the username for digest authentication
digest-passwordstring
DigestAuthPassword specifies the password for digest authentication
disable-path-automergebool
DisablePathAutomerge disables merging target url path with raw request path
## HTTPMethodTypeHolder
HTTPMethodTypeHolder is used to hold internal type of the HTTP Method
Appears in:
- http.Request.method
HTTPMethodType
Enum Values:
- GET
- HEAD
- POST
- PUT
- DELETE
- CONNECT
- OPTIONS
- TRACE
- PATCH
- PURGE
- Debug
## fuzz.Rule
Rule is a single rule which describes how to fuzz the request
Appears in:
- http.Request.fuzzing
- headless.Request.fuzzing
typestring
Type is the type of fuzzing rule to perform.
replace replaces the values entirely. prefix prefixes the value. postfix postfixes the value
and infix places between the values.
Valid values:
- replace
- prefix
- postfix
- infix
partstring
Part is the part of request to fuzz.
Valid values:
- query
- header
- path
- body
- cookie
- request
parts[]string
Parts is the list of parts to fuzz. If multiple parts need to be
defined while excluding some, this should be used instead of singular part.
Valid values:
- query
- header
- path
- body
- cookie
- request
modestring
Mode is the mode of fuzzing to perform.
single fuzzes one value at a time. multiple fuzzes all values at same time.
Valid values:
- single
- multiple
keys[]string
Keys is the optional list of key named parameters to fuzz.
Examples:
```yaml
# Examples of keys
keys:
- url
- file
- host
```
keys-regex[]string
KeysRegex is the optional list of regex key parameters to fuzz.
Examples:
```yaml
# Examples of key regex
keys-regex:
- url.*
```
values[]string
Values is the optional list of regex value parameters to fuzz.
Examples:
```yaml
# Examples of value regex
values:
- https?://.*
```
description: |
Fuzz is the list of payloads to perform substitutions with.
examples:
- name: Examples of fuzz
value: >
[]string{"{{ssrf}}", "{{interactsh-url}}", "example-value"}
or
x-header: 1
x-header: 2
replace-regexstring
replace-regex is regex for regex-replace rule type
it is only required for replace-regex rule type
## SliceOrMapSlice
Appears in:
- fuzz.Rule.fuzz
## analyzers.AnalyzerTemplate
AnalyzerTemplate is the template for the analyzer
Appears in:
- http.Request.analyzer
namestring
Name is the name of the analyzer to use
Valid values:
- time_delay
parametersmap[string]interface{}
Parameters is the parameters for the analyzer
Parameters are different for each analyzer. For example, you can customize
time_delay analyzer with sleep_duration, time_slope_error_range, etc. Refer
to the docs for each analyzer to get an idea about parameters.
## SignatureTypeHolder
SignatureTypeHolder is used to hold internal type of the signature
Appears in:
- http.Request.signature
## matchers.Matcher
Matcher is used to match a part in the output from a protocol.
Appears in:
- http.Request.pre-condition
Condition is the optional condition between two matcher variables. By default,
the condition is assumed to be OR.
Valid values:
- and
- or
partstring
Part is the part of the request response to match data from.
Each protocol exposes a lot of different parts which are well
documented in docs for each request type.
Examples:
```yaml
part: body
```
```yaml
part: raw
```
negativebool
Negative specifies if the match should be reversed
It will only match if the condition is not true.
namestring
Name of the matcher. Name should be lowercase and must not contain
spaces or underscores (_).
Examples:
```yaml
name: cookie-matcher
```
status[]int
Status are the acceptable status codes for the response.
Examples:
```yaml
status:
- 200
- 302
```
size[]int
Size is the acceptable size for the response
Examples:
```yaml
size:
- 3029
- 2042
```
words[]string
Words contains word patterns required to be present in the response part.
Examples:
```yaml
# Match for Outlook mail protection domain
words:
- mail.protection.outlook.com
```
```yaml
# Match for application/json in response headers
words:
- application/json
```
regex[]string
Regex contains Regular Expression patterns required to be present in the response part.
Examples:
```yaml
# Match for Linkerd Service via Regex
regex:
- (?mi)^Via\\s*?:.*?linkerd.*$
```
```yaml
# Match for Open Redirect via Location header
regex:
- (?m)^(?:Location\\s*?:\\s*?)(?:https?://|//)?(?:[a-zA-Z0-9\\-_\\.@]*)example\\.com.*$
```
binary[]string
Binary are the binary patterns required to be present in the response part.
Examples:
```yaml
# Match for Springboot Heapdump Actuator "JAVA PROFILE", "HPROF", "Gunzip magic byte"
binary:
- 4a4156412050524f46494c45
- 4850524f46
- 1f8b080000000000
```
```yaml
# Match for 7zip files
binary:
- 377ABCAF271C
```
dsl[]string
DSL are the dsl expressions that will be evaluated as part of nuclei matching rules.
A list of these helper functions are available [here](https://nuclei.projectdiscovery.io/templating-guide/helper-functions/).
Examples:
```yaml
# DSL Matcher for package.json file
dsl:
- contains(body, 'packages') && contains(tolower(all_headers), 'application/octet-stream') && status_code == 200
```
```yaml
# DSL Matcher for missing strict transport security header
dsl:
- '!contains(tolower(all_headers), ''''strict-transport-security'''')'
```
xpath[]string
XPath are the xpath queries expressions that will be evaluated against the response part.
Examples:
```yaml
# XPath Matcher to check a title
xpath:
- /html/head/title[contains(text(), 'How to Find XPath')]
```
```yaml
# XPath Matcher for finding links with target="_blank"
xpath:
- //a[@target="_blank"]
```
encodingstring
Encoding specifies the encoding for the words field if any.
Valid values:
- hex
MatchAll enables matching for all matcher values. Default is false.
Valid values:
- false
- true
internalbool
description: |
Internal when true hides the matcher from output. Default is false.
It is meant to be used in multiprotocol / flow templates to create internal matcher condition without printing it in output.
or other similar use cases.
values:
- false
- true
## MatcherTypeHolder
MatcherTypeHolder is used to hold internal type of the matcher
Appears in:
- matchers.Matcher.type
MatcherType
Enum Values:
- word
- regex
- binary
- status
- size
- dsl
- xpath
## dns.Request
Request contains a DNS protocol request to be made from a template
Appears in:
- Template.dns
```yaml
extractors:
- type: regex
regex:
- ec2-[-\d]+\.compute[-\d]*\.amazonaws\.com
- ec2-[-\d]+\.[\w\d\-]+\.compute[-\d]*\.amazonaws\.com
name: '{{FQDN}}'
type: CNAME
class: inet
retries: 2
recursion: false
```
Part Definitions:
- template-id - ID of the template executed
- template-info - Info Block of the template executed
- template-path - Path of the template executed
- host - Host is the input to the template
- matched - Matched is the input which was matched upon
- request - Request contains the DNS request in text format
- type - Type is the type of request made
- rcode - Rcode field returned for the DNS request
- question - Question contains the DNS question field
- extra - Extra contains the DNS response extra field
- answer - Answer contains the DNS response answer field
- ns - NS contains the DNS response NS field
- raw,body,all - Raw contains the raw DNS response (default)
- trace - Trace contains trace data for DNS request if enabled
idstring
ID is the optional id of the request
namestring
Name is the Hostname to make DNS request for.
Generally, it is set to {{FQDN}} which is the domain we get from input.
Examples:
```yaml
name: '{{FQDN}}'
```
Class is the class of the DNS request.
Usually it's enough to just leave it as INET.
Valid values:
- inet
- csnet
- chaos
- hesiod
- none
- any
retriesint
Retries is the number of retries for the DNS request
Examples:
```yaml
# Use a retry of 3 to 5 generally
retries: 5
```
tracebool
Trace performs a trace operation for the target.
trace-max-recursionint
TraceMaxRecursion is the number of max recursion allowed for trace operations
Examples:
```yaml
# Use a retry of 100 to 150 generally
trace-max-recursion: 100
```
Attack is the type of payload combinations to perform.
Batteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates
permutations and combinations for all payloads.
payloadsmap[string]interface{}
Payloads contains any payloads for the current request.
Payloads support both key-values combinations where a list
of payloads is provided, or optionally a single file can also
be provided as payload which will be read on run-time.
threadsint
Threads to use when sending iterating over payloads
Examples:
```yaml
# Send requests using 10 concurrent threads
threads: 10
```
recursiondns.bool
Recursion determines if resolver should recurse all records to get fresh results.
resolvers[]string
Resolvers to use for the dns requests
## DNSRequestTypeHolder
DNSRequestTypeHolder is used to hold internal type of the DNS type
Appears in:
- dns.Request.type
DNSRequestType
Enum Values:
- A
- NS
- DS
- CNAME
- SOA
- PTR
- MX
- TXT
- AAAA
- CAA
- TLSA
- ANY
- SRV
## file.Request
Request contains a File matching mechanism for local disk operations.
Appears in:
- Template.file
```yaml
extractors:
- type: regex
regex:
- amzn\.mws\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
extensions:
- all
```
Part Definitions:
- template-id - ID of the template executed
- template-info - Info Block of the template executed
- template-path - Path of the template executed
- matched - Matched is the input which was matched upon
- path - Path is the path of file on local filesystem
- type - Type is the type of request made
- raw,body,all,data - Raw contains the raw file contents
extensions[]string
Extensions is the list of extensions or mime types to perform matching on.
Examples:
```yaml
extensions:
- .txt
- .go
- .json
```
denylist[]string
DenyList is the list of file, directories, mime types or extensions to deny during matching.
By default, it contains some non-interesting extensions that are hardcoded
in nuclei.
Examples:
```yaml
denylist:
- .avi
- .mov
- .mp3
```
idstring
ID is the optional id of the request
max-sizestring
MaxSize is the maximum size of the file to run request on.
By default, nuclei will process 1 GB of content and not go more than that.
It can be set to much lower or higher depending on use.
If set to "no" then all content will be processed
Examples:
```yaml
max-size: 5Mb
```
archivebool
elaborates archives
mime-typebool
enables mime types check
no-recursivebool
NoRecursive specifies whether to not do recursive checks if folders are provided.
## network.Request
Request contains a Network protocol request to be made from a template
Appears in:
- Template.network
- Template.tcp
```yaml
host:
- '{{Hostname}}'
- '{{Hostname}}:2181'
inputs:
- data: "envi\r\nquit\r\n"
read-size: 2048
matchers:
- type: word
words:
- zookeeper.version
```
Part Definitions:
- template-id - ID of the template executed
- template-info - Info Block of the template executed
- template-path - Path of the template executed
- host - Host is the input to the template
- matched - Matched is the input which was matched upon
- type - Type is the type of request made
- request - Network request made from the client
- body,all,data - Network response received from server (default)
- raw - Full Network protocol data
idstring
ID is the optional id of the request
host[]string
Host to send network requests to.
Usually it's set to `{{Hostname}}`. If you want to enable TLS for
TCP Connection, you can use `tls://{{Hostname}}`.
Examples:
```yaml
host:
- '{{Hostname}}'
```
Attack is the type of payload combinations to perform.
Batteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates
permutations and combinations for all payloads.
payloadsmap[string]interface{}
Payloads contains any payloads for the current request.
Payloads support both key-values combinations where a list
of payloads is provided, or optionally a single file can also
be provided as payload which will be read on run-time.
threadsint
Threads specifies number of threads to use sending requests. This enables Connection Pooling.
Connection: Close attribute must not be used in request while using threads flag, otherwise
pooling will fail and engine will continue to close connections after requests.
Examples:
```yaml
# Send requests using 10 concurrent threads
threads: 10
```
description: |
Port is the port to send network requests to. this acts as default port but is overridden if target/input contains
non-http(s) ports like 80,8080,8081 etc
exclude-portsstring
description: |
ExcludePorts is the list of ports to exclude from being scanned . It is intended to be used with `Port` field and contains a list of ports which are ignored/skipped
read-sizeint
ReadSize is the size of response to read at the end
Default value for read-size is 1024.
Examples:
```yaml
read-size: 2048
```
read-allbool
ReadAll determines if the data stream should be read till the end regardless of the size
Default value for read-all is false.
Examples:
```yaml
read-all: false
```
stop-at-first-matchbool
StopAtFirstMatch stops the execution of the requests and template as soon as a match is found.
Data is the data to send as the input.
It supports DSL Helper Functions as well as normal expressions.
Examples:
```yaml
data: TEST
```
```yaml
data: hex_decode('50494e47')
```
Type is the type of input specified in `data` field.
Default value is text, but hex can be used for hex formatted data.
Valid values:
- hex
- text
readint
Read is the number of bytes to read from socket.
This can be used for protocols which expect an immediate response. You can
read and write responses one after another and eventually perform matching
on every data captured with `name` attribute.
The [network docs](https://nuclei.projectdiscovery.io/templating-guide/protocols/network/) highlight more on how to do this.
Examples:
```yaml
read: 1024
```
namestring
Name is the optional name of the data read to provide matching on.
Examples:
```yaml
name: prefix
```
## NetworkInputTypeHolder
NetworkInputTypeHolder is used to hold internal type of the Network type
Appears in:
- network.Input.type
NetworkInputType
Enum Values:
- hex
- text
## headless.Request
Request contains a Headless protocol request to be made from a template
Appears in:
- Template.headless
Part Definitions:
- template-id - ID of the template executed
- template-info - Info Block of the template executed
- template-path - Path of the template executed
- host - Host is the input to the template
- matched - Matched is the input which was matched upon
- type - Type is the type of request made
- req - Headless request made from the client
- resp,body,data - Headless response received from client (default)
Attack is the type of payload combinations to perform.
Batteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates
permutations and combinations for all payloads.
payloadsmap[string]interface{}
Payloads contains any payloads for the current request.
Payloads support both key-values combinations where a list
of payloads is provided, or optionally a single file can also
be provided as payload which will be read on run-time.
Fuzzing describes schema to fuzz headless requests
cookie-reusebool
CookieReuse is an optional setting that enables cookie reuse
disable-cookiebool
DisableCookie is an optional setting that disables cookie reuse
## engine.Action
Action is an action taken by the browser to reach a navigation
Each step that the browser executes is an action. Most navigations
usually start from the ActionLoadURL event, and further navigations
are discovered on the found page. We also keep track and only
scrape new navigation from pages we haven't crawled yet.
Appears in:
- headless.Request.steps
argsmap[string]string
Args contain arguments for the headless action.
Per action arguments are described in detail [here](https://nuclei.projectdiscovery.io/templating-guide/protocols/headless/).
namestring
Name is the name assigned to the headless action.
This can be used to execute code, for instance in browser
DOM using script action, and get the result in a variable
which can be matched upon by nuclei. An Example template [here](https://github.com/projectdiscovery/nuclei-templates/blob/main/headless/prototype-pollution-check.yaml).
descriptionstring
Description is the optional description of the headless action
## userAgent.UserAgentHolder
UserAgentHolder holds a UserAgent type. Required for un/marshalling purposes
Appears in:
- headless.Request.user_agent
UserAgent
Enum Values:
- random
- off
- default
- custom
## ssl.Request
Request is a request for the SSL protocol
Appears in:
- Template.ssl
Part Definitions:
- template-id - ID of the template executed
- template-info - Info Block of the template executed
- template-path - Path of the template executed
- host - Host is the input to the template
- port - Port is the port of the host
- matched - Matched is the input which was matched upon
- type - Type is the type of request made
- timestamp - Timestamp is the time when the request was made
- response - JSON SSL protocol handshake details
- cipher - Cipher is the encryption algorithm used
- domains - Domains are the list of domain names in the certificate
- fingerprint_hash - Fingerprint hash is the unique identifier of the certificate
- ip - IP is the IP address of the server
- issuer_cn - Issuer CN is the common name of the certificate issuer
- issuer_dn - Issuer DN is the distinguished name of the certificate issuer
- issuer_org - Issuer organization is the organization of the certificate issuer
- not_after - Timestamp after which the remote cert expires
- not_before - Timestamp before which the certificate is not valid
- probe_status - Probe status indicates if the probe was successful
- serial - Serial is the serial number of the certificate
- sni - SNI is the server name indication used in the handshake
- subject_an - Subject AN is the list of subject alternative names
- subject_cn - Subject CN is the common name of the certificate subject
- subject_dn - Subject DN is the distinguished name of the certificate subject
- subject_org - Subject organization is the organization of the certificate subject
- tls_connection - TLS connection is the type of TLS connection used
- tls_version - TLS version is the version of the TLS protocol used
idstring
ID is the optional id of the request
addressstring
Address contains address for the request
min_versionstring
Minimum tls version - auto if not specified.
Valid values:
- sslv3
- tls10
- tls11
- tls12
- tls13
max_versionstring
Max tls version - auto if not specified.
Valid values:
- sslv3
- tls10
- tls11
- tls12
- tls13
cipher_suites[]string
Client Cipher Suites - auto if not specified.
scan_modestring
description: |
Tls Scan Mode - auto if not specified
values:
- "ctls"
- "ztls"
- "auto"
- "openssl" # reverts to "auto" is openssl is not installed
tls_version_enumbool
TLS Versions Enum - false if not specified
Enumerates supported TLS versions
tls_cipher_enumbool
TLS Ciphers Enum - false if not specified
Enumerates supported TLS ciphers
## websocket.Request
Request is a request for the Websocket protocol
Appears in:
- Template.websocket
Part Definitions:
- type - Type is the type of request made
- success - Success specifies whether websocket connection was successful
- request - Websocket request made to the server
- response - Websocket response received from the server
- host - Host is the input to the template
- matched - Matched is the input which was matched upon
Attack is the type of payload combinations to perform.
Sniper is each payload once, pitchfork combines multiple payload sets and clusterbomb generates
permutations and combinations for all payloads.
payloadsmap[string]interface{}
Payloads contains any payloads for the current request.
Payloads support both key-values combinations where a list
of payloads is provided, or optionally a single file can also
be provided as payload which will be read on run-time.
Data is the data to send as the input.
It supports DSL Helper Functions as well as normal expressions.
Examples:
```yaml
data: TEST
```
```yaml
data: hex_decode('50494e47')
```
namestring
Name is the optional name of the data read to provide matching on.
Examples:
```yaml
name: prefix
```
## whois.Request
Request is a request for the WHOIS protocol
Appears in:
- Template.whois
idstring
ID is the optional id of the request
querystring
Query contains query for the request
serverstring
description: |
Optional WHOIS server URL.
If present, specifies the WHOIS server to execute the Request on.
Otherwise, nil enables bootstrapping
## code.Request
Request is a request for the SSL protocol
Appears in:
- Template.code
Part Definitions:
- type - Type is the type of request made
- host - Host is the input to the template
- matched - Matched is the input which was matched upon
idstring
ID is the optional id of the request
engine[]string
Engine type
pre-conditionstring
PreCondition is a condition which is evaluated before sending the request.
args[]string
Engine Arguments
patternstring
Pattern preferred for file name
sourcestring
Source File/Snippet
## javascript.Request
Request is a request for the javascript protocol
Appears in:
- Template.javascript
Part Definitions:
- type - Type is the type of request made
- response - Javascript protocol result response
- host - Host is the input to the template
- matched - Matched is the input which was matched upon
idstring
description: |
ID is request id in that protocol
initstring
Init is javascript code to execute after compiling template and before executing it on any target
This is helpful for preparing payloads or other setup that maybe required for exploits
pre-conditionstring
PreCondition is a condition which is evaluated before sending the request.
argsmap[string]interface{}
Args contains the arguments to pass to the javascript code.
codestring
Code contains code to execute for the javascript request.
stop-at-first-matchbool
StopAtFirstMatch stops processing the request at first match.
Attack is the type of payload combinations to perform.
Sniper is each payload once, pitchfork combines multiple payload sets and clusterbomb generates
permutations and combinations for all payloads.
threadsint
Payload concurrency i.e threads for sending requests.
Examples:
```yaml
# Send requests using 10 concurrent threads
threads: 10
```
payloadsmap[string]interface{}
Payloads contains any payloads for the current request.
Payloads support both key-values combinations where a list
of payloads is provided, or optionally a single file can also
be provided as payload which will be read on run-time.
## http.SignatureTypeHolder
SignatureTypeHolder is used to hold internal type of the signature
Appears in:
- Template.signature
## variables.Variable
Variable is a key-value pair of strings that can be used
throughout template.
Appears in:
- Template.variables
================================================
FILE: THANKS.md
================================================
### Thanks
Many people have contributed to **nuclei** making it a wonderful tool either by making a pull request fixing some stuff. Here, we recognize these persons and thank them.
- All the contributors at [CONTRIBUTORS](https://github.com/projectdiscovery/nuclei/graphs/contributors) who made Nuclei what it is.
We'd like to thank some additional amazing people, who contributed a lot in nuclei's journey -
- [@manuelbua](https://www.github.com/manuelbua) - Multiple feature additions including progress bar.
- [@dwisiswant0](https://www.github.com/dwisiswant0) - For fixing multiple bugs with HTTP Raw requests.
- [@toufik-airane](https://www.github.com/toufik-airane) - For adding binary matchers.
- [@lc](https://www.github.com/lc) - For adding directory support for templates.
- [@wdahlenburg](https://www.github.com/wdahlenburg) - For adding wildcard globing support to run templates.
- [@Marmelatze](https://www.github.com/Marmelatze) - For adding support to save matched request/response in JSON format.
- [@ankh2054](https://www.github.com/ankh2054) - For adding description field for template information.
- [@rotemreiss](https://www.github.com/rotemreiss) - For adding docker support.
================================================
FILE: _typos.toml
================================================
[files]
extend-exclude = [
# Non-English translations
"README_CN.md",
"README_ES.md",
"README_ID.md",
"README_JP.md",
"README_KR.md",
"README_PT-BR.md",
"README_TR.md",
# Test fixtures and data files
"integration_tests/",
"pkg/input/formats/testdata/",
"pkg/output/stats/waf/",
# Deserialization test data
"pkg/protocols/common/helpers/deserialization/testdata/",
# Certificate/encoded data in test utilities
"pkg/testutils/integration.go",
# Vendor directory
"vendor/",
# Go checksum file
"go.sum",
]
[default.extend-identifiers]
# Go identifiers that cannot be renamed without breaking API
MisMatched = "MisMatched"
NoopWriter = "NoopWriter"
AllowdTypes = "AllowdTypes"
# Deprecated alias kept for backward compatibility
ExludedDastTmplStats = "ExludedDastTmplStats"
[default.extend-words]
# Variable name used for flow annotations
fo = "fo"
# Serbian test data in XML
alo = "alo"
# SQL test data (Spanish)
algoritmos = "algoritmos"
# Base64/hex encoded test data
Noo = "Noo"
# Certificate data fragments
ba = "ba"
nd = "nd"
# Base64 encoded test data fragments
Iif = "Iif"
ser = "ser"
[type.go.extend-identifiers]
# Short CLI flag names that appear in help strings
ot = "ot"
ue = "ue"
ine = "ine"
ines = "ines"
hae = "hae"
[type.md.extend-identifiers]
# CLI flag references in documentation
ot = "ot"
ue = "ue"
ine = "ine"
ines = "ines"
hae = "hae"
================================================
FILE: cmd/docgen/docgen.go
================================================
package main
import (
"bytes"
"log"
"os"
"reflect"
"regexp"
"github.com/invopop/jsonschema"
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
)
var pathRegex = regexp.MustCompile(`github\.com/projectdiscovery/nuclei/v3/(?:internal|pkg)/(?:.*/)?([A-Za-z.]+)`)
func writeToFile(filename string, data []byte) {
file, err := os.Create(filename)
if err != nil {
log.Fatalf("Could not create file %s: %s\n", filename, err)
}
defer func() {
_ = file.Close()
}()
_, err = file.Write(data)
if err != nil {
log.Fatalf("Could not write to file %s: %s\n", filename, err)
}
}
func main() {
if len(os.Args) < 3 {
log.Fatalf("syntax: %s md-docs-file jsonschema-file\n", os.Args[0])
}
// Generate YAML documentation
data, err := templates.GetTemplateDoc().Encode()
if err != nil {
log.Fatalf("Could not encode docs: %s\n", err)
}
writeToFile(os.Args[1], data)
// Generate JSON Schema
r := &jsonschema.Reflector{
Namer: func(t reflect.Type) string {
if t.Kind() == reflect.Slice {
return ""
}
return t.String()
},
}
jsonschemaData := r.Reflect(&templates.Template{})
var buf bytes.Buffer
encoder := json.NewEncoder(&buf)
encoder.SetIndent("", " ")
if err := encoder.Encode(jsonschemaData); err != nil {
log.Fatalf("Could not encode JSON schema: %s\n", err)
}
schema := pathRegex.ReplaceAllString(buf.String(), "$1")
writeToFile(os.Args[2], []byte(schema))
}
================================================
FILE: cmd/functional-test/main.go
================================================
package main
import (
"bufio"
"flag"
"fmt"
"log"
"os"
"strings"
"github.com/kitabisa/go-ci"
"github.com/logrusorgru/aurora"
"github.com/pkg/errors"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
)
var (
success = aurora.Green("[✓]").String()
failed = aurora.Red("[✘]").String()
mainNucleiBinary = flag.String("main", "", "Main Branch Nuclei Binary")
devNucleiBinary = flag.String("dev", "", "Dev Branch Nuclei Binary")
testcases = flag.String("testcases", "", "Test cases file for nuclei functional tests")
)
func main() {
flag.Parse()
debug := os.Getenv("DEBUG") == "true" || os.Getenv("RUNNER_DEBUG") == "1"
if err, errored := runFunctionalTests(debug); err != nil {
log.Fatalf("Could not run functional tests: %s\n", err)
} else if errored {
os.Exit(1)
}
}
func runFunctionalTests(debug bool) (error, bool) {
file, err := os.Open(*testcases)
if err != nil {
return errors.Wrap(err, "could not open test cases"), true
}
defer func() {
_ = file.Close()
}()
errored, failedTestCases := runTestCases(file, debug)
if ci.IsCI() {
fmt.Println("::group::Failed tests with debug")
for _, failedTestCase := range failedTestCases {
_ = runTestCase(failedTestCase, true)
}
fmt.Println("::endgroup::")
}
return nil, errored
}
func runTestCases(file *os.File, debug bool) (bool, []string) {
errored := false
var failedTestCases []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
testCase := strings.TrimSpace(scanner.Text())
if testCase == "" {
continue
}
// skip comments
if strings.HasPrefix(testCase, "#") {
continue
}
if runTestCase(testCase, debug) {
errored = true
failedTestCases = append(failedTestCases, testCase)
}
}
return errored, failedTestCases
}
func runTestCase(testCase string, debug bool) bool {
if err := runIndividualTestCase(testCase, debug); err != nil {
fmt.Fprintf(os.Stderr, "%s Test \"%s\" failed: %s\n", failed, testCase, err)
return true
} else {
fmt.Printf("%s Test \"%s\" passed!\n", success, testCase)
}
return false
}
func runIndividualTestCase(testcase string, debug bool) error {
quoted := false
// split upon unquoted spaces
parts := strings.FieldsFunc(testcase, func(r rune) bool {
if r == '"' {
quoted = !quoted
}
return !quoted && r == ' '
})
// Quoted strings containing spaces are expressions and must have trailing \" removed
for index, part := range parts {
if strings.Contains(part, " ") {
parts[index] = strings.Trim(part, "\"")
}
}
var finalArgs []string
if len(parts) > 1 {
finalArgs = parts[1:]
}
mainOutput, err := testutils.RunNucleiBinaryAndGetLoadedTemplates(*mainNucleiBinary, debug, finalArgs)
if err != nil {
return errors.Wrap(err, "could not run nuclei main test")
}
devOutput, err := testutils.RunNucleiBinaryAndGetLoadedTemplates(*devNucleiBinary, debug, finalArgs)
if err != nil {
return errors.Wrap(err, "could not run nuclei dev test")
}
if mainOutput == devOutput {
return nil
}
return fmt.Errorf("%s main is not equal to %s dev", mainOutput, devOutput)
}
================================================
FILE: cmd/functional-test/run.sh
================================================
#!/bin/bash
if [ "${RUNNER_OS}" == "Windows" ]; then
EXT=".exe"
elif [ "${RUNNER_OS}" == "macOS" ]; then
if [ "${CI}" == "true" ]; then
sudo sysctl -w kern.maxfiles{,perproc}=524288
sudo launchctl limit maxfiles 65536 524288
fi
ORIGINAL_ULIMIT="$(ulimit -n)"
ulimit -n 65536 || true
fi
mkdir -p .nuclei-config/nuclei/
touch .nuclei-config/nuclei/.nuclei-ignore
echo "::group::Building functional-test binary"
go build -o "functional-test${EXT}"
echo "::endgroup::"
echo "::group::Building Nuclei binary from current branch"
go build -o "nuclei-dev${EXT}" ../nuclei
echo "::endgroup::"
echo "::group::Building latest release of nuclei"
go build -o "nuclei${EXT}" -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei
echo "::endgroup::"
echo "::group::Installing nuclei templates"
eval "./nuclei-dev${EXT} -update-templates"
echo "::endgroup::"
echo "::group::Validating templates"
eval "./nuclei-dev${EXT} -validate"
echo "::endgroup::"
echo "Starting Nuclei functional test"
eval "./functional-test${EXT} -main ./nuclei${EXT} -dev ./nuclei-dev${EXT} -testcases testcases.txt"
if [ "${RUNNER_OS}" == "macOS" ]; then
ulimit -n "${ORIGINAL_ULIMIT}" || true
fi
================================================
FILE: cmd/functional-test/targets-1000.txt
================================================
https://scanme.sh/?a=1
https://scanme.sh/?a=2
https://scanme.sh/?a=3
https://scanme.sh/?a=4
https://scanme.sh/?a=5
https://scanme.sh/?a=6
https://scanme.sh/?a=7
https://scanme.sh/?a=8
https://scanme.sh/?a=9
https://scanme.sh/?a=10
https://scanme.sh/?a=11
https://scanme.sh/?a=12
https://scanme.sh/?a=13
https://scanme.sh/?a=14
https://scanme.sh/?a=15
https://scanme.sh/?a=16
https://scanme.sh/?a=17
https://scanme.sh/?a=18
https://scanme.sh/?a=19
https://scanme.sh/?a=20
https://scanme.sh/?a=21
https://scanme.sh/?a=22
https://scanme.sh/?a=23
https://scanme.sh/?a=24
https://scanme.sh/?a=25
https://scanme.sh/?a=26
https://scanme.sh/?a=27
https://scanme.sh/?a=28
https://scanme.sh/?a=29
https://scanme.sh/?a=30
https://scanme.sh/?a=31
https://scanme.sh/?a=32
https://scanme.sh/?a=33
https://scanme.sh/?a=34
https://scanme.sh/?a=35
https://scanme.sh/?a=36
https://scanme.sh/?a=37
https://scanme.sh/?a=38
https://scanme.sh/?a=39
https://scanme.sh/?a=40
https://scanme.sh/?a=41
https://scanme.sh/?a=42
https://scanme.sh/?a=43
https://scanme.sh/?a=44
https://scanme.sh/?a=45
https://scanme.sh/?a=46
https://scanme.sh/?a=47
https://scanme.sh/?a=48
https://scanme.sh/?a=49
https://scanme.sh/?a=50
https://scanme.sh/?a=51
https://scanme.sh/?a=52
https://scanme.sh/?a=53
https://scanme.sh/?a=54
https://scanme.sh/?a=55
https://scanme.sh/?a=56
https://scanme.sh/?a=57
https://scanme.sh/?a=58
https://scanme.sh/?a=59
https://scanme.sh/?a=60
https://scanme.sh/?a=61
https://scanme.sh/?a=62
https://scanme.sh/?a=63
https://scanme.sh/?a=64
https://scanme.sh/?a=65
https://scanme.sh/?a=66
https://scanme.sh/?a=67
https://scanme.sh/?a=68
https://scanme.sh/?a=69
https://scanme.sh/?a=70
https://scanme.sh/?a=71
https://scanme.sh/?a=72
https://scanme.sh/?a=73
https://scanme.sh/?a=74
https://scanme.sh/?a=75
https://scanme.sh/?a=76
https://scanme.sh/?a=77
https://scanme.sh/?a=78
https://scanme.sh/?a=79
https://scanme.sh/?a=80
https://scanme.sh/?a=81
https://scanme.sh/?a=82
https://scanme.sh/?a=83
https://scanme.sh/?a=84
https://scanme.sh/?a=85
https://scanme.sh/?a=86
https://scanme.sh/?a=87
https://scanme.sh/?a=88
https://scanme.sh/?a=89
https://scanme.sh/?a=90
https://scanme.sh/?a=91
https://scanme.sh/?a=92
https://scanme.sh/?a=93
https://scanme.sh/?a=94
https://scanme.sh/?a=95
https://scanme.sh/?a=96
https://scanme.sh/?a=97
https://scanme.sh/?a=98
https://scanme.sh/?a=99
https://scanme.sh/?a=100
https://scanme.sh/?a=101
https://scanme.sh/?a=102
https://scanme.sh/?a=103
https://scanme.sh/?a=104
https://scanme.sh/?a=105
https://scanme.sh/?a=106
https://scanme.sh/?a=107
https://scanme.sh/?a=108
https://scanme.sh/?a=109
https://scanme.sh/?a=110
https://scanme.sh/?a=111
https://scanme.sh/?a=112
https://scanme.sh/?a=113
https://scanme.sh/?a=114
https://scanme.sh/?a=115
https://scanme.sh/?a=116
https://scanme.sh/?a=117
https://scanme.sh/?a=118
https://scanme.sh/?a=119
https://scanme.sh/?a=120
https://scanme.sh/?a=121
https://scanme.sh/?a=122
https://scanme.sh/?a=123
https://scanme.sh/?a=124
https://scanme.sh/?a=125
https://scanme.sh/?a=126
https://scanme.sh/?a=127
https://scanme.sh/?a=128
https://scanme.sh/?a=129
https://scanme.sh/?a=130
https://scanme.sh/?a=131
https://scanme.sh/?a=132
https://scanme.sh/?a=133
https://scanme.sh/?a=134
https://scanme.sh/?a=135
https://scanme.sh/?a=136
https://scanme.sh/?a=137
https://scanme.sh/?a=138
https://scanme.sh/?a=139
https://scanme.sh/?a=140
https://scanme.sh/?a=141
https://scanme.sh/?a=142
https://scanme.sh/?a=143
https://scanme.sh/?a=144
https://scanme.sh/?a=145
https://scanme.sh/?a=146
https://scanme.sh/?a=147
https://scanme.sh/?a=148
https://scanme.sh/?a=149
https://scanme.sh/?a=150
https://scanme.sh/?a=151
https://scanme.sh/?a=152
https://scanme.sh/?a=153
https://scanme.sh/?a=154
https://scanme.sh/?a=155
https://scanme.sh/?a=156
https://scanme.sh/?a=157
https://scanme.sh/?a=158
https://scanme.sh/?a=159
https://scanme.sh/?a=160
https://scanme.sh/?a=161
https://scanme.sh/?a=162
https://scanme.sh/?a=163
https://scanme.sh/?a=164
https://scanme.sh/?a=165
https://scanme.sh/?a=166
https://scanme.sh/?a=167
https://scanme.sh/?a=168
https://scanme.sh/?a=169
https://scanme.sh/?a=170
https://scanme.sh/?a=171
https://scanme.sh/?a=172
https://scanme.sh/?a=173
https://scanme.sh/?a=174
https://scanme.sh/?a=175
https://scanme.sh/?a=176
https://scanme.sh/?a=177
https://scanme.sh/?a=178
https://scanme.sh/?a=179
https://scanme.sh/?a=180
https://scanme.sh/?a=181
https://scanme.sh/?a=182
https://scanme.sh/?a=183
https://scanme.sh/?a=184
https://scanme.sh/?a=185
https://scanme.sh/?a=186
https://scanme.sh/?a=187
https://scanme.sh/?a=188
https://scanme.sh/?a=189
https://scanme.sh/?a=190
https://scanme.sh/?a=191
https://scanme.sh/?a=192
https://scanme.sh/?a=193
https://scanme.sh/?a=194
https://scanme.sh/?a=195
https://scanme.sh/?a=196
https://scanme.sh/?a=197
https://scanme.sh/?a=198
https://scanme.sh/?a=199
https://scanme.sh/?a=200
https://scanme.sh/?a=201
https://scanme.sh/?a=202
https://scanme.sh/?a=203
https://scanme.sh/?a=204
https://scanme.sh/?a=205
https://scanme.sh/?a=206
https://scanme.sh/?a=207
https://scanme.sh/?a=208
https://scanme.sh/?a=209
https://scanme.sh/?a=210
https://scanme.sh/?a=211
https://scanme.sh/?a=212
https://scanme.sh/?a=213
https://scanme.sh/?a=214
https://scanme.sh/?a=215
https://scanme.sh/?a=216
https://scanme.sh/?a=217
https://scanme.sh/?a=218
https://scanme.sh/?a=219
https://scanme.sh/?a=220
https://scanme.sh/?a=221
https://scanme.sh/?a=222
https://scanme.sh/?a=223
https://scanme.sh/?a=224
https://scanme.sh/?a=225
https://scanme.sh/?a=226
https://scanme.sh/?a=227
https://scanme.sh/?a=228
https://scanme.sh/?a=229
https://scanme.sh/?a=230
https://scanme.sh/?a=231
https://scanme.sh/?a=232
https://scanme.sh/?a=233
https://scanme.sh/?a=234
https://scanme.sh/?a=235
https://scanme.sh/?a=236
https://scanme.sh/?a=237
https://scanme.sh/?a=238
https://scanme.sh/?a=239
https://scanme.sh/?a=240
https://scanme.sh/?a=241
https://scanme.sh/?a=242
https://scanme.sh/?a=243
https://scanme.sh/?a=244
https://scanme.sh/?a=245
https://scanme.sh/?a=246
https://scanme.sh/?a=247
https://scanme.sh/?a=248
https://scanme.sh/?a=249
https://scanme.sh/?a=250
https://scanme.sh/?a=251
https://scanme.sh/?a=252
https://scanme.sh/?a=253
https://scanme.sh/?a=254
https://scanme.sh/?a=255
https://scanme.sh/?a=256
https://scanme.sh/?a=257
https://scanme.sh/?a=258
https://scanme.sh/?a=259
https://scanme.sh/?a=260
https://scanme.sh/?a=261
https://scanme.sh/?a=262
https://scanme.sh/?a=263
https://scanme.sh/?a=264
https://scanme.sh/?a=265
https://scanme.sh/?a=266
https://scanme.sh/?a=267
https://scanme.sh/?a=268
https://scanme.sh/?a=269
https://scanme.sh/?a=270
https://scanme.sh/?a=271
https://scanme.sh/?a=272
https://scanme.sh/?a=273
https://scanme.sh/?a=274
https://scanme.sh/?a=275
https://scanme.sh/?a=276
https://scanme.sh/?a=277
https://scanme.sh/?a=278
https://scanme.sh/?a=279
https://scanme.sh/?a=280
https://scanme.sh/?a=281
https://scanme.sh/?a=282
https://scanme.sh/?a=283
https://scanme.sh/?a=284
https://scanme.sh/?a=285
https://scanme.sh/?a=286
https://scanme.sh/?a=287
https://scanme.sh/?a=288
https://scanme.sh/?a=289
https://scanme.sh/?a=290
https://scanme.sh/?a=291
https://scanme.sh/?a=292
https://scanme.sh/?a=293
https://scanme.sh/?a=294
https://scanme.sh/?a=295
https://scanme.sh/?a=296
https://scanme.sh/?a=297
https://scanme.sh/?a=298
https://scanme.sh/?a=299
https://scanme.sh/?a=300
https://scanme.sh/?a=301
https://scanme.sh/?a=302
https://scanme.sh/?a=303
https://scanme.sh/?a=304
https://scanme.sh/?a=305
https://scanme.sh/?a=306
https://scanme.sh/?a=307
https://scanme.sh/?a=308
https://scanme.sh/?a=309
https://scanme.sh/?a=310
https://scanme.sh/?a=311
https://scanme.sh/?a=312
https://scanme.sh/?a=313
https://scanme.sh/?a=314
https://scanme.sh/?a=315
https://scanme.sh/?a=316
https://scanme.sh/?a=317
https://scanme.sh/?a=318
https://scanme.sh/?a=319
https://scanme.sh/?a=320
https://scanme.sh/?a=321
https://scanme.sh/?a=322
https://scanme.sh/?a=323
https://scanme.sh/?a=324
https://scanme.sh/?a=325
https://scanme.sh/?a=326
https://scanme.sh/?a=327
https://scanme.sh/?a=328
https://scanme.sh/?a=329
https://scanme.sh/?a=330
https://scanme.sh/?a=331
https://scanme.sh/?a=332
https://scanme.sh/?a=333
https://scanme.sh/?a=334
https://scanme.sh/?a=335
https://scanme.sh/?a=336
https://scanme.sh/?a=337
https://scanme.sh/?a=338
https://scanme.sh/?a=339
https://scanme.sh/?a=340
https://scanme.sh/?a=341
https://scanme.sh/?a=342
https://scanme.sh/?a=343
https://scanme.sh/?a=344
https://scanme.sh/?a=345
https://scanme.sh/?a=346
https://scanme.sh/?a=347
https://scanme.sh/?a=348
https://scanme.sh/?a=349
https://scanme.sh/?a=350
https://scanme.sh/?a=351
https://scanme.sh/?a=352
https://scanme.sh/?a=353
https://scanme.sh/?a=354
https://scanme.sh/?a=355
https://scanme.sh/?a=356
https://scanme.sh/?a=357
https://scanme.sh/?a=358
https://scanme.sh/?a=359
https://scanme.sh/?a=360
https://scanme.sh/?a=361
https://scanme.sh/?a=362
https://scanme.sh/?a=363
https://scanme.sh/?a=364
https://scanme.sh/?a=365
https://scanme.sh/?a=366
https://scanme.sh/?a=367
https://scanme.sh/?a=368
https://scanme.sh/?a=369
https://scanme.sh/?a=370
https://scanme.sh/?a=371
https://scanme.sh/?a=372
https://scanme.sh/?a=373
https://scanme.sh/?a=374
https://scanme.sh/?a=375
https://scanme.sh/?a=376
https://scanme.sh/?a=377
https://scanme.sh/?a=378
https://scanme.sh/?a=379
https://scanme.sh/?a=380
https://scanme.sh/?a=381
https://scanme.sh/?a=382
https://scanme.sh/?a=383
https://scanme.sh/?a=384
https://scanme.sh/?a=385
https://scanme.sh/?a=386
https://scanme.sh/?a=387
https://scanme.sh/?a=388
https://scanme.sh/?a=389
https://scanme.sh/?a=390
https://scanme.sh/?a=391
https://scanme.sh/?a=392
https://scanme.sh/?a=393
https://scanme.sh/?a=394
https://scanme.sh/?a=395
https://scanme.sh/?a=396
https://scanme.sh/?a=397
https://scanme.sh/?a=398
https://scanme.sh/?a=399
https://scanme.sh/?a=400
https://scanme.sh/?a=401
https://scanme.sh/?a=402
https://scanme.sh/?a=403
https://scanme.sh/?a=404
https://scanme.sh/?a=405
https://scanme.sh/?a=406
https://scanme.sh/?a=407
https://scanme.sh/?a=408
https://scanme.sh/?a=409
https://scanme.sh/?a=410
https://scanme.sh/?a=411
https://scanme.sh/?a=412
https://scanme.sh/?a=413
https://scanme.sh/?a=414
https://scanme.sh/?a=415
https://scanme.sh/?a=416
https://scanme.sh/?a=417
https://scanme.sh/?a=418
https://scanme.sh/?a=419
https://scanme.sh/?a=420
https://scanme.sh/?a=421
https://scanme.sh/?a=422
https://scanme.sh/?a=423
https://scanme.sh/?a=424
https://scanme.sh/?a=425
https://scanme.sh/?a=426
https://scanme.sh/?a=427
https://scanme.sh/?a=428
https://scanme.sh/?a=429
https://scanme.sh/?a=430
https://scanme.sh/?a=431
https://scanme.sh/?a=432
https://scanme.sh/?a=433
https://scanme.sh/?a=434
https://scanme.sh/?a=435
https://scanme.sh/?a=436
https://scanme.sh/?a=437
https://scanme.sh/?a=438
https://scanme.sh/?a=439
https://scanme.sh/?a=440
https://scanme.sh/?a=441
https://scanme.sh/?a=442
https://scanme.sh/?a=443
https://scanme.sh/?a=444
https://scanme.sh/?a=445
https://scanme.sh/?a=446
https://scanme.sh/?a=447
https://scanme.sh/?a=448
https://scanme.sh/?a=449
https://scanme.sh/?a=450
https://scanme.sh/?a=451
https://scanme.sh/?a=452
https://scanme.sh/?a=453
https://scanme.sh/?a=454
https://scanme.sh/?a=455
https://scanme.sh/?a=456
https://scanme.sh/?a=457
https://scanme.sh/?a=458
https://scanme.sh/?a=459
https://scanme.sh/?a=460
https://scanme.sh/?a=461
https://scanme.sh/?a=462
https://scanme.sh/?a=463
https://scanme.sh/?a=464
https://scanme.sh/?a=465
https://scanme.sh/?a=466
https://scanme.sh/?a=467
https://scanme.sh/?a=468
https://scanme.sh/?a=469
https://scanme.sh/?a=470
https://scanme.sh/?a=471
https://scanme.sh/?a=472
https://scanme.sh/?a=473
https://scanme.sh/?a=474
https://scanme.sh/?a=475
https://scanme.sh/?a=476
https://scanme.sh/?a=477
https://scanme.sh/?a=478
https://scanme.sh/?a=479
https://scanme.sh/?a=480
https://scanme.sh/?a=481
https://scanme.sh/?a=482
https://scanme.sh/?a=483
https://scanme.sh/?a=484
https://scanme.sh/?a=485
https://scanme.sh/?a=486
https://scanme.sh/?a=487
https://scanme.sh/?a=488
https://scanme.sh/?a=489
https://scanme.sh/?a=490
https://scanme.sh/?a=491
https://scanme.sh/?a=492
https://scanme.sh/?a=493
https://scanme.sh/?a=494
https://scanme.sh/?a=495
https://scanme.sh/?a=496
https://scanme.sh/?a=497
https://scanme.sh/?a=498
https://scanme.sh/?a=499
https://scanme.sh/?a=500
https://scanme.sh/?a=501
https://scanme.sh/?a=502
https://scanme.sh/?a=503
https://scanme.sh/?a=504
https://scanme.sh/?a=505
https://scanme.sh/?a=506
https://scanme.sh/?a=507
https://scanme.sh/?a=508
https://scanme.sh/?a=509
https://scanme.sh/?a=510
https://scanme.sh/?a=511
https://scanme.sh/?a=512
https://scanme.sh/?a=513
https://scanme.sh/?a=514
https://scanme.sh/?a=515
https://scanme.sh/?a=516
https://scanme.sh/?a=517
https://scanme.sh/?a=518
https://scanme.sh/?a=519
https://scanme.sh/?a=520
https://scanme.sh/?a=521
https://scanme.sh/?a=522
https://scanme.sh/?a=523
https://scanme.sh/?a=524
https://scanme.sh/?a=525
https://scanme.sh/?a=526
https://scanme.sh/?a=527
https://scanme.sh/?a=528
https://scanme.sh/?a=529
https://scanme.sh/?a=530
https://scanme.sh/?a=531
https://scanme.sh/?a=532
https://scanme.sh/?a=533
https://scanme.sh/?a=534
https://scanme.sh/?a=535
https://scanme.sh/?a=536
https://scanme.sh/?a=537
https://scanme.sh/?a=538
https://scanme.sh/?a=539
https://scanme.sh/?a=540
https://scanme.sh/?a=541
https://scanme.sh/?a=542
https://scanme.sh/?a=543
https://scanme.sh/?a=544
https://scanme.sh/?a=545
https://scanme.sh/?a=546
https://scanme.sh/?a=547
https://scanme.sh/?a=548
https://scanme.sh/?a=549
https://scanme.sh/?a=550
https://scanme.sh/?a=551
https://scanme.sh/?a=552
https://scanme.sh/?a=553
https://scanme.sh/?a=554
https://scanme.sh/?a=555
https://scanme.sh/?a=556
https://scanme.sh/?a=557
https://scanme.sh/?a=558
https://scanme.sh/?a=559
https://scanme.sh/?a=560
https://scanme.sh/?a=561
https://scanme.sh/?a=562
https://scanme.sh/?a=563
https://scanme.sh/?a=564
https://scanme.sh/?a=565
https://scanme.sh/?a=566
https://scanme.sh/?a=567
https://scanme.sh/?a=568
https://scanme.sh/?a=569
https://scanme.sh/?a=570
https://scanme.sh/?a=571
https://scanme.sh/?a=572
https://scanme.sh/?a=573
https://scanme.sh/?a=574
https://scanme.sh/?a=575
https://scanme.sh/?a=576
https://scanme.sh/?a=577
https://scanme.sh/?a=578
https://scanme.sh/?a=579
https://scanme.sh/?a=580
https://scanme.sh/?a=581
https://scanme.sh/?a=582
https://scanme.sh/?a=583
https://scanme.sh/?a=584
https://scanme.sh/?a=585
https://scanme.sh/?a=586
https://scanme.sh/?a=587
https://scanme.sh/?a=588
https://scanme.sh/?a=589
https://scanme.sh/?a=590
https://scanme.sh/?a=591
https://scanme.sh/?a=592
https://scanme.sh/?a=593
https://scanme.sh/?a=594
https://scanme.sh/?a=595
https://scanme.sh/?a=596
https://scanme.sh/?a=597
https://scanme.sh/?a=598
https://scanme.sh/?a=599
https://scanme.sh/?a=600
https://scanme.sh/?a=601
https://scanme.sh/?a=602
https://scanme.sh/?a=603
https://scanme.sh/?a=604
https://scanme.sh/?a=605
https://scanme.sh/?a=606
https://scanme.sh/?a=607
https://scanme.sh/?a=608
https://scanme.sh/?a=609
https://scanme.sh/?a=610
https://scanme.sh/?a=611
https://scanme.sh/?a=612
https://scanme.sh/?a=613
https://scanme.sh/?a=614
https://scanme.sh/?a=615
https://scanme.sh/?a=616
https://scanme.sh/?a=617
https://scanme.sh/?a=618
https://scanme.sh/?a=619
https://scanme.sh/?a=620
https://scanme.sh/?a=621
https://scanme.sh/?a=622
https://scanme.sh/?a=623
https://scanme.sh/?a=624
https://scanme.sh/?a=625
https://scanme.sh/?a=626
https://scanme.sh/?a=627
https://scanme.sh/?a=628
https://scanme.sh/?a=629
https://scanme.sh/?a=630
https://scanme.sh/?a=631
https://scanme.sh/?a=632
https://scanme.sh/?a=633
https://scanme.sh/?a=634
https://scanme.sh/?a=635
https://scanme.sh/?a=636
https://scanme.sh/?a=637
https://scanme.sh/?a=638
https://scanme.sh/?a=639
https://scanme.sh/?a=640
https://scanme.sh/?a=641
https://scanme.sh/?a=642
https://scanme.sh/?a=643
https://scanme.sh/?a=644
https://scanme.sh/?a=645
https://scanme.sh/?a=646
https://scanme.sh/?a=647
https://scanme.sh/?a=648
https://scanme.sh/?a=649
https://scanme.sh/?a=650
https://scanme.sh/?a=651
https://scanme.sh/?a=652
https://scanme.sh/?a=653
https://scanme.sh/?a=654
https://scanme.sh/?a=655
https://scanme.sh/?a=656
https://scanme.sh/?a=657
https://scanme.sh/?a=658
https://scanme.sh/?a=659
https://scanme.sh/?a=660
https://scanme.sh/?a=661
https://scanme.sh/?a=662
https://scanme.sh/?a=663
https://scanme.sh/?a=664
https://scanme.sh/?a=665
https://scanme.sh/?a=666
https://scanme.sh/?a=667
https://scanme.sh/?a=668
https://scanme.sh/?a=669
https://scanme.sh/?a=670
https://scanme.sh/?a=671
https://scanme.sh/?a=672
https://scanme.sh/?a=673
https://scanme.sh/?a=674
https://scanme.sh/?a=675
https://scanme.sh/?a=676
https://scanme.sh/?a=677
https://scanme.sh/?a=678
https://scanme.sh/?a=679
https://scanme.sh/?a=680
https://scanme.sh/?a=681
https://scanme.sh/?a=682
https://scanme.sh/?a=683
https://scanme.sh/?a=684
https://scanme.sh/?a=685
https://scanme.sh/?a=686
https://scanme.sh/?a=687
https://scanme.sh/?a=688
https://scanme.sh/?a=689
https://scanme.sh/?a=690
https://scanme.sh/?a=691
https://scanme.sh/?a=692
https://scanme.sh/?a=693
https://scanme.sh/?a=694
https://scanme.sh/?a=695
https://scanme.sh/?a=696
https://scanme.sh/?a=697
https://scanme.sh/?a=698
https://scanme.sh/?a=699
https://scanme.sh/?a=700
https://scanme.sh/?a=701
https://scanme.sh/?a=702
https://scanme.sh/?a=703
https://scanme.sh/?a=704
https://scanme.sh/?a=705
https://scanme.sh/?a=706
https://scanme.sh/?a=707
https://scanme.sh/?a=708
https://scanme.sh/?a=709
https://scanme.sh/?a=710
https://scanme.sh/?a=711
https://scanme.sh/?a=712
https://scanme.sh/?a=713
https://scanme.sh/?a=714
https://scanme.sh/?a=715
https://scanme.sh/?a=716
https://scanme.sh/?a=717
https://scanme.sh/?a=718
https://scanme.sh/?a=719
https://scanme.sh/?a=720
https://scanme.sh/?a=721
https://scanme.sh/?a=722
https://scanme.sh/?a=723
https://scanme.sh/?a=724
https://scanme.sh/?a=725
https://scanme.sh/?a=726
https://scanme.sh/?a=727
https://scanme.sh/?a=728
https://scanme.sh/?a=729
https://scanme.sh/?a=730
https://scanme.sh/?a=731
https://scanme.sh/?a=732
https://scanme.sh/?a=733
https://scanme.sh/?a=734
https://scanme.sh/?a=735
https://scanme.sh/?a=736
https://scanme.sh/?a=737
https://scanme.sh/?a=738
https://scanme.sh/?a=739
https://scanme.sh/?a=740
https://scanme.sh/?a=741
https://scanme.sh/?a=742
https://scanme.sh/?a=743
https://scanme.sh/?a=744
https://scanme.sh/?a=745
https://scanme.sh/?a=746
https://scanme.sh/?a=747
https://scanme.sh/?a=748
https://scanme.sh/?a=749
https://scanme.sh/?a=750
https://scanme.sh/?a=751
https://scanme.sh/?a=752
https://scanme.sh/?a=753
https://scanme.sh/?a=754
https://scanme.sh/?a=755
https://scanme.sh/?a=756
https://scanme.sh/?a=757
https://scanme.sh/?a=758
https://scanme.sh/?a=759
https://scanme.sh/?a=760
https://scanme.sh/?a=761
https://scanme.sh/?a=762
https://scanme.sh/?a=763
https://scanme.sh/?a=764
https://scanme.sh/?a=765
https://scanme.sh/?a=766
https://scanme.sh/?a=767
https://scanme.sh/?a=768
https://scanme.sh/?a=769
https://scanme.sh/?a=770
https://scanme.sh/?a=771
https://scanme.sh/?a=772
https://scanme.sh/?a=773
https://scanme.sh/?a=774
https://scanme.sh/?a=775
https://scanme.sh/?a=776
https://scanme.sh/?a=777
https://scanme.sh/?a=778
https://scanme.sh/?a=779
https://scanme.sh/?a=780
https://scanme.sh/?a=781
https://scanme.sh/?a=782
https://scanme.sh/?a=783
https://scanme.sh/?a=784
https://scanme.sh/?a=785
https://scanme.sh/?a=786
https://scanme.sh/?a=787
https://scanme.sh/?a=788
https://scanme.sh/?a=789
https://scanme.sh/?a=790
https://scanme.sh/?a=791
https://scanme.sh/?a=792
https://scanme.sh/?a=793
https://scanme.sh/?a=794
https://scanme.sh/?a=795
https://scanme.sh/?a=796
https://scanme.sh/?a=797
https://scanme.sh/?a=798
https://scanme.sh/?a=799
https://scanme.sh/?a=800
https://scanme.sh/?a=801
https://scanme.sh/?a=802
https://scanme.sh/?a=803
https://scanme.sh/?a=804
https://scanme.sh/?a=805
https://scanme.sh/?a=806
https://scanme.sh/?a=807
https://scanme.sh/?a=808
https://scanme.sh/?a=809
https://scanme.sh/?a=810
https://scanme.sh/?a=811
https://scanme.sh/?a=812
https://scanme.sh/?a=813
https://scanme.sh/?a=814
https://scanme.sh/?a=815
https://scanme.sh/?a=816
https://scanme.sh/?a=817
https://scanme.sh/?a=818
https://scanme.sh/?a=819
https://scanme.sh/?a=820
https://scanme.sh/?a=821
https://scanme.sh/?a=822
https://scanme.sh/?a=823
https://scanme.sh/?a=824
https://scanme.sh/?a=825
https://scanme.sh/?a=826
https://scanme.sh/?a=827
https://scanme.sh/?a=828
https://scanme.sh/?a=829
https://scanme.sh/?a=830
https://scanme.sh/?a=831
https://scanme.sh/?a=832
https://scanme.sh/?a=833
https://scanme.sh/?a=834
https://scanme.sh/?a=835
https://scanme.sh/?a=836
https://scanme.sh/?a=837
https://scanme.sh/?a=838
https://scanme.sh/?a=839
https://scanme.sh/?a=840
https://scanme.sh/?a=841
https://scanme.sh/?a=842
https://scanme.sh/?a=843
https://scanme.sh/?a=844
https://scanme.sh/?a=845
https://scanme.sh/?a=846
https://scanme.sh/?a=847
https://scanme.sh/?a=848
https://scanme.sh/?a=849
https://scanme.sh/?a=850
https://scanme.sh/?a=851
https://scanme.sh/?a=852
https://scanme.sh/?a=853
https://scanme.sh/?a=854
https://scanme.sh/?a=855
https://scanme.sh/?a=856
https://scanme.sh/?a=857
https://scanme.sh/?a=858
https://scanme.sh/?a=859
https://scanme.sh/?a=860
https://scanme.sh/?a=861
https://scanme.sh/?a=862
https://scanme.sh/?a=863
https://scanme.sh/?a=864
https://scanme.sh/?a=865
https://scanme.sh/?a=866
https://scanme.sh/?a=867
https://scanme.sh/?a=868
https://scanme.sh/?a=869
https://scanme.sh/?a=870
https://scanme.sh/?a=871
https://scanme.sh/?a=872
https://scanme.sh/?a=873
https://scanme.sh/?a=874
https://scanme.sh/?a=875
https://scanme.sh/?a=876
https://scanme.sh/?a=877
https://scanme.sh/?a=878
https://scanme.sh/?a=879
https://scanme.sh/?a=880
https://scanme.sh/?a=881
https://scanme.sh/?a=882
https://scanme.sh/?a=883
https://scanme.sh/?a=884
https://scanme.sh/?a=885
https://scanme.sh/?a=886
https://scanme.sh/?a=887
https://scanme.sh/?a=888
https://scanme.sh/?a=889
https://scanme.sh/?a=890
https://scanme.sh/?a=891
https://scanme.sh/?a=892
https://scanme.sh/?a=893
https://scanme.sh/?a=894
https://scanme.sh/?a=895
https://scanme.sh/?a=896
https://scanme.sh/?a=897
https://scanme.sh/?a=898
https://scanme.sh/?a=899
https://scanme.sh/?a=900
https://scanme.sh/?a=901
https://scanme.sh/?a=902
https://scanme.sh/?a=903
https://scanme.sh/?a=904
https://scanme.sh/?a=905
https://scanme.sh/?a=906
https://scanme.sh/?a=907
https://scanme.sh/?a=908
https://scanme.sh/?a=909
https://scanme.sh/?a=910
https://scanme.sh/?a=911
https://scanme.sh/?a=912
https://scanme.sh/?a=913
https://scanme.sh/?a=914
https://scanme.sh/?a=915
https://scanme.sh/?a=916
https://scanme.sh/?a=917
https://scanme.sh/?a=918
https://scanme.sh/?a=919
https://scanme.sh/?a=920
https://scanme.sh/?a=921
https://scanme.sh/?a=922
https://scanme.sh/?a=923
https://scanme.sh/?a=924
https://scanme.sh/?a=925
https://scanme.sh/?a=926
https://scanme.sh/?a=927
https://scanme.sh/?a=928
https://scanme.sh/?a=929
https://scanme.sh/?a=930
https://scanme.sh/?a=931
https://scanme.sh/?a=932
https://scanme.sh/?a=933
https://scanme.sh/?a=934
https://scanme.sh/?a=935
https://scanme.sh/?a=936
https://scanme.sh/?a=937
https://scanme.sh/?a=938
https://scanme.sh/?a=939
https://scanme.sh/?a=940
https://scanme.sh/?a=941
https://scanme.sh/?a=942
https://scanme.sh/?a=943
https://scanme.sh/?a=944
https://scanme.sh/?a=945
https://scanme.sh/?a=946
https://scanme.sh/?a=947
https://scanme.sh/?a=948
https://scanme.sh/?a=949
https://scanme.sh/?a=950
https://scanme.sh/?a=951
https://scanme.sh/?a=952
https://scanme.sh/?a=953
https://scanme.sh/?a=954
https://scanme.sh/?a=955
https://scanme.sh/?a=956
https://scanme.sh/?a=957
https://scanme.sh/?a=958
https://scanme.sh/?a=959
https://scanme.sh/?a=960
https://scanme.sh/?a=961
https://scanme.sh/?a=962
https://scanme.sh/?a=963
https://scanme.sh/?a=964
https://scanme.sh/?a=965
https://scanme.sh/?a=966
https://scanme.sh/?a=967
https://scanme.sh/?a=968
https://scanme.sh/?a=969
https://scanme.sh/?a=970
https://scanme.sh/?a=971
https://scanme.sh/?a=972
https://scanme.sh/?a=973
https://scanme.sh/?a=974
https://scanme.sh/?a=975
https://scanme.sh/?a=976
https://scanme.sh/?a=977
https://scanme.sh/?a=978
https://scanme.sh/?a=979
https://scanme.sh/?a=980
https://scanme.sh/?a=981
https://scanme.sh/?a=982
https://scanme.sh/?a=983
https://scanme.sh/?a=984
https://scanme.sh/?a=985
https://scanme.sh/?a=986
https://scanme.sh/?a=987
https://scanme.sh/?a=988
https://scanme.sh/?a=989
https://scanme.sh/?a=990
https://scanme.sh/?a=991
https://scanme.sh/?a=992
https://scanme.sh/?a=993
https://scanme.sh/?a=994
https://scanme.sh/?a=995
https://scanme.sh/?a=996
https://scanme.sh/?a=997
https://scanme.sh/?a=998
https://scanme.sh/?a=999
https://scanme.sh/?a=1000
================================================
FILE: cmd/functional-test/targets-150.txt
================================================
https://scanme.sh/?a=1
https://scanme.sh/?a=2
https://scanme.sh/?a=3
https://scanme.sh/?a=4
https://scanme.sh/?a=5
https://scanme.sh/?a=6
https://scanme.sh/?a=7
https://scanme.sh/?a=8
https://scanme.sh/?a=9
https://scanme.sh/?a=10
https://scanme.sh/?a=11
https://scanme.sh/?a=12
https://scanme.sh/?a=13
https://scanme.sh/?a=14
https://scanme.sh/?a=15
https://scanme.sh/?a=16
https://scanme.sh/?a=17
https://scanme.sh/?a=18
https://scanme.sh/?a=19
https://scanme.sh/?a=20
https://scanme.sh/?a=21
https://scanme.sh/?a=22
https://scanme.sh/?a=23
https://scanme.sh/?a=24
https://scanme.sh/?a=25
https://scanme.sh/?a=26
https://scanme.sh/?a=27
https://scanme.sh/?a=28
https://scanme.sh/?a=29
https://scanme.sh/?a=30
https://scanme.sh/?a=31
https://scanme.sh/?a=32
https://scanme.sh/?a=33
https://scanme.sh/?a=34
https://scanme.sh/?a=35
https://scanme.sh/?a=36
https://scanme.sh/?a=37
https://scanme.sh/?a=38
https://scanme.sh/?a=39
https://scanme.sh/?a=40
https://scanme.sh/?a=41
https://scanme.sh/?a=42
https://scanme.sh/?a=43
https://scanme.sh/?a=44
https://scanme.sh/?a=45
https://scanme.sh/?a=46
https://scanme.sh/?a=47
https://scanme.sh/?a=48
https://scanme.sh/?a=49
https://scanme.sh/?a=50
https://scanme.sh/?a=51
https://scanme.sh/?a=52
https://scanme.sh/?a=53
https://scanme.sh/?a=54
https://scanme.sh/?a=55
https://scanme.sh/?a=56
https://scanme.sh/?a=57
https://scanme.sh/?a=58
https://scanme.sh/?a=59
https://scanme.sh/?a=60
https://scanme.sh/?a=61
https://scanme.sh/?a=62
https://scanme.sh/?a=63
https://scanme.sh/?a=64
https://scanme.sh/?a=65
https://scanme.sh/?a=66
https://scanme.sh/?a=67
https://scanme.sh/?a=68
https://scanme.sh/?a=69
https://scanme.sh/?a=70
https://scanme.sh/?a=71
https://scanme.sh/?a=72
https://scanme.sh/?a=73
https://scanme.sh/?a=74
https://scanme.sh/?a=75
https://scanme.sh/?a=76
https://scanme.sh/?a=77
https://scanme.sh/?a=78
https://scanme.sh/?a=79
https://scanme.sh/?a=80
https://scanme.sh/?a=81
https://scanme.sh/?a=82
https://scanme.sh/?a=83
https://scanme.sh/?a=84
https://scanme.sh/?a=85
https://scanme.sh/?a=86
https://scanme.sh/?a=87
https://scanme.sh/?a=88
https://scanme.sh/?a=89
https://scanme.sh/?a=90
https://scanme.sh/?a=91
https://scanme.sh/?a=92
https://scanme.sh/?a=93
https://scanme.sh/?a=94
https://scanme.sh/?a=95
https://scanme.sh/?a=96
https://scanme.sh/?a=97
https://scanme.sh/?a=98
https://scanme.sh/?a=99
https://scanme.sh/?a=100
https://scanme.sh/?a=101
https://scanme.sh/?a=102
https://scanme.sh/?a=103
https://scanme.sh/?a=104
https://scanme.sh/?a=105
https://scanme.sh/?a=106
https://scanme.sh/?a=107
https://scanme.sh/?a=108
https://scanme.sh/?a=109
https://scanme.sh/?a=110
https://scanme.sh/?a=111
https://scanme.sh/?a=112
https://scanme.sh/?a=113
https://scanme.sh/?a=114
https://scanme.sh/?a=115
https://scanme.sh/?a=116
https://scanme.sh/?a=117
https://scanme.sh/?a=118
https://scanme.sh/?a=119
https://scanme.sh/?a=120
https://scanme.sh/?a=121
https://scanme.sh/?a=122
https://scanme.sh/?a=123
https://scanme.sh/?a=124
https://scanme.sh/?a=125
https://scanme.sh/?a=126
https://scanme.sh/?a=127
https://scanme.sh/?a=128
https://scanme.sh/?a=129
https://scanme.sh/?a=130
https://scanme.sh/?a=131
https://scanme.sh/?a=132
https://scanme.sh/?a=133
https://scanme.sh/?a=134
https://scanme.sh/?a=135
https://scanme.sh/?a=136
https://scanme.sh/?a=137
https://scanme.sh/?a=138
https://scanme.sh/?a=139
https://scanme.sh/?a=140
https://scanme.sh/?a=141
https://scanme.sh/?a=142
https://scanme.sh/?a=143
https://scanme.sh/?a=144
https://scanme.sh/?a=145
https://scanme.sh/?a=146
https://scanme.sh/?a=147
https://scanme.sh/?a=148
https://scanme.sh/?a=149
https://scanme.sh/?a=150
================================================
FILE: cmd/functional-test/targets-250.txt
================================================
https://scanme.sh/?a=1
https://scanme.sh/?a=2
https://scanme.sh/?a=3
https://scanme.sh/?a=4
https://scanme.sh/?a=5
https://scanme.sh/?a=6
https://scanme.sh/?a=7
https://scanme.sh/?a=8
https://scanme.sh/?a=9
https://scanme.sh/?a=10
https://scanme.sh/?a=11
https://scanme.sh/?a=12
https://scanme.sh/?a=13
https://scanme.sh/?a=14
https://scanme.sh/?a=15
https://scanme.sh/?a=16
https://scanme.sh/?a=17
https://scanme.sh/?a=18
https://scanme.sh/?a=19
https://scanme.sh/?a=20
https://scanme.sh/?a=21
https://scanme.sh/?a=22
https://scanme.sh/?a=23
https://scanme.sh/?a=24
https://scanme.sh/?a=25
https://scanme.sh/?a=26
https://scanme.sh/?a=27
https://scanme.sh/?a=28
https://scanme.sh/?a=29
https://scanme.sh/?a=30
https://scanme.sh/?a=31
https://scanme.sh/?a=32
https://scanme.sh/?a=33
https://scanme.sh/?a=34
https://scanme.sh/?a=35
https://scanme.sh/?a=36
https://scanme.sh/?a=37
https://scanme.sh/?a=38
https://scanme.sh/?a=39
https://scanme.sh/?a=40
https://scanme.sh/?a=41
https://scanme.sh/?a=42
https://scanme.sh/?a=43
https://scanme.sh/?a=44
https://scanme.sh/?a=45
https://scanme.sh/?a=46
https://scanme.sh/?a=47
https://scanme.sh/?a=48
https://scanme.sh/?a=49
https://scanme.sh/?a=50
https://scanme.sh/?a=51
https://scanme.sh/?a=52
https://scanme.sh/?a=53
https://scanme.sh/?a=54
https://scanme.sh/?a=55
https://scanme.sh/?a=56
https://scanme.sh/?a=57
https://scanme.sh/?a=58
https://scanme.sh/?a=59
https://scanme.sh/?a=60
https://scanme.sh/?a=61
https://scanme.sh/?a=62
https://scanme.sh/?a=63
https://scanme.sh/?a=64
https://scanme.sh/?a=65
https://scanme.sh/?a=66
https://scanme.sh/?a=67
https://scanme.sh/?a=68
https://scanme.sh/?a=69
https://scanme.sh/?a=70
https://scanme.sh/?a=71
https://scanme.sh/?a=72
https://scanme.sh/?a=73
https://scanme.sh/?a=74
https://scanme.sh/?a=75
https://scanme.sh/?a=76
https://scanme.sh/?a=77
https://scanme.sh/?a=78
https://scanme.sh/?a=79
https://scanme.sh/?a=80
https://scanme.sh/?a=81
https://scanme.sh/?a=82
https://scanme.sh/?a=83
https://scanme.sh/?a=84
https://scanme.sh/?a=85
https://scanme.sh/?a=86
https://scanme.sh/?a=87
https://scanme.sh/?a=88
https://scanme.sh/?a=89
https://scanme.sh/?a=90
https://scanme.sh/?a=91
https://scanme.sh/?a=92
https://scanme.sh/?a=93
https://scanme.sh/?a=94
https://scanme.sh/?a=95
https://scanme.sh/?a=96
https://scanme.sh/?a=97
https://scanme.sh/?a=98
https://scanme.sh/?a=99
https://scanme.sh/?a=100
https://scanme.sh/?a=101
https://scanme.sh/?a=102
https://scanme.sh/?a=103
https://scanme.sh/?a=104
https://scanme.sh/?a=105
https://scanme.sh/?a=106
https://scanme.sh/?a=107
https://scanme.sh/?a=108
https://scanme.sh/?a=109
https://scanme.sh/?a=110
https://scanme.sh/?a=111
https://scanme.sh/?a=112
https://scanme.sh/?a=113
https://scanme.sh/?a=114
https://scanme.sh/?a=115
https://scanme.sh/?a=116
https://scanme.sh/?a=117
https://scanme.sh/?a=118
https://scanme.sh/?a=119
https://scanme.sh/?a=120
https://scanme.sh/?a=121
https://scanme.sh/?a=122
https://scanme.sh/?a=123
https://scanme.sh/?a=124
https://scanme.sh/?a=125
https://scanme.sh/?a=126
https://scanme.sh/?a=127
https://scanme.sh/?a=128
https://scanme.sh/?a=129
https://scanme.sh/?a=130
https://scanme.sh/?a=131
https://scanme.sh/?a=132
https://scanme.sh/?a=133
https://scanme.sh/?a=134
https://scanme.sh/?a=135
https://scanme.sh/?a=136
https://scanme.sh/?a=137
https://scanme.sh/?a=138
https://scanme.sh/?a=139
https://scanme.sh/?a=140
https://scanme.sh/?a=141
https://scanme.sh/?a=142
https://scanme.sh/?a=143
https://scanme.sh/?a=144
https://scanme.sh/?a=145
https://scanme.sh/?a=146
https://scanme.sh/?a=147
https://scanme.sh/?a=148
https://scanme.sh/?a=149
https://scanme.sh/?a=150
https://scanme.sh/?a=151
https://scanme.sh/?a=152
https://scanme.sh/?a=153
https://scanme.sh/?a=154
https://scanme.sh/?a=155
https://scanme.sh/?a=156
https://scanme.sh/?a=157
https://scanme.sh/?a=158
https://scanme.sh/?a=159
https://scanme.sh/?a=160
https://scanme.sh/?a=161
https://scanme.sh/?a=162
https://scanme.sh/?a=163
https://scanme.sh/?a=164
https://scanme.sh/?a=165
https://scanme.sh/?a=166
https://scanme.sh/?a=167
https://scanme.sh/?a=168
https://scanme.sh/?a=169
https://scanme.sh/?a=170
https://scanme.sh/?a=171
https://scanme.sh/?a=172
https://scanme.sh/?a=173
https://scanme.sh/?a=174
https://scanme.sh/?a=175
https://scanme.sh/?a=176
https://scanme.sh/?a=177
https://scanme.sh/?a=178
https://scanme.sh/?a=179
https://scanme.sh/?a=180
https://scanme.sh/?a=181
https://scanme.sh/?a=182
https://scanme.sh/?a=183
https://scanme.sh/?a=184
https://scanme.sh/?a=185
https://scanme.sh/?a=186
https://scanme.sh/?a=187
https://scanme.sh/?a=188
https://scanme.sh/?a=189
https://scanme.sh/?a=190
https://scanme.sh/?a=191
https://scanme.sh/?a=192
https://scanme.sh/?a=193
https://scanme.sh/?a=194
https://scanme.sh/?a=195
https://scanme.sh/?a=196
https://scanme.sh/?a=197
https://scanme.sh/?a=198
https://scanme.sh/?a=199
https://scanme.sh/?a=200
https://scanme.sh/?a=201
https://scanme.sh/?a=202
https://scanme.sh/?a=203
https://scanme.sh/?a=204
https://scanme.sh/?a=205
https://scanme.sh/?a=206
https://scanme.sh/?a=207
https://scanme.sh/?a=208
https://scanme.sh/?a=209
https://scanme.sh/?a=210
https://scanme.sh/?a=211
https://scanme.sh/?a=212
https://scanme.sh/?a=213
https://scanme.sh/?a=214
https://scanme.sh/?a=215
https://scanme.sh/?a=216
https://scanme.sh/?a=217
https://scanme.sh/?a=218
https://scanme.sh/?a=219
https://scanme.sh/?a=220
https://scanme.sh/?a=221
https://scanme.sh/?a=222
https://scanme.sh/?a=223
https://scanme.sh/?a=224
https://scanme.sh/?a=225
https://scanme.sh/?a=226
https://scanme.sh/?a=227
https://scanme.sh/?a=228
https://scanme.sh/?a=229
https://scanme.sh/?a=230
https://scanme.sh/?a=231
https://scanme.sh/?a=232
https://scanme.sh/?a=233
https://scanme.sh/?a=234
https://scanme.sh/?a=235
https://scanme.sh/?a=236
https://scanme.sh/?a=237
https://scanme.sh/?a=238
https://scanme.sh/?a=239
https://scanme.sh/?a=240
https://scanme.sh/?a=241
https://scanme.sh/?a=242
https://scanme.sh/?a=243
https://scanme.sh/?a=244
https://scanme.sh/?a=245
https://scanme.sh/?a=246
https://scanme.sh/?a=247
https://scanme.sh/?a=248
https://scanme.sh/?a=249
https://scanme.sh/?a=250
================================================
FILE: cmd/functional-test/targets.txt
================================================
scanme.sh
scanme.sh?a=1
scanme.sh?a=2
scanme.sh?a=3
================================================
FILE: cmd/functional-test/testcases.txt
================================================
# Simple binary invocation
{{binary}}
# Template tags filter
{{binary}} -tags cve -ntv 8.8.8,8.8.9
{{binary}} -tags cve
{{binary}} -tags cve,exposure
{{binary}} -tags cve,exposure -tags token
{{binary}} -tags cve,exposure -tags token,logs
{{binary}} -tags "cve","exposure" -tags "token","logs"
{{binary}} -tags 'cve','exposure' -tags 'token','logs'
{{binary}} -tags cve -severity high
{{binary}} -tags cve,exposure -severity high,critical
{{binary}} -tags cve,exposure -severity high,critical,medium
{{binary}} -tags cve -author geeknik
{{binary}} -tags cve -author geeknik,pdteam
{{binary}} -tags cve -author geeknik -severity high
{{binary}} -tags cve
{{binary}} -tags cve,exposure
{{binary}} -tags cve,exposure -tags token
{{binary}} -tags cve,exposure -tags token,logs
{{binary}} -tags "cve","exposure" -tags "token","logs"
{{binary}} -tags 'cve','exposure' -tags 'token','logs'
{{binary}} -tags cve -severity high
{{binary}} -tags cve,exposure -severity high,critical
{{binary}} -tags cve,exposure -severity high,critical,medium
{{binary}} -tags cve -author geeknik
{{binary}} -tags cve -author geeknik,pdteam
{{binary}} -tags cve -author geeknik -severity high
{{binary}} -tags cve,exposure -author geeknik,pdteam -severity high,critical
{{binary}} -tags "cve,exposure" -author "geeknik,pdteam" -severity high,critical
{{binary}} -tags cve -etags ssrf
{{binary}} -tags cve,exposure -etags ssrf,config
{{binary}} -tags cve,exposure -etags ssrf,config -severity high
{{binary}} -tags cve,exposure -etags ssrf,config -severity high -author geeknik
{{binary}} -tags cve,dos,fuzz
{{binary}} -tags cve -include-tags dos,fuzz
{{binary}} -tags cve -exclude-tags cve2020
{{binary}} -tags cve -exclude-templates http/cves/2020/
{{binary}} -tags cve -exclude-templates http/cves/2020/CVE-2020-9757.yaml
{{binary}} -tags cve -exclude-templates http/cves/2020/CVE-2020-9757.yaml -exclude-templates http/cves/2021/
{{binary}} -t http/cves/
{{binary}} -t http/cves/ -t http/exposures/
{{binary}} -t http/cves/ -t http/exposures/ -tags config
{{binary}} -t http/cves/ -t http/exposures/ -tags config,ssrf
{{binary}} -t http/cves/ -t http/exposures/ -tags config -severity high,critical
{{binary}} -t http/cves/ -t http/exposures/ -tags config -severity high,critical -author geeknik,pdteam
{{binary}} -t http/cves/ -t http/exposures/ -tags config -severity high,critical -author geeknik,pdteam -etags sqli
{{binary}} -t http/cves/ -t http/exposures/ -tags config -severity high,critical -author geeknik,pdteam -etags sqli -exclude-templates http/cves/2021/
{{binary}} -t http/cves/ -t http/exposures/ -tags config -severity high,critical -author geeknik,pdteam -etags sqli -exclude-templates http/cves/2017/CVE-2017-7269.yaml
{{binary}} -t http/cves/ -t http/exposures/ -tags config -severity high,critical -author geeknik,pdteam -etags sqli -include-templates http/cves/2017/CVE-2017-7269.yaml
# Advanced Filtering
{{binary}} -tags cve -author geeknik,pdteam -tc severity=='high'
{{binary}} -tc contains(authors,'pdteam')
{{binary}} -t http/cves/ -t http/exposures/ -tc contains(tags,'cve') -exclude-templates http/cves/2020/CVE-2020-9757.yaml
{{binary}} -tc protocol=='dns'
{{binary}} -tc contains(http_method,'GET')
{{binary}} -tc len(body)>0
{{binary}} -tc contains(matcher_type,'word')
{{binary}} -tc contains(extractor_type,'regex')
{{binary}} -tc contains(description,'wordpress')
# Workflow Filters
{{binary}} -w workflows
{{binary}} -w workflows -author geeknik,pdteam
{{binary}} -w workflows -severity high,critical
{{binary}} -w workflows -author geeknik,pdteam -severity high,critical
# Input Types
# http protocol
# host
{{binary}} -id tech-detect -u scanme.sh
# host:port
{{binary}} -id tech-detect -u scanme.sh:80
# scheme://host:port
{{binary}} -id tech-detect -u http://scanme.sh:80
# scheme://host
{{binary}} -id tech-detect -u https://scanme.sh
# Network Protocol
# host
{{binary}} -id ftp-weak-credentials -u scanme.sh
# host:port
{{binary}} -id ftp-weak-credentials -u scanme.sh:21
# SSL Protocol
# host
{{binary}} -id tls-version -u scanme.sh
# host:port
{{binary}} -id tls-version -u scanme.sh:22
# Options
# Tls Impersonate
{{binary}} -id tech-detect -tlsi -u https://scanme.sh
================================================
FILE: cmd/generate-checksum/main.go
================================================
package main
import (
"bytes"
"crypto/sha1"
"encoding/hex"
"io"
"io/fs"
"log"
"os"
"path/filepath"
"strings"
)
func main() {
if len(os.Args) < 3 {
log.Fatalf("Usage: %s \n", os.Args[0])
}
checksumFile := os.Args[2]
templatesDirectory := os.Args[1]
file, err := os.Create(checksumFile)
if err != nil {
log.Fatalf("Could not create file: %s\n", err)
}
defer func() {
_ = file.Close()
}()
err = filepath.WalkDir(templatesDirectory, func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() {
return nil
}
pathIndex := path[strings.Index(path, "nuclei-templates/")+17:]
pathIndex = strings.TrimPrefix(pathIndex, "nuclei-templates/")
// Ignore items starting with dots
if strings.HasPrefix(pathIndex, ".") {
return nil
}
data, err := os.ReadFile(path)
if err != nil {
return nil
}
h := sha1.New()
_, _ = io.Copy(h, bytes.NewReader(data))
hash := hex.EncodeToString(h.Sum(nil))
_, _ = file.WriteString(pathIndex)
_, _ = file.WriteString(":")
_, _ = file.WriteString(hash)
_, _ = file.WriteString("\n")
return nil
})
if err != nil {
log.Fatalf("Could not walk directory: %s\n", err)
}
}
================================================
FILE: cmd/integration-test/code.go
================================================
package main
import (
"errors"
"log"
"os"
"path/filepath"
osutils "github.com/projectdiscovery/utils/os"
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
"github.com/projectdiscovery/nuclei/v3/pkg/templates/signer"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
)
var isCodeDisabled = func() bool { return osutils.IsWindows() && os.Getenv("CI") == "true" }
var codeTestCases = []TestCaseInfo{
{Path: "protocols/code/py-snippet.yaml", TestCase: &codeSnippet{}, DisableOn: isCodeDisabled},
{Path: "protocols/code/py-file.yaml", TestCase: &codeFile{}, DisableOn: isCodeDisabled},
{Path: "protocols/code/py-env-var.yaml", TestCase: &codeEnvVar{}, DisableOn: isCodeDisabled},
{Path: "protocols/code/unsigned.yaml", TestCase: &unsignedCode{}, DisableOn: isCodeDisabled},
{Path: "protocols/code/py-nosig.yaml", TestCase: &codePyNoSig{}, DisableOn: isCodeDisabled},
{Path: "protocols/code/py-interactsh.yaml", TestCase: &codeSnippet{}, DisableOn: isCodeDisabled},
{Path: "protocols/code/ps1-snippet.yaml", TestCase: &codeSnippet{}, DisableOn: func() bool { return !osutils.IsWindows() || isCodeDisabled() }},
{Path: "protocols/code/pre-condition.yaml", TestCase: &codePreCondition{}, DisableOn: isCodeDisabled},
{Path: "protocols/code/sh-virtual.yaml", TestCase: &codeSnippet{}, DisableOn: func() bool { return !osutils.IsLinux() || isCodeDisabled() }},
{Path: "protocols/code/py-virtual.yaml", TestCase: &codeSnippet{}, DisableOn: func() bool { return !osutils.IsLinux() || isCodeDisabled() }},
{Path: "protocols/code/pwsh-echo.yaml", TestCase: &codeSnippet{}, DisableOn: func() bool { return isCodeDisabled() }},
}
const (
testCertFile = "protocols/keys/ci.crt"
testKeyFile = "protocols/keys/ci-private-key.pem"
)
var testcertpath = ""
func init() {
if isCodeDisabled() {
// skip executing code protocol in CI on windows
return
}
// allow local file access to load content of file references in template
// in order to sign them for testing purposes
templates.TemplateSignerLFA()
tsigner, err := signer.NewTemplateSignerFromFiles(testCertFile, testKeyFile)
if err != nil {
panic(err)
}
testcertpath, _ = filepath.Abs(testCertFile)
for _, v := range codeTestCases {
templatePath := v.Path
testCase := v.TestCase
if v.DisableOn != nil && v.DisableOn() {
// skip ps1 test case on non-windows platforms
continue
}
templatePath, err := filepath.Abs(templatePath)
if err != nil {
panic(err)
}
// skip
// - unsigned test cases
if _, ok := testCase.(*unsignedCode); ok {
continue
}
if _, ok := testCase.(*codePyNoSig); ok {
continue
}
if err := templates.SignTemplate(tsigner, templatePath); err != nil {
log.Fatalf("Could not sign template %v got: %s\n", templatePath, err)
}
}
}
func getEnvValues() []string {
return []string{
signer.CertEnvVarName + "=" + testcertpath,
}
}
type codeSnippet struct{}
// Execute executes a test case and returns an error if occurred
func (h *codeSnippet) Execute(filePath string) error {
results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), "-t", filePath, "-u", "input", "-code")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type codePreCondition struct{}
// Execute executes a test case and returns an error if occurred
func (h *codePreCondition) Execute(filePath string) error {
results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), "-t", filePath, "-u", "input", "-code", "-esc")
if err != nil {
return err
}
if osutils.IsLinux() {
return expectResultsCount(results, 1)
} else {
return expectResultsCount(results, 0)
}
}
type codeFile struct{}
// Execute executes a test case and returns an error if occurred
func (h *codeFile) Execute(filePath string) error {
results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), "-t", filePath, "-u", "input", "-code")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type codeEnvVar struct{}
// Execute executes a test case and returns an error if occurred
func (h *codeEnvVar) Execute(filePath string) error {
results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), "-t", filePath, "-u", "input", "-V", "baz=baz", "-code")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type unsignedCode struct{}
// Execute executes a test case and returns an error if occurred
func (h *unsignedCode) Execute(filePath string) error {
results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), "-t", filePath, "-u", "input", "-code")
// should error out
if err != nil {
return nil
}
// this point should never be reached
return errors.Join(expectResultsCount(results, 1), errors.New("unsigned template was executed"))
}
type codePyNoSig struct{}
// Execute executes a test case and returns an error if occurred
func (h *codePyNoSig) Execute(filePath string) error {
results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), "-t", filePath, "-u", "input", "-code")
// should error out
if err != nil {
return nil
}
// this point should never be reached
return errors.Join(expectResultsCount(results, 1), errors.New("unsigned template was executed"))
}
================================================
FILE: cmd/integration-test/custom-dir.go
================================================
package main
import (
"os"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
)
type customConfigDirTest struct{}
var customConfigDirTestCases = []TestCaseInfo{
{Path: "protocols/dns/cname-fingerprint.yaml", TestCase: &customConfigDirTest{}},
}
// Execute executes a test case and returns an error if occurred
func (h *customConfigDirTest) Execute(filePath string) error {
customTempDirectory, err := os.MkdirTemp("", "")
if err != nil {
return err
}
defer func() {
_ = os.RemoveAll(customTempDirectory)
}()
results, err := testutils.RunNucleiBareArgsAndGetResults(debug, []string{"NUCLEI_CONFIG_DIR=" + customTempDirectory}, "-t", filePath, "-u", "8x8exch02.8x8.com")
if err != nil {
return err
}
if len(results) == 0 {
return nil
}
files, err := os.ReadDir(customTempDirectory)
if err != nil {
return err
}
var fileNames []string
for _, file := range files {
fileNames = append(fileNames, file.Name())
}
return expectResultsCount(fileNames, 4)
}
================================================
FILE: cmd/integration-test/dns.go
================================================
package main
import (
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
)
var dnsTestCases = []TestCaseInfo{
{Path: "protocols/dns/a.yaml", TestCase: &dnsBasic{}},
{Path: "protocols/dns/aaaa.yaml", TestCase: &dnsBasic{}},
{Path: "protocols/dns/cname.yaml", TestCase: &dnsBasic{}},
{Path: "protocols/dns/srv.yaml", TestCase: &dnsBasic{}},
{Path: "protocols/dns/ns.yaml", TestCase: &dnsBasic{}},
{Path: "protocols/dns/txt.yaml", TestCase: &dnsBasic{}},
{Path: "protocols/dns/ptr.yaml", TestCase: &dnsPtr{}},
{Path: "protocols/dns/caa.yaml", TestCase: &dnsCAA{}},
{Path: "protocols/dns/tlsa.yaml", TestCase: &dnsTLSA{}},
{Path: "protocols/dns/variables.yaml", TestCase: &dnsVariables{}},
{Path: "protocols/dns/payload.yaml", TestCase: &dnsPayload{}},
{Path: "protocols/dns/dsl-matcher-variable.yaml", TestCase: &dnsDSLMatcherVariable{}},
}
type dnsBasic struct{}
// Execute executes a test case and returns an error if occurred
func (h *dnsBasic) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "one.one.one.one", debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type dnsPtr struct{}
// Execute executes a test case and returns an error if occurred
func (h *dnsPtr) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "1.1.1.1", debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type dnsCAA struct{}
// Execute executes a test case and returns an error if occurred
func (h *dnsCAA) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "google.com", debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type dnsTLSA struct{}
// Execute executes a test case and returns an error if occurred
func (h *dnsTLSA) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
if err != nil {
return err
}
return expectResultsCount(results, 0)
}
type dnsVariables struct{}
// Execute executes a test case and returns an error if occurred
func (h *dnsVariables) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "one.one.one.one", debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type dnsPayload struct{}
// Execute executes a test case and returns an error if occurred
func (h *dnsPayload) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "google.com", debug)
if err != nil {
return err
}
if err := expectResultsCount(results, 3); err != nil {
return err
}
// override payload from CLI
results, err = testutils.RunNucleiTemplateAndGetResults(filePath, "google.com", debug, "-var", "subdomain_wordlist=subdomains.txt")
if err != nil {
return err
}
return expectResultsCount(results, 4)
}
type dnsDSLMatcherVariable struct{}
// Execute executes a test case and returns an error if occurred
func (h *dnsDSLMatcherVariable) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "one.one.one.one", debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
================================================
FILE: cmd/integration-test/dsl.go
================================================
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"github.com/julienschmidt/httprouter"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
)
var dslTestcases = []TestCaseInfo{
{Path: "dsl/hide-version-warning.yaml", TestCase: &dslVersionWarning{}},
{Path: "dsl/show-version-warning.yaml", TestCase: &dslShowVersionWarning{}},
}
var defaultDSLEnvs = []string{"HIDE_TEMPLATE_SIG_WARNING=true"}
type dslVersionWarning struct{}
func (d *dslVersionWarning) Execute(templatePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "DSL version parsing warning test")
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiArgsAndGetErrors(debug, defaultDSLEnvs, "-t", templatePath, "-target", ts.URL, "-v")
if err != nil {
return err
}
return expectResultsCount(results, 0)
}
type dslShowVersionWarning struct{}
func (d *dslShowVersionWarning) Execute(templatePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "DSL version parsing warning test")
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiArgsAndGetErrors(debug, append(defaultDSLEnvs, "SHOW_DSL_ERRORS=true"), "-t", templatePath, "-target", ts.URL, "-v")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
================================================
FILE: cmd/integration-test/exporters.go
================================================
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/mongo"
"github.com/testcontainers/testcontainers-go"
mongocontainer "github.com/testcontainers/testcontainers-go/modules/mongodb"
osutil "github.com/projectdiscovery/utils/os"
mongoclient "go.mongodb.org/mongo-driver/mongo"
mongooptions "go.mongodb.org/mongo-driver/mongo/options"
)
const (
dbName = "test"
dbImage = "mongo:8"
)
var exportersTestCases = []TestCaseInfo{
{Path: "exporters/mongo", TestCase: &mongoExporter{}, DisableOn: func() bool {
return osutil.IsWindows() || osutil.IsOSX()
}},
}
type mongoExporter struct{}
func (m *mongoExporter) Execute(filepath string) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
// Start a MongoDB container
mongodbContainer, err := mongocontainer.Run(ctx, dbImage)
defer func() {
if err := testcontainers.TerminateContainer(mongodbContainer); err != nil {
log.Printf("failed to terminate container: %s", err)
}
}()
if err != nil {
return fmt.Errorf("failed to start container: %w", err)
}
connString, err := mongodbContainer.ConnectionString(ctx)
if err != nil {
return fmt.Errorf("failed to get connection string for MongoDB container: %s", err)
}
connString = connString + dbName
// Create a MongoDB exporter and write a test result to the database
opts := mongo.Options{
ConnectionString: connString,
CollectionName: "test",
BatchSize: 1, // Ensure we write the result immediately
}
exporter, err := mongo.New(&opts)
if err != nil {
return fmt.Errorf("failed to create MongoDB exporter: %s", err)
}
defer func() {
if err := exporter.Close(); err != nil {
fmt.Printf("failed to close exporter: %s\n", err)
}
}()
res := &output.ResultEvent{
Request: "test request",
Response: "test response",
}
err = exporter.Export(res)
if err != nil {
return fmt.Errorf("failed to export result event to MongoDB: %s", err)
}
// Verify that the result was written to the database
clientOptions := mongooptions.Client().ApplyURI(connString)
client, err := mongoclient.Connect(ctx, clientOptions)
if err != nil {
return fmt.Errorf("error creating MongoDB client: %s", err)
}
defer func() {
if err := client.Disconnect(ctx); err != nil {
fmt.Printf("failed to disconnect from MongoDB: %s\n", err)
}
}()
collection := client.Database(dbName).Collection(opts.CollectionName)
var actualRes output.ResultEvent
err = collection.FindOne(ctx, map[string]interface{}{"request": res.Request}).Decode(&actualRes)
if err != nil {
return fmt.Errorf("failed to find document in MongoDB: %s", err)
}
if actualRes.Request != res.Request || actualRes.Response != res.Response {
return fmt.Errorf("exported result does not match expected result: got %v, want %v", actualRes, res)
}
return nil
}
================================================
FILE: cmd/integration-test/file.go
================================================
package main
import (
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
)
var fileTestcases = []TestCaseInfo{
{Path: "protocols/file/matcher-with-or.yaml", TestCase: &fileWithOrMatcher{}},
{Path: "protocols/file/matcher-with-and.yaml", TestCase: &fileWithAndMatcher{}},
{Path: "protocols/file/matcher-with-nested-and.yaml", TestCase: &fileWithAndMatcher{}},
{Path: "protocols/file/extract.yaml", TestCase: &fileWithExtractor{}},
}
type fileWithOrMatcher struct{}
// Execute executes a test case and returns an error if occurred
func (h *fileWithOrMatcher) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "protocols/file/data/", debug, "-file")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type fileWithAndMatcher struct{}
// Execute executes a test case and returns an error if occurred
func (h *fileWithAndMatcher) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "protocols/file/data/", debug, "-file")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type fileWithExtractor struct{}
// Execute executes a test case and returns an error if occurred
func (h *fileWithExtractor) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "protocols/file/data/", debug, "-file")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
================================================
FILE: cmd/integration-test/flow.go
================================================
package main
import (
"encoding/base64"
"fmt"
"net/http"
"net/http/httptest"
"github.com/julienschmidt/httprouter"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
)
var flowTestcases = []TestCaseInfo{
{Path: "flow/conditional-flow.yaml", TestCase: &conditionalFlow{}},
{Path: "flow/conditional-flow-negative.yaml", TestCase: &conditionalFlowNegative{}},
{Path: "flow/iterate-values-flow.yaml", TestCase: &iterateValuesFlow{}},
{Path: "flow/iterate-one-value-flow.yaml", TestCase: &iterateOneValueFlow{}},
{Path: "flow/dns-ns-probe.yaml", TestCase: &dnsNsProbe{}},
{Path: "flow/flow-hide-matcher.yaml", TestCase: &flowHideMatcher{}},
}
type conditionalFlow struct{}
func (t *conditionalFlow) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "cloud.projectdiscovery.io", debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type conditionalFlowNegative struct{}
func (t *conditionalFlowNegative) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
if err != nil {
return err
}
return expectResultsCount(results, 0)
}
type iterateValuesFlow struct{}
func (t *iterateValuesFlow) Execute(filePath string) error {
router := httprouter.New()
testemails := []string{
"secrets@scanme.sh",
"superadmin@scanme.sh",
}
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprint(w, testemails)
})
router.GET("/user/"+getBase64(testemails[0]), func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("Welcome ! This is test matcher text"))
})
router.GET("/user/"+getBase64(testemails[1]), func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("Welcome ! This is test matcher text"))
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 2)
}
type iterateOneValueFlow struct{}
func (t *iterateOneValueFlow) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "https://scanme.sh", debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type dnsNsProbe struct{}
func (t *dnsNsProbe) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "oast.fun", debug)
if err != nil {
return err
}
return expectResultsCount(results, 2)
}
func getBase64(input string) string {
return base64.StdEncoding.EncodeToString([]byte(input))
}
type flowHideMatcher struct{}
func (t *flowHideMatcher) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
if err != nil {
return err
}
// this matcher should not return any results
return expectResultsCount(results, 0)
}
================================================
FILE: cmd/integration-test/fuzz.go
================================================
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"github.com/julienschmidt/httprouter"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
)
const (
targetFile = "fuzz/testData/ginandjuice.proxify.yaml"
)
var fuzzingTestCases = []TestCaseInfo{
{Path: "fuzz/fuzz-mode.yaml", TestCase: &fuzzModeOverride{}},
{Path: "fuzz/fuzz-multi-mode.yaml", TestCase: &fuzzMultipleMode{}},
{Path: "fuzz/fuzz-type.yaml", TestCase: &fuzzTypeOverride{}},
{Path: "fuzz/fuzz-query.yaml", TestCase: &httpFuzzQuery{}},
{Path: "fuzz/fuzz-headless.yaml", TestCase: &HeadlessFuzzingQuery{}},
// for fuzzing we should prioritize adding test case related backend
// logic in fuzz playground server instead of adding them here
{Path: "fuzz/fuzz-query-num-replace.yaml", TestCase: &genericFuzzTestCase{expectedResults: 2}},
{Path: "fuzz/fuzz-host-header-injection.yaml", TestCase: &genericFuzzTestCase{expectedResults: 1}},
{Path: "fuzz/fuzz-path-sqli.yaml", TestCase: &genericFuzzTestCase{expectedResults: 1}},
{Path: "fuzz/fuzz-cookie-error-sqli.yaml", TestCase: &genericFuzzTestCase{expectedResults: 1}},
{Path: "fuzz/fuzz-body-json-sqli.yaml", TestCase: &genericFuzzTestCase{expectedResults: 1}},
{Path: "fuzz/fuzz-body-multipart-form-sqli.yaml", TestCase: &genericFuzzTestCase{expectedResults: 1}},
{Path: "fuzz/fuzz-body-params-sqli.yaml", TestCase: &genericFuzzTestCase{expectedResults: 1}},
{Path: "fuzz/fuzz-body-xml-sqli.yaml", TestCase: &genericFuzzTestCase{expectedResults: 1}},
{Path: "fuzz/fuzz-body-generic-sqli.yaml", TestCase: &genericFuzzTestCase{expectedResults: 4}},
}
type genericFuzzTestCase struct {
expectedResults int
}
func (g *genericFuzzTestCase) Execute(filePath string) error {
results, err := testutils.RunNucleiWithArgsAndGetResults(debug, "-t", filePath, "-l", targetFile, "-im", "yaml")
if err != nil {
return err
}
return expectResultsCount(results, g.expectedResults)
}
type httpFuzzQuery struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpFuzzQuery) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.Header().Set("Content-Type", "text/html")
value := r.URL.Query().Get("id")
_, _ = fmt.Fprintf(w, "This is test matcher text: %v", value)
})
ts := httptest.NewTLSServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"/?id=example", debug, "-fuzz")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type fuzzModeOverride struct{}
// Execute executes a test case and returns an error if occurred
func (h *fuzzModeOverride) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.Header().Set("Content-Type", "text/html")
value := r.URL.Query().Get("id")
_, _ = fmt.Fprintf(w, "This is test matcher text: %v", value)
})
ts := httptest.NewTLSServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"/?id=example&name=nuclei", debug, "-fuzzing-mode", "single", "-jsonl", "-fuzz")
if err != nil {
return err
}
if err = expectResultsCount(results, 1); err != nil {
return err
}
var event output.ResultEvent
err = json.Unmarshal([]byte(results[0]), &event)
if err != nil {
return fmt.Errorf("could not unmarshal event: %s", err)
}
// Check whether the matched value url query params are correct
// default fuzzing mode is multiple in template, so all query params should be fuzzed
// but using -fm flag we are overriding fuzzing mode to single,
// so only one query param should be fuzzed, and the other should be the same
//parse url to get query params
matchedURL, err := url.Parse(event.Matched)
if err != nil {
return err
}
values, err := url.ParseQuery(matchedURL.RawQuery)
if err != nil {
return err
}
if values.Get("name") != "nuclei" {
return fmt.Errorf("expected fuzzing should not override the name nuclei got %s", values.Get("name"))
}
return nil
}
type fuzzTypeOverride struct{}
// Execute executes a test case and returns an error if occurred
func (h *fuzzTypeOverride) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.Header().Set("Content-Type", "text/html")
value := r.URL.Query().Get("id")
_, _ = fmt.Fprintf(w, "This is test matcher text: %v", value)
})
ts := httptest.NewTLSServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"?id=example", debug, "-fuzzing-type", "replace", "-jsonl", "-fuzz")
if err != nil {
return err
}
if err = expectResultsCount(results, 1); err != nil {
return err
}
var event output.ResultEvent
err = json.Unmarshal([]byte(results[0]), &event)
if err != nil {
return fmt.Errorf("could not unmarshal event: %s", err)
}
// check whether the matched url query params are fuzzed
// default fuzzing type in template is postfix but we are overriding it to replace
// so the matched url query param should be replaced with fuzz-word
//parse url to get query params
matchedURL, err := url.Parse(event.Matched)
if err != nil {
return err
}
values, err := url.ParseQuery(matchedURL.RawQuery)
if err != nil {
return err
}
if values.Get("id") != "fuzz-word" {
return fmt.Errorf("expected id to be fuzz-word, got %s", values.Get("id"))
}
return nil
}
// HeadlessFuzzingQuery tests fuzzing is working not in headless mode
type HeadlessFuzzingQuery struct{}
// Execute executes a test case and returns an error if occurred
func (h *HeadlessFuzzingQuery) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
resp := fmt.Sprintf("%s", r.URL.Query().Get("url"))
_, _ = fmt.Fprint(w, resp)
})
ts := httptest.NewTLSServer(router)
defer ts.Close()
got, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"?url=https://scanme.sh", debug, "-headless", "-fuzz")
if err != nil {
return err
}
return expectResultsCount(got, 2)
}
type fuzzMultipleMode struct{}
// Execute executes a test case and returns an error if occurred
func (h *fuzzMultipleMode) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
xClientId := r.Header.Get("X-Client-Id")
xSecretId := r.Header.Get("X-Secret-Id")
if xClientId != "nuclei-v3" || xSecretId != "nuclei-v3" {
w.WriteHeader(http.StatusUnauthorized)
return
}
w.Header().Set("Content-Type", "text/html")
resp := fmt.Sprintf("
This is multi-mode fuzzing test: %v
", xClientId)
_, _ = fmt.Fprint(w, resp)
})
ts := httptest.NewTLSServer(router)
defer ts.Close()
got, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"?url=https://scanme.sh", debug, "-jsonl", "-fuzz")
if err != nil {
return err
}
return expectResultsCount(got, 1)
}
================================================
FILE: cmd/integration-test/generic.go
================================================
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"net/http"
"net/http/httptest"
"os"
"github.com/julienschmidt/httprouter"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
permissionutil "github.com/projectdiscovery/utils/permission"
)
var genericTestcases = []TestCaseInfo{
{Path: "generic/auth/certificate/http-get.yaml", TestCase: &clientCertificate{}},
}
var (
serverCRT = `-----BEGIN CERTIFICATE-----
MIIDEzCCAfsCFC21Zw7U0tGDyLyMalwfo9cWbL6dMA0GCSqGSIb3DQEBCwUAMEUx
CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl
cm5ldCBXaWRnaXRzIFB0eSBMdGQwIBcNMjMwNzI4MTAwNzI3WhgPMzAwMzA5Mjkx
MDA3MjdaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD
VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQCjMlvOKQX9yn9SOYPJ8p+jeDUU/JWPwT4LRfqaxvvKSnS7
NZzd7lS4AR0YTjyjiRj3+t0QnEDHVKBD8cMh9kMXkQ2S0r7psCURLvvZOYt4v6KM
CyZpBbp8b/pG3aJQHDZjRDOApQrXhx62XJDIs64YKA8NybYOLqNisrWGrfqF4uEz
RMgVGlthuQcXo3n2HzobuYN7RsHBzCWGLn9fRMDC2j3IAnQLf4YOznOJ57CjMd2W
mn/yhHK8h9s4iU5zw3+PK+X/IM4GeAfeJMx8c5uq2A8A24uzMidyhxJCK7VUprjK
/ckdNYya6dkG2De+LR7W82ygfWbFDOnZKM26cPG/AgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBAH5+Wdb/1jgBhihN6Pb6SWJmDvwkOEP3t00E3fBao4TDqdDOhPsLYrAm
8gt16OcGrrXDQA3bi79mAVqAqCvaf4hk0vSI0L4rNcCSP4D3fUBjRO3fY3fM4Qw8
xg9AusF5hRrvzFbEak7lPJ01kLOJEgBA1l457HrLnXcpDTml8Y46WqdWa6yVM33l
7tNaXWrPwYZYMTcRumIytsYtIJXp/sMLBIT0AO/QR4yarvVOeMSJ1va459PjKLBG
JGGmf2rigaT050e71QOrGyMXgT6xsNjJgzeVhUgPO422mPT692kDi2oB5DA0Fau0
4qm5CMFgmYcC3zQoN53aDs1mHyWeroc=
-----END CERTIFICATE-----
`
serverKey = `-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCjMlvOKQX9yn9S
OYPJ8p+jeDUU/JWPwT4LRfqaxvvKSnS7NZzd7lS4AR0YTjyjiRj3+t0QnEDHVKBD
8cMh9kMXkQ2S0r7psCURLvvZOYt4v6KMCyZpBbp8b/pG3aJQHDZjRDOApQrXhx62
XJDIs64YKA8NybYOLqNisrWGrfqF4uEzRMgVGlthuQcXo3n2HzobuYN7RsHBzCWG
Ln9fRMDC2j3IAnQLf4YOznOJ57CjMd2Wmn/yhHK8h9s4iU5zw3+PK+X/IM4GeAfe
JMx8c5uq2A8A24uzMidyhxJCK7VUprjK/ckdNYya6dkG2De+LR7W82ygfWbFDOnZ
KM26cPG/AgMBAAECggEAFtRko2J5xBcf2JDTLt0SF/wo8Nak1Ydi9pDDjgNoFdR0
n/vQBfvhPhxpxYysTvRO2eHuKvSw2zGredXIRmf82r8f9vokWuyZQt4fvTOfnzSv
uIeWx/pVLDM9/8vhePN5aEmSKtzrt1rfoQMx/eGk6RwxfuxI25MKqDP30O9lrHTn
Y0lW7dthgdDMlQnSpOqUm2ldDsykYCBFteh4i5RDzAhiGx1ryaz3FMg+/y0VTTk0
BM43qW6H9PD8P4iOau3DGIPNqtIlFSnWoYaM6Ta2osfzzdsnFbe5F7JbdMrf5MBc
Jq3VMUqffRmHubz7di03qRsRqGYQn2cJeiuVC+y6gQKBgQDYpq3MfMjwzPeoB1Ay
ZQdzx+T290XRxFZwkiv3uugsYMlFGEabdAMFx5oIIOdjWSBLI92RvXbg7qMd/xMC
ya/GzbKQd+5GbRLW+TZ0odGkMFkTo+DEkt07yEM8mrPJ6XePUndHbiNFSdpVKx4g
KdmiRHinm3R8Lr5/puvISrOdcwKBgQDA1kln9aD1mvIdObI6MubPitb+NuNcpVDo
myc1UrEJbcn8nBbLb+0Q+7gckjau2C8GN7Olnd8RCYLc7kU1On2pY+f19Ru/PdZX
cjCCTcxqCJvWkNWOzw14ag6UrDTF5nxtoVl/eXbHxWqFjdt0a211sa1mp3Gn3ZNq
m/teImYHhQKBgQCzWUA1MPPzi+pU2kEEhugla8xauha9cUiRhiAJw1uiKTlVDqSc
2ewKo9MaeYqzjruSGI26sVqxGDxGf7tQKoBuFiiFOhMxj+fxuHrhEHiI8FE9VgOj
F2U3sTAgAn1lX/VO21jM9BsUp++rY7dbrulwUDiFn8ZNazDeYeN8eoK4iwKBgQCb
cqJN+YW9NyCBSqdPnwTMvSE+YES7xFAKkjfzFiu8bBJtXe5KJHm4PRJXhc4q9/5A
Rtq8YR0WgNJLApArrnDqAa1Vajbp3RFSAKz1/X0Q5MurFanxqxsyvFvwoTkRZxFa
1rxstB96Prv12TrVCFx+ibI8lDJcnZNeV0s0wQn6eQKBgQDXkfPuX5TFBpNe1bWI
KUFmw9R1ynmUlIOaU3ITLv9C+w8zaJSpxFDZgJdv3uT8PfnXrsHm+lWjaOunvjri
quZSc06mLlEbggYoIFQNPeNPRyN0+GLvefMS3mCotzanZTmD5GrH9XG451tVPiH9
G/lpNA1ccRCCsLslcG/aaa5PQw==
-----END PRIVATE KEY-----
`
)
type clientCertificate struct{}
// Execute executes a test case and returns an error if occurred
func (h *clientCertificate) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if len(r.TLS.PeerCertificates) == 0 {
http.Error(w, "Client certificate required", http.StatusForbidden)
return
}
_, _ = fmt.Fprintf(w, "Hello, %s!\n", r.TLS.PeerCertificates[0].Subject)
})
_ = os.WriteFile("server.crt", []byte(serverCRT), permissionutil.ConfigFilePermission)
_ = os.WriteFile("server.key", []byte(serverKey), permissionutil.ConfigFilePermission)
defer func() {
_ = os.Remove("server.crt")
_ = os.Remove("server.key")
}()
serverCert, _ := tls.LoadX509KeyPair("server.crt", "server.key")
certPool := x509.NewCertPool()
caCert, _ := os.ReadFile("server.crt")
certPool.AppendCertsFromPEM(caCert)
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{serverCert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: certPool,
}
ts := httptest.NewUnstartedServer(router)
ts.TLS = tlsConfig
ts.StartTLS()
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug,
"-ca", "generic/auth/certificate/assets/server.crt",
"-cc", "generic/auth/certificate/assets/client.crt",
"-ck", "generic/auth/certificate/assets/client.key")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
================================================
FILE: cmd/integration-test/headless.go
================================================
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"github.com/julienschmidt/httprouter"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
)
var headlessTestcases = []TestCaseInfo{
{Path: "protocols/headless/headless-basic.yaml", TestCase: &headlessBasic{}},
{Path: "protocols/headless/headless-waitevent.yaml", TestCase: &headlessBasic{}},
{Path: "protocols/headless/headless-dsl.yaml", TestCase: &headlessBasic{}},
{Path: "protocols/headless/headless-self-contained.yaml", TestCase: &headlessSelfContained{}},
{Path: "protocols/headless/headless-header-action.yaml", TestCase: &headlessHeaderActions{}},
{Path: "protocols/headless/headless-extract-values.yaml", TestCase: &headlessExtractValues{}},
{Path: "protocols/headless/headless-payloads.yaml", TestCase: &headlessPayloads{}},
{Path: "protocols/headless/variables.yaml", TestCase: &headlessVariables{}},
{Path: "protocols/headless/headless-local.yaml", TestCase: &headlessLocal{}},
{Path: "protocols/headless/file-upload.yaml", TestCase: &headlessFileUpload{}},
{Path: "protocols/headless/file-upload-negative.yaml", TestCase: &headlessFileUploadNegative{}},
{Path: "protocols/headless/headless-header-status-test.yaml", TestCase: &headlessHeaderStatus{}},
}
type headlessBasic struct{}
// Execute executes a test case and returns an error if occurred
func (h *headlessBasic) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "%s", r.URL.Query().Get("_"))
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-headless")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type headlessSelfContained struct{}
// Execute executes a test case and returns an error if occurred
func (h *headlessSelfContained) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "", debug, "-headless", "-var query=selfcontained")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type headlessLocal struct{}
// Execute executes a test case and returns an error if occurred
// in this testcases local network access is disabled
func (h *headlessLocal) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = w.Write([]byte(""))
})
ts := httptest.NewServer(router)
defer ts.Close()
args := []string{"-t", filePath, "-u", ts.URL, "-headless", "-lna"}
results, err := testutils.RunNucleiWithArgsAndGetResults(debug, args...)
if err != nil {
return err
}
return expectResultsCount(results, 0)
}
type headlessHeaderActions struct{}
// Execute executes a test case and returns an error if occurred
func (h *headlessHeaderActions) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
testValue := r.Header.Get("test")
if r.Header.Get("test") != "" {
_, _ = w.Write([]byte("" + testValue + ""))
}
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-headless")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type headlessExtractValues struct{}
// Execute executes a test case and returns an error if occurred
func (h *headlessExtractValues) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = w.Write([]byte("test"))
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-headless")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type headlessPayloads struct{}
// Execute executes a test case and returns an error if occurred
func (h *headlessPayloads) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = w.Write([]byte("test"))
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-headless")
if err != nil {
return err
}
return expectResultsCount(results, 4)
}
type headlessVariables struct{}
// Execute executes a test case and returns an error if occurred
func (h *headlessVariables) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = w.Write([]byte("aGVsbG8="))
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-headless")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type headlessFileUpload struct{}
// Execute executes a test case and returns an error if occurred
func (h *headlessFileUpload) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = w.Write([]byte(`
`))
})
router.POST("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
file, _, err := r.FormFile("file")
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
defer func() {
_ = file.Close()
}()
content, err := io.ReadAll(file)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
_, _ = w.Write(content)
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-headless")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type headlessHeaderStatus struct{}
// Execute executes a test case and returns an error if occurred
func (h *headlessHeaderStatus) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "https://scanme.sh", debug, "-headless")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type headlessFileUploadNegative struct{}
// Execute executes a test case and returns an error if occurred
func (h *headlessFileUploadNegative) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = w.Write([]byte(`
`))
})
router.POST("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
file, _, err := r.FormFile("file")
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
defer func() {
_ = file.Close()
}()
content, err := io.ReadAll(file)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
_, _ = w.Write(content)
})
ts := httptest.NewServer(router)
defer ts.Close()
args := []string{"-t", filePath, "-u", ts.URL, "-headless"}
results, err := testutils.RunNucleiWithArgsAndGetResults(debug, args...)
if err != nil {
return err
}
return expectResultsCount(results, 0)
}
================================================
FILE: cmd/integration-test/http.go
================================================
package main
import (
"errors"
"fmt"
"net/http"
"net/http/httptest"
"net/http/httputil"
"os"
"reflect"
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/julienschmidt/httprouter"
"gopkg.in/yaml.v2"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
"github.com/projectdiscovery/retryablehttp-go"
"github.com/projectdiscovery/utils/errkit"
logutil "github.com/projectdiscovery/utils/log"
sliceutil "github.com/projectdiscovery/utils/slice"
stringsutil "github.com/projectdiscovery/utils/strings"
unitutils "github.com/projectdiscovery/utils/unit"
)
var httpTestcases = []TestCaseInfo{
// TODO: excluded due to parsing errors with console
// "http/raw-unsafe-request.yaml": &httpRawUnsafeRequest{},
{Path: "protocols/http/get-headers.yaml", TestCase: &httpGetHeaders{}},
{Path: "protocols/http/get-query-string.yaml", TestCase: &httpGetQueryString{}},
{Path: "protocols/http/get-redirects.yaml", TestCase: &httpGetRedirects{}},
{Path: "protocols/http/get-host-redirects.yaml", TestCase: &httpGetHostRedirects{}},
{Path: "protocols/http/disable-redirects.yaml", TestCase: &httpDisableRedirects{}},
{Path: "protocols/http/get.yaml", TestCase: &httpGet{}},
{Path: "protocols/http/post-body.yaml", TestCase: &httpPostBody{}},
{Path: "protocols/http/post-json-body.yaml", TestCase: &httpPostJSONBody{}},
{Path: "protocols/http/post-multipart-body.yaml", TestCase: &httpPostMultipartBody{}},
{Path: "protocols/http/raw-cookie-reuse.yaml", TestCase: &httpRawCookieReuse{}},
{Path: "protocols/http/raw-dynamic-extractor.yaml", TestCase: &httpRawDynamicExtractor{}},
{Path: "protocols/http/raw-get-query.yaml", TestCase: &httpRawGetQuery{}},
{Path: "protocols/http/raw-get.yaml", TestCase: &httpRawGet{}},
{Path: "protocols/http/raw-with-params.yaml", TestCase: &httpRawWithParams{}},
{Path: "protocols/http/raw-unsafe-with-params.yaml", TestCase: &httpRawWithParams{}}, // Not a typo, functionality is same as above
{Path: "protocols/http/raw-path-trailing-slash.yaml", TestCase: &httpRawPathTrailingSlash{}},
{Path: "protocols/http/raw-payload.yaml", TestCase: &httpRawPayload{}},
{Path: "protocols/http/raw-post-body.yaml", TestCase: &httpRawPostBody{}},
{Path: "protocols/http/raw-unsafe-path.yaml", TestCase: &httpRawUnsafePath{}},
{Path: "protocols/http/http-paths.yaml", TestCase: &httpPaths{}},
{Path: "protocols/http/request-condition.yaml", TestCase: &httpRequestCondition{}},
{Path: "protocols/http/request-condition-new.yaml", TestCase: &httpRequestCondition{}},
{Path: "protocols/http/self-contained.yaml", TestCase: &httpRequestSelfContained{}},
{Path: "protocols/http/self-contained-with-path.yaml", TestCase: &httpRequestSelfContained{}}, // Not a typo, functionality is same as above
{Path: "protocols/http/self-contained-with-params.yaml", TestCase: &httpRequestSelfContainedWithParams{}},
{Path: "protocols/http/self-contained-file-input.yaml", TestCase: &httpRequestSelfContainedFileInput{}},
{Path: "protocols/http/get-case-insensitive.yaml", TestCase: &httpGetCaseInsensitive{}},
{Path: "protocols/http/get.yaml,protocols/http/get-case-insensitive.yaml", TestCase: &httpGetCaseInsensitiveCluster{}},
{Path: "protocols/http/get-redirects-chain-headers.yaml", TestCase: &httpGetRedirectsChainHeaders{}},
{Path: "protocols/http/dsl-matcher-variable.yaml", TestCase: &httpDSLVariable{}},
{Path: "protocols/http/dsl-functions.yaml", TestCase: &httpDSLFunctions{}},
{Path: "protocols/http/race-simple.yaml", TestCase: &httpRaceSimple{}},
{Path: "protocols/http/race-multiple.yaml", TestCase: &httpRaceMultiple{}},
{Path: "protocols/http/race-condition-with-delay.yaml", TestCase: &httpRaceWithDelay{}},
{Path: "protocols/http/race-with-variables.yaml", TestCase: &httpRaceWithVariables{}},
{Path: "protocols/http/stop-at-first-match.yaml", TestCase: &httpStopAtFirstMatch{}},
{Path: "protocols/http/stop-at-first-match-with-extractors.yaml", TestCase: &httpStopAtFirstMatchWithExtractors{}},
{Path: "protocols/http/variables.yaml", TestCase: &httpVariables{}},
{Path: "protocols/http/variables-threads-previous.yaml", TestCase: &httpVariablesThreadsPrevious{}},
{Path: "protocols/http/variable-dsl-function.yaml", TestCase: &httpVariableDSLFunction{}},
{Path: "protocols/http/get-override-sni.yaml", TestCase: &httpSniAnnotation{}},
{Path: "protocols/http/get-sni.yaml", TestCase: &customCLISNI{}},
{Path: "protocols/http/redirect-match-url.yaml", TestCase: &httpRedirectMatchURL{}},
{Path: "protocols/http/get-sni-unsafe.yaml", TestCase: &customCLISNIUnsafe{}},
{Path: "protocols/http/annotation-timeout.yaml", TestCase: &annotationTimeout{}},
{Path: "protocols/http/custom-attack-type.yaml", TestCase: &customAttackType{}},
{Path: "protocols/http/get-all-ips.yaml", TestCase: &scanAllIPS{}},
{Path: "protocols/http/get-without-scheme.yaml", TestCase: &httpGetWithoutScheme{}},
{Path: "protocols/http/cl-body-without-header.yaml", TestCase: &httpCLBodyWithoutHeader{}},
{Path: "protocols/http/cl-body-with-header.yaml", TestCase: &httpCLBodyWithHeader{}},
{Path: "protocols/http/cli-with-constants.yaml", TestCase: &ConstantWithCliVar{}},
{Path: "protocols/http/constants-with-threads.yaml", TestCase: &constantsWithThreads{}},
{Path: "protocols/http/matcher-status.yaml", TestCase: &matcherStatusTest{}},
{Path: "protocols/http/disable-path-automerge.yaml", TestCase: &httpDisablePathAutomerge{}},
{Path: "protocols/http/http-preprocessor.yaml", TestCase: &httpPreprocessor{}},
{Path: "protocols/http/multi-request.yaml", TestCase: &httpMultiRequest{}},
{Path: "protocols/http/http-matcher-extractor-dy-extractor.yaml", TestCase: &httpMatcherExtractorDynamicExtractor{}},
{Path: "protocols/http/multi-http-var-sharing.yaml", TestCase: &httpMultiVarSharing{}},
{Path: "protocols/http/response-data-literal-reuse.yaml", TestCase: &httpResponseDataLiteralReuse{}},
{Path: "protocols/http/raw-path-single-slash.yaml", TestCase: &httpRawPathSingleSlash{}},
{Path: "protocols/http/raw-unsafe-path-single-slash.yaml", TestCase: &httpRawUnsafePathSingleSlash{}},
}
type httpMultiVarSharing struct{}
func (h *httpMultiVarSharing) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "https://scanme.sh", debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpResponseDataLiteralReuse struct{}
func (h *httpResponseDataLiteralReuse) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprint(w, `{{md5("Hello")}}`)
})
router.GET("/echo", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if r.URL.Query().Get("x") != `{{md5("Hello")}}` {
w.WriteHeader(http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusOK)
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpMatcherExtractorDynamicExtractor struct{}
func (h *httpMatcherExtractorDynamicExtractor) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
html := `
Domains
`
_, _ = fmt.Fprint(w, html)
})
router.GET("/domains", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
html := `
Dynamic Extractor Test
`
_, _ = fmt.Fprint(w, html)
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpInteractshRequest struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpInteractshRequest) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
value := r.Header.Get("url")
if value != "" {
if resp, _ := retryablehttp.DefaultClient().Get(value); resp != nil {
_ = resp.Body.Close()
}
}
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1, 2)
}
type httpInteractshWithPayloadsRequest struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpInteractshWithPayloadsRequest) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
value := r.Header.Get("url")
if value != "" {
if resp, _ := retryablehttp.DefaultClient().Get(value); resp != nil {
_ = resp.Body.Close()
}
}
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1, 3)
}
type httpDefaultMatcherCondition struct{}
// Execute executes a test case and returns an error if occurred
func (d *httpDefaultMatcherCondition) Execute(filePath string) error {
// to simulate matcher-condition `or`
// - template should be run twice and vulnerable server should send response that fits for that specific run
router := httprouter.New()
var routerErr error
// Server endpoint where only interactsh matcher is successful and status code is not 200
router.GET("/interactsh/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
value := r.URL.Query().Get("url")
if value != "" {
if _, err := retryablehttp.DefaultClient().Get("https://" + value); err != nil {
routerErr = err
}
}
w.WriteHeader(http.StatusNotFound)
})
// Server endpoint where url is not probed but sends a 200 status code
router.GET("/status/", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
w.WriteHeader(http.StatusOK)
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"/status", debug)
if err != nil {
return err
}
if err := expectResultsCount(results, 1); err != nil {
return err
}
results, err = testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"/interactsh", debug)
if err != nil {
return err
}
if routerErr != nil {
return errkit.Wrap(routerErr, "failed to send http request to interactsh server")
}
if err := expectResultsCount(results, 1); err != nil {
return err
}
return nil
}
type httpInteractshStopAtFirstMatchRequest struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpInteractshStopAtFirstMatchRequest) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
value := r.Header.Get("url")
if value != "" {
if resp, _ := retryablehttp.DefaultClient().Get(value); resp != nil {
_ = resp.Body.Close()
}
}
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
// polling is asynchronous, so the interactions may be retrieved after the first request
return expectResultsCount(results, 1)
}
type httpGetHeaders struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpGetHeaders) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if strings.EqualFold(r.Header.Get("test"), "nuclei") {
_, _ = fmt.Fprintf(w, "This is test headers matcher text")
}
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpGetQueryString struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpGetQueryString) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if strings.EqualFold(r.URL.Query().Get("test"), "nuclei") {
_, _ = fmt.Fprintf(w, "This is test querystring matcher text")
}
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpGetRedirects struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpGetRedirects) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
http.Redirect(w, r, "/redirected", http.StatusFound)
})
router.GET("/redirected", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "This is test redirects matcher text")
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpGetHostRedirects struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpGetHostRedirects) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
http.Redirect(w, r, "/redirected1", http.StatusFound)
})
router.GET("/redirected1", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
http.Redirect(w, r, "redirected2", http.StatusFound)
})
router.GET("/redirected2", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
http.Redirect(w, r, "/redirected3", http.StatusFound)
})
router.GET("/redirected3", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
http.Redirect(w, r, "https://scanme.sh", http.StatusTemporaryRedirect)
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpDisableRedirects struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpDisableRedirects) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
http.Redirect(w, r, "/redirected", http.StatusMovedPermanently)
})
router.GET("/redirected", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "This is test redirects matcher text")
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-dr")
if err != nil {
return err
}
return expectResultsCount(results, 0)
}
type httpGet struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpGet) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "This is test matcher text")
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpDSLVariable struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpDSLVariable) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "This is test matcher text")
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 5)
}
type httpDSLFunctions struct{}
func (h *httpDSLFunctions) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
request, err := httputil.DumpRequest(r, true)
if err != nil {
_, _ = fmt.Fprint(w, err.Error())
} else {
_, _ = fmt.Fprint(w, string(request))
}
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-nc")
if err != nil {
return err
}
if err := expectResultsCount(results, 1); err != nil {
return err
}
// get result part
resultPart, err := stringsutil.After(results[0], ts.URL)
if err != nil {
return err
}
// remove additional characters till the first valid result and ignore last ] which doesn't alter the total count
resultPart = stringsutil.TrimPrefixAny(resultPart, "/", " ", "[")
extracted := strings.Split(resultPart, ",")
numberOfDslFunctions := 88
if len(extracted) != numberOfDslFunctions {
return errors.New("incorrect number of results")
}
for _, header := range extracted {
header = strings.Trim(header, `"`)
parts := strings.Split(header, ": ")
index, err := strconv.Atoi(parts[0])
if err != nil {
return err
}
if index < 0 || index > numberOfDslFunctions {
return fmt.Errorf("incorrect header index found: %d", index)
}
if strings.TrimSpace(parts[1]) == "" {
return fmt.Errorf("the DSL expression with index %d was not evaluated correctly", index)
}
}
return nil
}
type httpPostBody struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpPostBody) Execute(filePath string) error {
router := httprouter.New()
var routerErr error
router.POST("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if err := r.ParseForm(); err != nil {
routerErr = err
return
}
if strings.EqualFold(r.Form.Get("username"), "test") && strings.EqualFold(r.Form.Get("password"), "nuclei") {
_, _ = fmt.Fprintf(w, "This is test post-body matcher text")
}
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
if routerErr != nil {
return routerErr
}
return expectResultsCount(results, 1)
}
type httpPostJSONBody struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpPostJSONBody) Execute(filePath string) error {
router := httprouter.New()
var routerErr error
router.POST("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
type doc struct {
Username string `json:"username"`
Password string `json:"password"`
}
obj := &doc{}
if err := json.NewDecoder(r.Body).Decode(obj); err != nil {
routerErr = err
return
}
if strings.EqualFold(obj.Username, "test") && strings.EqualFold(obj.Password, "nuclei") {
_, _ = fmt.Fprintf(w, "This is test post-json-body matcher text")
}
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
if routerErr != nil {
return routerErr
}
return expectResultsCount(results, 1)
}
type httpPostMultipartBody struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpPostMultipartBody) Execute(filePath string) error {
router := httprouter.New()
var routerErr error
router.POST("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if err := r.ParseMultipartForm(unitutils.Mega); err != nil {
routerErr = err
return
}
password, ok := r.MultipartForm.Value["password"]
if !ok || len(password) != 1 {
routerErr = errors.New("no password in request")
return
}
file := r.MultipartForm.File["username"]
if len(file) != 1 {
routerErr = errors.New("no file in request")
return
}
if strings.EqualFold(password[0], "nuclei") && strings.EqualFold(file[0].Filename, "username") {
_, _ = fmt.Fprintf(w, "This is test post-multipart matcher text")
}
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
if routerErr != nil {
return routerErr
}
return expectResultsCount(results, 1)
}
type httpRawDynamicExtractor struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpRawDynamicExtractor) Execute(filePath string) error {
router := httprouter.New()
var routerErr error
router.POST("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if err := r.ParseForm(); err != nil {
routerErr = err
return
}
if strings.EqualFold(r.Form.Get("testing"), "parameter") {
_, _ = fmt.Fprintf(w, "Token: 'nuclei'")
}
})
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if strings.EqualFold(r.URL.Query().Get("username"), "nuclei") {
_, _ = fmt.Fprintf(w, "Test is test-dynamic-extractor-raw matcher text")
}
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
if routerErr != nil {
return routerErr
}
return expectResultsCount(results, 1)
}
type httpRawGetQuery struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpRawGetQuery) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if strings.EqualFold(r.URL.Query().Get("test"), "nuclei") {
_, _ = fmt.Fprintf(w, "Test is test raw-get-query-matcher text")
}
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpRawGet struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpRawGet) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "Test is test raw-get-matcher text")
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpRawWithParams struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpRawWithParams) Execute(filePath string) error {
router := httprouter.New()
var errx error
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
params := r.URL.Query()
// we intentionally use params["test"] instead of params.Get("test") to test the case where
// there are multiple parameters with the same name
if !reflect.DeepEqual(params["key1"], []string{"value1"}) {
errx = errkit.Append(errx, errkit.New("key1 not found in params", "expected", []string{"value1"}, "got", params["key1"]))
}
if !reflect.DeepEqual(params["key2"], []string{"value2"}) {
errx = errkit.Append(errx, errkit.New("key2 not found in params", "expected", []string{"value2"}, "got", params["key2"]))
}
_, _ = fmt.Fprintf(w, "Test is test raw-params-matcher text")
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"/?key1=value1", debug)
if err != nil {
return err
}
if errx != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpRawPathTrailingSlash struct{}
func (h *httpRawPathTrailingSlash) Execute(filepath string) error {
router := httprouter.New()
var routerErr error
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if r.RequestURI != "/test/..;/..;/" {
routerErr = fmt.Errorf("expected path /test/..;/..;/ but got %v", r.RequestURI)
return
}
})
ts := httptest.NewServer(router)
defer ts.Close()
_, err := testutils.RunNucleiTemplateAndGetResults(filepath, ts.URL, debug)
if err != nil {
return err
}
if routerErr != nil {
return routerErr
}
return nil
}
type httpRawPayload struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpRawPayload) Execute(filePath string) error {
router := httprouter.New()
var routerErr error
router.POST("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if err := r.ParseForm(); err != nil {
routerErr = err
return
}
if !strings.EqualFold(r.Header.Get("another_header"), "bnVjbGVp") && !strings.EqualFold(r.Header.Get("another_header"), "Z3Vlc3Q=") {
return
}
if strings.EqualFold(r.Form.Get("username"), "test") && (strings.EqualFold(r.Form.Get("password"), "nuclei") || strings.EqualFold(r.Form.Get("password"), "guest")) {
_, _ = fmt.Fprintf(w, "Test is raw-payload matcher text")
}
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
if routerErr != nil {
return routerErr
}
return expectResultsCount(results, 2)
}
type httpRawPostBody struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpRawPostBody) Execute(filePath string) error {
router := httprouter.New()
var routerErr error
router.POST("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if err := r.ParseForm(); err != nil {
routerErr = err
return
}
if strings.EqualFold(r.Form.Get("username"), "test") && strings.EqualFold(r.Form.Get("password"), "nuclei") {
_, _ = fmt.Fprintf(w, "Test is test raw-post-body-matcher text")
}
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
if routerErr != nil {
return routerErr
}
return expectResultsCount(results, 1)
}
type httpRawUnsafePath struct{}
func (h *httpRawUnsafePath) Execute(filepath string) error {
// testing unsafe paths using router feedback is not possible cause they are `unsafe urls`
// hence it is done by parsing and matching paths from nuclei output with `-debug-req` flag
// read template files
bin, err := os.ReadFile(filepath)
if err != nil {
return err
}
// Instead of storing expected `paths` in code it is stored in
// `reference` section of template
type template struct {
Info struct {
Reference []string `yaml:"reference"`
}
}
var tpl template
if err = yaml.Unmarshal(bin, &tpl); err != nil {
return err
}
// expected relative paths
expected := []string{}
expected = append(expected, tpl.Info.Reference...)
if len(expected) == 0 {
return fmt.Errorf("something went wrong with %v template", filepath)
}
results, err := testutils.RunNucleiBinaryAndGetCombinedOutput(debug, []string{"-t", filepath, "-u", "scanme.sh", "-debug-req"})
if err != nil {
return err
}
actual := []string{}
for _, v := range strings.Split(results, "\n") {
if strings.Contains(v, "GET") {
parts := strings.Fields(v)
if len(parts) == 3 {
actual = append(actual, parts[1])
}
}
}
if !reflect.DeepEqual(expected, actual) {
return fmt.Errorf("%8v: %v\n%-8v: %v", "expected", expected, "actual", actual)
}
return nil
}
type httpPaths struct{}
func (h *httpPaths) Execute(filepath string) error {
// covers testcases similar to httpRawUnsafePath but when `unsafe:false`
bin, err := os.ReadFile(filepath)
if err != nil {
return err
}
// Instead of storing expected `paths` in code it is stored in
// `reference` section of template
type template struct {
Info struct {
Reference []string `yaml:"reference"`
}
}
var tpl template
if err = yaml.Unmarshal(bin, &tpl); err != nil {
return err
}
// expected relative paths
expected := []string{}
expected = append(expected, tpl.Info.Reference...)
if len(expected) == 0 {
return fmt.Errorf("something went wrong with %v template", filepath)
}
results, err := testutils.RunNucleiBinaryAndGetCombinedOutput(debug, []string{"-t", filepath, "-u", "scanme.sh", "-debug-req"})
if err != nil {
return err
}
actual := []string{}
for _, v := range strings.Split(results, "\n") {
if strings.Contains(v, "GET") {
parts := strings.Fields(v)
if len(parts) == 3 {
actual = append(actual, parts[1])
}
}
}
if len(expected) > len(actual) {
actualValuesIndex := max(len(actual)-1, 0)
return fmt.Errorf("missing values : %v", expected[actualValuesIndex:])
} else if len(expected) < len(actual) {
return fmt.Errorf("unexpected values : %v", actual[len(expected)-1:])
} else {
if !reflect.DeepEqual(expected, actual) {
return fmt.Errorf("expected: %v\n\nactual: %v", expected, actual)
}
}
return nil
}
type httpRawCookieReuse struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpRawCookieReuse) Execute(filePath string) error {
router := httprouter.New()
var routerErr error
router.POST("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if err := r.ParseForm(); err != nil {
routerErr = err
return
}
if strings.EqualFold(r.Form.Get("testing"), "parameter") {
http.SetCookie(w, &http.Cookie{Name: "nuclei", Value: "test"})
}
})
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if err := r.ParseForm(); err != nil {
routerErr = err
return
}
cookie, err := r.Cookie("nuclei")
if err != nil {
routerErr = err
return
}
if strings.EqualFold(cookie.Value, "test") {
_, _ = fmt.Fprintf(w, "Test is test-cookie-reuse matcher text")
}
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
if routerErr != nil {
return routerErr
}
return expectResultsCount(results, 1)
}
// TODO: excluded due to parsing errors with console
// type httpRawUnsafeRequest struct{
// Execute executes a test case and returns an error if occurred
// func (h *httpRawUnsafeRequest) Execute(filePath string) error {
// var routerErr error
//
// ts := testutils.NewTCPServer(nil, defaultStaticPort, func(conn net.Conn) {
// defer conn.Close()
// _, _ = conn.Write([]byte("protocols/http/1.1 200 OK\r\nContent-Length: 36\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nThis is test raw-unsafe-matcher test"))
// })
// defer ts.Close()
//
// results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "http://"+ts.URL, debug)
// if err != nil {
// return err
// }
// if routerErr != nil {
// return routerErr
// }
//
// return expectResultsCount(results, 1)
// }
type httpRequestCondition struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpRequestCondition) Execute(filePath string) error {
router := httprouter.New()
router.GET("/200", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.WriteHeader(http.StatusOK)
})
router.GET("/400", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.WriteHeader(http.StatusBadRequest)
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpRequestSelfContained struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpRequestSelfContained) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = w.Write([]byte("This is self-contained response"))
})
server := &http.Server{
Addr: fmt.Sprintf("localhost:%d", defaultStaticPort),
Handler: router,
}
go func() {
_ = server.ListenAndServe()
}()
defer func() {
_ = server.Close()
}()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "", debug, "-esc")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
// testcase to check duplicated values in params
type httpRequestSelfContainedWithParams struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpRequestSelfContainedWithParams) Execute(filePath string) error {
router := httprouter.New()
var errx error
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
params := r.URL.Query()
// we intentionally use params["test"] instead of params.Get("test") to test the case where
// there are multiple parameters with the same name
if !reflect.DeepEqual(params["something"], []string{"here"}) {
errx = errkit.Append(errx, errkit.New("something not found in params", "expected", []string{"here"}, "got", params["something"]))
}
if !reflect.DeepEqual(params["key"], []string{"value"}) {
errx = errkit.Append(errx, errkit.New("key not found in params", "expected", []string{"value"}, "got", params["key"]))
}
_, _ = w.Write([]byte("This is self-contained response"))
})
server := &http.Server{
Addr: fmt.Sprintf("localhost:%d", defaultStaticPort),
Handler: router,
}
go func() {
_ = server.ListenAndServe()
}()
defer func() {
_ = server.Close()
}()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "", debug, "-esc")
if err != nil {
return err
}
if errx != nil {
return errx
}
return expectResultsCount(results, 1)
}
type httpRequestSelfContainedFileInput struct{}
func (h *httpRequestSelfContainedFileInput) Execute(filePath string) error {
router := httprouter.New()
gotReqToEndpoints := []string{}
router.GET("/one", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
gotReqToEndpoints = append(gotReqToEndpoints, "/one")
_, _ = w.Write([]byte("This is self-contained response"))
})
router.GET("/two", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
gotReqToEndpoints = append(gotReqToEndpoints, "/two")
_, _ = w.Write([]byte("This is self-contained response"))
})
server := &http.Server{
Addr: fmt.Sprintf("localhost:%d", defaultStaticPort),
Handler: router,
}
go func() {
_ = server.ListenAndServe()
}()
defer func() {
_ = server.Close()
}()
// create temp file
FileLoc, err := os.CreateTemp("", "self-contained-payload-*.txt")
if err != nil {
return errkit.Wrap(err, "failed to create temp file")
}
if _, err := FileLoc.Write([]byte("one\ntwo\n")); err != nil {
return errkit.Wrap(err, "failed to write payload to temp file")
}
defer func() {
_ = FileLoc.Close()
}()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "", debug, "-V", "test="+FileLoc.Name(), "-esc")
if err != nil {
return err
}
if err := expectResultsCount(results, 4); err != nil {
return err
}
if !sliceutil.ElementsMatch(gotReqToEndpoints, []string{"/one", "/two", "/one", "/two"}) {
return errkit.New("expected requests to be sent to `/one` and `/two` endpoints but were sent to `%v`", gotReqToEndpoints, "filePath", filePath)
}
return nil
}
type httpGetCaseInsensitive struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpGetCaseInsensitive) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "THIS IS TEST MATCHER TEXT")
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpGetCaseInsensitiveCluster struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpGetCaseInsensitiveCluster) Execute(filesPath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "This is test matcher text")
})
ts := httptest.NewServer(router)
defer ts.Close()
files := strings.Split(filesPath, ",")
results, err := testutils.RunNucleiTemplateAndGetResults(files[0], ts.URL, debug, "-t", files[1])
if err != nil {
return err
}
return expectResultsCount(results, 2)
}
type httpGetRedirectsChainHeaders struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpGetRedirectsChainHeaders) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
http.Redirect(w, r, "/redirected", http.StatusFound)
})
router.GET("/redirected", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.Header().Set("Secret", "TestRedirectHeaderMatch")
http.Redirect(w, r, "/final", http.StatusFound)
})
router.GET("/final", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = w.Write([]byte("ok"))
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpRaceSimple struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpRaceSimple) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.WriteHeader(http.StatusOK)
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 10)
}
type httpRaceMultiple struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpRaceMultiple) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.WriteHeader(http.StatusOK)
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 5)
}
type httpRaceWithDelay struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpRaceWithDelay) Execute(filePath string) error {
var requestTimes []time.Time
var mu sync.Mutex
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
mu.Lock()
requestTimes = append(requestTimes, time.Now())
mu.Unlock()
time.Sleep(2 * time.Second)
w.WriteHeader(http.StatusOK)
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
if err := expectResultsCount(results, 3); err != nil {
return err
}
mu.Lock()
defer mu.Unlock()
if len(requestTimes) != 3 {
return fmt.Errorf("expected 3 requests, got %d", len(requestTimes))
}
// Check concurrency of first two requests (should be very close)
if diff := requestTimes[1].Sub(requestTimes[0]); diff > 500*time.Millisecond {
return fmt.Errorf("expected first 2 requests to be concurrent, diff: %v", diff)
}
// Check delay of third request (should be after ~2s)
if diff := requestTimes[2].Sub(requestTimes[0]); diff < 1500*time.Millisecond {
return fmt.Errorf("expected 3rd request to be delayed, diff: %v", diff)
}
return nil
}
type httpRaceWithVariables struct{}
// Execute tests that variables and constants are properly resolved in race mode.
func (h *httpRaceWithVariables) Execute(filePath string) error {
router := httprouter.New()
router.GET("/race", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
// Echo back the API key header so we can match on it
_, _ = fmt.Fprint(w, r.Header.Get("X-API-Key"))
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 3)
}
type httpStopAtFirstMatch struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpStopAtFirstMatch) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "This is test")
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpStopAtFirstMatchWithExtractors struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpStopAtFirstMatchWithExtractors) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "This is test")
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 2)
}
type httpVariables struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpVariables) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "%s\n%s\n%s", r.Header.Get("Test"), r.Header.Get("Another"), r.Header.Get("Email"))
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
if err := expectResultsCount(results, 1); err != nil {
return err
}
// variable override that does not have any match
// to make sure the variable override is working
results, err = testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-var", "a1=failed")
if err != nil {
return err
}
return expectResultsCount(results, 0)
}
type httpVariablesThreadsPrevious struct{}
// Execute tests that variables can reference data extracted from previous requests
// when using threads mode (parallel execution).
func (h *httpVariablesThreadsPrevious) Execute(filePath string) error {
router := httprouter.New()
router.GET("/login", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprint(w, "token=secret123")
})
router.GET("/api", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
// Echo back the Authorization header so we can match on it
_, _ = fmt.Fprint(w, r.Header.Get("Authorization"))
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpVariableDSLFunction struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpVariableDSLFunction) Execute(filePath string) error {
results, err := testutils.RunNucleiBinaryAndGetCombinedOutput(debug, []string{"-t", filePath, "-u", "https://scanme.sh", "-debug-req"})
if err != nil {
return err
}
actual := []string{}
for _, v := range strings.Split(results, "\n") {
if strings.Contains(v, "GET") {
parts := strings.Fields(v)
if len(parts) == 3 {
actual = append(actual, parts[1])
}
}
}
if len(actual) == 2 && actual[0] == actual[1] {
return nil
}
return fmt.Errorf("expected 2 requests with same URL, got %v", actual)
}
type customCLISNI struct{}
// Execute executes a test case and returns an error if occurred
func (h *customCLISNI) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if r.TLS.ServerName == "test" {
_, _ = w.Write([]byte("test-ok"))
} else {
_, _ = w.Write([]byte("test-ko"))
}
})
ts := httptest.NewTLSServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-sni", "test")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpSniAnnotation struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpSniAnnotation) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if r.TLS.ServerName == "test" {
_, _ = w.Write([]byte("test-ok"))
} else {
_, _ = w.Write([]byte("test-ko"))
}
})
ts := httptest.NewTLSServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpRedirectMatchURL struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpRedirectMatchURL) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
http.Redirect(w, r, "/redirected", http.StatusFound)
_, _ = w.Write([]byte("This is test redirects matcher text"))
})
router.GET("/redirected", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "This is test redirects matcher text")
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-no-meta")
if err != nil {
return err
}
if err := expectResultsCount(results, 1); err != nil {
return err
}
if results[0] != fmt.Sprintf("%s/redirected", ts.URL) {
return fmt.Errorf("mismatched url found: %s", results[0])
}
return nil
}
type customCLISNIUnsafe struct{}
// Execute executes a test case and returns an error if occurred
func (h *customCLISNIUnsafe) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if r.TLS.ServerName == "test" {
_, _ = w.Write([]byte("test-ok"))
} else {
_, _ = w.Write([]byte("test-ko"))
}
})
ts := httptest.NewTLSServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-sni", "test")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type annotationTimeout struct{}
// Execute executes a test case and returns an error if occurred
func (h *annotationTimeout) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
time.Sleep(4 * time.Second)
_, _ = fmt.Fprintf(w, "This is test matcher text")
})
ts := httptest.NewTLSServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-timeout", "1")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type customAttackType struct{}
// Execute executes a test case and returns an error if occurred
func (h *customAttackType) Execute(filePath string) error {
router := httprouter.New()
got := []string{}
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
got = append(got, r.URL.RawQuery)
_, _ = fmt.Fprintf(w, "This is test custom payload")
})
ts := httptest.NewTLSServer(router)
defer ts.Close()
_, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-attack-type", "clusterbomb")
if err != nil {
return err
}
return expectResultsCount(got, 4)
}
// Disabled as GH doesn't support ipv6
type scanAllIPS struct{}
// Execute executes a test case and returns an error if occurred
func (h *scanAllIPS) Execute(filePath string) error {
got, err := testutils.RunNucleiTemplateAndGetResults(filePath, "https://scanme.sh", debug, "-scan-all-ips", "-iv", "4")
if err != nil {
return err
}
// limiting test to ipv4 (GH doesn't support ipv6)
return expectResultsCount(got, 1)
}
// ensure that ip|host are handled without http|https scheme
type httpGetWithoutScheme struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpGetWithoutScheme) Execute(filePath string) error {
got, err := testutils.RunNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
if err != nil {
return err
}
return expectResultsCount(got, 1)
}
// content-length in case the response has no header but has a body
type httpCLBodyWithoutHeader struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpCLBodyWithoutHeader) Execute(filePath string) error {
logutil.DisableDefaultLogger()
defer logutil.EnableDefaultLogger()
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.Header()["Content-Length"] = []string{"-1"}
_, _ = fmt.Fprintf(w, "this is a test")
})
ts := httptest.NewTLSServer(router)
defer ts.Close()
got, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(got, 1)
}
// content-length in case the response has content-length header and a body
type httpCLBodyWithHeader struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpCLBodyWithHeader) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.Header()["Content-Length"] = []string{"50000"}
_, _ = fmt.Fprintf(w, "this is a test")
})
ts := httptest.NewTLSServer(router)
defer ts.Close()
got, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(got, 1)
}
// constant shouldn't be overwritten by cli var with same name
type ConstantWithCliVar struct{}
// Execute executes a test case and returns an error if occurred
func (h *ConstantWithCliVar) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprint(w, r.URL.Query().Get("p"))
})
ts := httptest.NewTLSServer(router)
defer ts.Close()
got, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-V", "test=fromcli")
if err != nil {
return err
}
return expectResultsCount(got, 1)
}
type constantsWithThreads struct{}
// Execute tests that constants are properly resolved when using threads mode.
func (h *constantsWithThreads) Execute(filePath string) error {
router := httprouter.New()
router.GET("/api/:version", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
// Echo back the API key header and version so we can match on them
_, _ = fmt.Fprintf(w, "%s %s", r.Header.Get("X-API-Key"), p.ByName("version"))
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type matcherStatusTest struct{}
// Execute executes a test case and returns an error if occurred
func (h *matcherStatusTest) Execute(filePath string) error {
router := httprouter.New()
router.GET("/200", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.WriteHeader(http.StatusOK)
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-ms")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
// disable path automerge in raw request
type httpDisablePathAutomerge struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpDisablePathAutomerge) Execute(filePath string) error {
router := httprouter.New()
router.GET("/api/v1/test", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprint(w, r.URL.Query().Get("id"))
})
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprint(w, "empty path in raw request")
})
ts := httptest.NewServer(router)
defer ts.Close()
got, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+"/api/v1/user", debug)
if err != nil {
return err
}
return expectResultsCount(got, 2)
}
type httpInteractshRequestsWithMCAnd struct{}
func (h *httpInteractshRequestsWithMCAnd) Execute(filePath string) error {
got, err := testutils.RunNucleiTemplateAndGetResults(filePath, "honey.scanme.sh", debug)
if err != nil {
return err
}
return expectResultsCount(got, 1)
}
// integration test to check if preprocessor i.e {{randstr}}
// is working correctly
type httpPreprocessor struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpPreprocessor) Execute(filePath string) error {
router := httprouter.New()
re := regexp.MustCompile(`[A-Za-z0-9]{25,}`)
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
value := r.URL.RequestURI()
if re.MatchString(value) {
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprint(w, "ok")
} else {
w.WriteHeader(http.StatusBadRequest)
_, _ = fmt.Fprint(w, "not ok")
}
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpMultiRequest struct{}
// Execute executes a test case and returns an error if occurred
func (h *httpMultiRequest) Execute(filePath string) error {
router := httprouter.New()
router.GET("/ping", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprint(w, "ping")
})
router.GET("/pong", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.WriteHeader(http.StatusOK)
_, _ = fmt.Fprint(w, "pong")
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type httpRawPathSingleSlash struct{}
func (h *httpRawPathSingleSlash) Execute(filepath string) error {
expectedPath := "/index.php"
results, err := testutils.RunNucleiBinaryAndGetCombinedOutput(debug, []string{"-t", filepath, "-u", "scanme.sh/index.php", "-debug-req"})
if err != nil {
return err
}
var actual string
for _, v := range strings.Split(results, "\n") {
if strings.Contains(v, "GET") {
parts := strings.Fields(v)
if len(parts) == 3 {
actual = parts[1]
}
}
}
if actual != expectedPath {
return fmt.Errorf("expected: %v\n\nactual: %v", expectedPath, actual)
}
return nil
}
type httpRawUnsafePathSingleSlash struct{}
func (h *httpRawUnsafePathSingleSlash) Execute(filepath string) error {
expectedPath := "/index.php"
results, err := testutils.RunNucleiBinaryAndGetCombinedOutput(debug, []string{"-t", filepath, "-u", "scanme.sh/index.php", "-debug-req"})
if err != nil {
return err
}
var actual string
for _, v := range strings.Split(results, "\n") {
if strings.Contains(v, "GET") {
parts := strings.Fields(v)
if len(parts) == 3 {
actual = parts[1]
}
}
}
if actual != expectedPath {
return fmt.Errorf("expected: %v\n\nactual: %v", expectedPath, actual)
}
return nil
}
================================================
FILE: cmd/integration-test/integration-test.go
================================================
package main
import (
"flag"
"fmt"
"os"
"regexp"
"runtime"
"slices"
"strings"
"github.com/kitabisa/go-ci"
"github.com/logrusorgru/aurora"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils/fuzzplayground"
sliceutil "github.com/projectdiscovery/utils/slice"
)
type TestCaseInfo struct {
Path string
TestCase testutils.TestCase
DisableOn func() bool
}
var (
debug = isDebugMode()
customTests = os.Getenv("TESTS")
protocol = os.Getenv("PROTO")
success = aurora.Green("[✓]").String()
failed = aurora.Red("[✘]").String()
protocolTests = map[string][]TestCaseInfo{
"http": httpTestcases,
"interactsh": interactshTestCases,
"network": networkTestcases,
"dns": dnsTestCases,
"workflow": workflowTestcases,
"loader": loaderTestcases,
"profile-loader": profileLoaderTestcases,
"websocket": websocketTestCases,
"headless": headlessTestcases,
"whois": whoisTestCases,
"ssl": sslTestcases,
"library": libraryTestcases,
"templatesPath": templatesPathTestCases,
"templatesDir": templatesDirTestCases,
"env_vars": templatesDirEnvTestCases,
"file": fileTestcases,
"offlineHttp": offlineHttpTestcases,
"customConfigDir": customConfigDirTestCases,
"fuzzing": fuzzingTestCases,
"code": codeTestCases,
"multi": multiProtoTestcases,
"generic": genericTestcases,
"dsl": dslTestcases,
"flow": flowTestcases,
"javascript": jsTestcases,
"matcher-status": matcherStatusTestcases,
"exporters": exportersTestCases,
}
// flakyTests are run with a retry count of 3
flakyTests = map[string]bool{
"protocols/http/self-contained-file-input.yaml": true,
}
// For debug purposes
runProtocol = ""
runTemplate = ""
extraArgs = []string{}
interactshRetryCount = 3
)
func main() {
flag.StringVar(&runProtocol, "protocol", "", "run integration tests of given protocol")
flag.StringVar(&runTemplate, "template", "", "run integration test of given template")
flag.Parse()
// allows passing extra args to nuclei
eargs := os.Getenv("DebugExtraArgs")
if eargs != "" {
extraArgs = strings.Split(eargs, " ")
testutils.ExtraDebugArgs = extraArgs
}
if runProtocol != "" {
debugTests()
os.Exit(1)
}
// start fuzz playground server
server := fuzzplayground.GetPlaygroundServer()
defer func() {
fuzzplayground.Cleanup()
_ = server.Close()
}()
go func() {
if err := server.Start("localhost:8082"); err != nil {
if !strings.Contains(err.Error(), "Server closed") {
gologger.Fatal().Msgf("Could not start server: %s\n", err)
}
}
}()
customTestsList := normalizeSplit(customTests)
failedTestTemplatePaths := runTests(customTestsList)
if len(failedTestTemplatePaths) > 0 {
if ci.IsCI() {
// run failed tests again assuming they are flaky
// if they fail as well only then we assume that there is an actual issue
fmt.Println("::group::Running failed tests again")
failedTestTemplatePaths = runTests(failedTestTemplatePaths)
fmt.Println("::endgroup::")
if len(failedTestTemplatePaths) > 0 {
debug = true
fmt.Println("::group::Failed integration tests in debug mode")
_ = runTests(failedTestTemplatePaths)
fmt.Println("::endgroup::")
} else {
fmt.Println("::group::All tests passed")
fmt.Println("::endgroup::")
os.Exit(0)
}
}
os.Exit(1)
}
}
// isDebugMode checks if debug mode is enabled via any of the supported debug
// environment variables.
func isDebugMode() bool {
debugEnvVars := []string{
"DEBUG",
"ACTIONS_RUNNER_DEBUG", // GitHub Actions runner debug
// Add more debug environment variables here as needed
}
truthyValues := []string{"true", "1", "yes", "on", "enabled"}
for _, envVar := range debugEnvVars {
envValue := strings.ToLower(strings.TrimSpace(os.Getenv(envVar)))
if slices.Contains(truthyValues, envValue) {
return true
}
}
return false
}
// execute a testcase with retry and consider best of N
// intended for flaky tests like interactsh
func executeWithRetry(testCase testutils.TestCase, templatePath string, retryCount int) (string, error) {
var err error
for i := 0; i < retryCount; i++ {
err = testCase.Execute(templatePath)
if err == nil {
fmt.Printf("%s Test \"%s\" passed!\n", success, templatePath)
return "", nil
}
}
_, _ = fmt.Fprintf(os.Stderr, "%s Test \"%s\" failed after %v attempts : %s\n", failed, templatePath, retryCount, err)
return templatePath, err
}
func debugTests() {
testCaseInfos := protocolTests[runProtocol]
for _, testCaseInfo := range testCaseInfos {
if (runTemplate != "" && !strings.Contains(testCaseInfo.Path, runTemplate)) ||
(testCaseInfo.DisableOn != nil && testCaseInfo.DisableOn()) {
continue
}
if runProtocol == "interactsh" {
if _, err := executeWithRetry(testCaseInfo.TestCase, testCaseInfo.Path, interactshRetryCount); err != nil {
fmt.Printf("\n%v", err.Error())
}
} else {
if _, err := execute(testCaseInfo.TestCase, testCaseInfo.Path); err != nil {
fmt.Printf("\n%v", err.Error())
}
}
}
}
func runTests(customTemplatePaths []string) []string {
var failedTestTemplatePaths []string
for proto, testCaseInfos := range protocolTests {
if protocol != "" {
if !strings.EqualFold(proto, protocol) {
continue
}
}
if len(customTemplatePaths) == 0 {
fmt.Printf("Running test cases for %q protocol\n", aurora.Blue(proto))
}
for _, testCaseInfo := range testCaseInfos {
if testCaseInfo.DisableOn != nil && testCaseInfo.DisableOn() {
fmt.Printf("skipping test case %v. disabled on %v.\n", aurora.Blue(testCaseInfo.Path), runtime.GOOS)
continue
}
if len(customTemplatePaths) == 0 || sliceutil.Contains(customTemplatePaths, testCaseInfo.Path) {
var failedTemplatePath string
var err error
if proto == "interactsh" || strings.Contains(testCaseInfo.Path, "interactsh") {
failedTemplatePath, err = executeWithRetry(testCaseInfo.TestCase, testCaseInfo.Path, interactshRetryCount)
} else if flakyTests[testCaseInfo.Path] {
failedTemplatePath, err = executeWithRetry(testCaseInfo.TestCase, testCaseInfo.Path, interactshRetryCount)
} else {
failedTemplatePath, err = execute(testCaseInfo.TestCase, testCaseInfo.Path)
}
if err != nil {
failedTestTemplatePaths = append(failedTestTemplatePaths, failedTemplatePath)
}
}
}
}
return failedTestTemplatePaths
}
func execute(testCase testutils.TestCase, templatePath string) (string, error) {
if err := testCase.Execute(templatePath); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%s Test \"%s\" failed: %s\n", failed, templatePath, err)
return templatePath, err
}
fmt.Printf("%s Test \"%s\" passed!\n", success, templatePath)
return "", nil
}
func expectResultsCount(results []string, expectedNumbers ...int) error {
results = filterLines(results)
match := sliceutil.Contains(expectedNumbers, len(results))
if !match {
return fmt.Errorf("incorrect number of results: %d (actual) vs %v (expected) \nResults:\n\t%s\n", len(results), expectedNumbers, strings.Join(results, "\n\t")) // nolint:all
}
return nil
}
func normalizeSplit(str string) []string {
return strings.FieldsFunc(str, func(r rune) bool {
return r == ','
})
}
// filterLines applies all filtering functions to the results
func filterLines(results []string) []string {
results = filterHeadlessLogs(results)
results = filterUnsignedTemplatesWarnings(results)
return results
}
// if chromium is not installed go-rod installs it in .cache directory
// this function filters out the logs from download and installation
func filterHeadlessLogs(results []string) []string {
// [launcher.Browser] 2021/09/23 15:24:05 [launcher] [info] Starting browser
filtered := []string{}
for _, result := range results {
if strings.Contains(result, "[launcher.Browser]") {
continue
}
filtered = append(filtered, result)
}
return filtered
}
// filterUnsignedTemplatesWarnings filters out warning messages about unsigned templates
func filterUnsignedTemplatesWarnings(results []string) []string {
filtered := []string{}
unsignedTemplatesRegex := regexp.MustCompile(`Loading \d+ unsigned templates for scan\. Use with caution\.`)
for _, result := range results {
if unsignedTemplatesRegex.MatchString(result) {
continue
}
filtered = append(filtered, result)
}
return filtered
}
================================================
FILE: cmd/integration-test/interactsh.go
================================================
package main
import osutils "github.com/projectdiscovery/utils/os"
// All Interactsh related testcases
var interactshTestCases = []TestCaseInfo{
{Path: "protocols/http/interactsh.yaml", TestCase: &httpInteractshRequest{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
{Path: "protocols/http/interactsh-with-payloads.yaml", TestCase: &httpInteractshWithPayloadsRequest{}, DisableOn: func() bool { return true }},
{Path: "protocols/http/interactsh-stop-at-first-match.yaml", TestCase: &httpInteractshStopAtFirstMatchRequest{}, DisableOn: func() bool { return true }}, // disable this test for now
{Path: "protocols/http/default-matcher-condition.yaml", TestCase: &httpDefaultMatcherCondition{}, DisableOn: func() bool { return true }},
{Path: "protocols/http/interactsh-requests-mc-and.yaml", TestCase: &httpInteractshRequestsWithMCAnd{}},
}
================================================
FILE: cmd/integration-test/javascript.go
================================================
package main
import (
"log"
"time"
"github.com/ory/dockertest/v3"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
osutils "github.com/projectdiscovery/utils/os"
"go.uber.org/multierr"
)
var jsTestcases = []TestCaseInfo{
{Path: "protocols/javascript/redis-pass-brute.yaml", TestCase: &javascriptRedisPassBrute{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
{Path: "protocols/javascript/ssh-server-fingerprint.yaml", TestCase: &javascriptSSHServerFingerprint{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
{Path: "protocols/javascript/net-multi-step.yaml", TestCase: &networkMultiStep{}},
{Path: "protocols/javascript/net-https.yaml", TestCase: &javascriptNetHttps{}},
{Path: "protocols/javascript/rsync-test.yaml", TestCase: &javascriptRsyncTest{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
{Path: "protocols/javascript/oracle-auth-test.yaml", TestCase: &javascriptOracleAuthTest{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
{Path: "protocols/javascript/vnc-pass-brute.yaml", TestCase: &javascriptVncPassBrute{}},
{Path: "protocols/javascript/postgres-pass-brute.yaml", TestCase: &javascriptPostgresPassBrute{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
{Path: "protocols/javascript/mysql-connect.yaml", TestCase: &javascriptMySQLConnect{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
{Path: "protocols/javascript/multi-ports.yaml", TestCase: &javascriptMultiPortsSSH{}},
{Path: "protocols/javascript/no-port-args.yaml", TestCase: &javascriptNoPortArgs{}},
{Path: "protocols/javascript/telnet-auth-test.yaml", TestCase: &javascriptTelnetAuthTest{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
}
var (
redisResource *dockertest.Resource
sshResource *dockertest.Resource
oracleResource *dockertest.Resource
vncResource *dockertest.Resource
telnetResource *dockertest.Resource
postgresResource *dockertest.Resource
mysqlResource *dockertest.Resource
rsyncResource *dockertest.Resource
pool *dockertest.Pool
defaultRetry = 3
)
type javascriptNetHttps struct{}
func (j *javascriptNetHttps) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type javascriptRedisPassBrute struct{}
func (j *javascriptRedisPassBrute) Execute(filePath string) error {
if redisResource == nil || pool == nil {
// skip test as redis is not running
return nil
}
tempPort := redisResource.GetPort("6379/tcp")
finalURL := "localhost:" + tempPort
defer purge(redisResource)
errs := []error{}
for i := 0; i < defaultRetry; i++ {
results := []string{}
var err error
_ = pool.Retry(func() error {
//let ssh server start
time.Sleep(3 * time.Second)
results, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)
return nil
})
if err != nil {
return err
}
if err := expectResultsCount(results, 1); err == nil {
return nil
} else {
errs = append(errs, err)
}
}
return multierr.Combine(errs...)
}
type javascriptSSHServerFingerprint struct{}
func (j *javascriptSSHServerFingerprint) Execute(filePath string) error {
if sshResource == nil || pool == nil {
// skip test as redis is not running
return nil
}
tempPort := sshResource.GetPort("2222/tcp")
finalURL := "localhost:" + tempPort
defer purge(sshResource)
errs := []error{}
for i := 0; i < defaultRetry; i++ {
results := []string{}
var err error
_ = pool.Retry(func() error {
//let ssh server start
time.Sleep(3 * time.Second)
results, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)
return nil
})
if err != nil {
return err
}
if err := expectResultsCount(results, 1); err == nil {
return nil
} else {
errs = append(errs, err)
}
}
return multierr.Combine(errs...)
}
type javascriptOracleAuthTest struct{}
func (j *javascriptOracleAuthTest) Execute(filePath string) error {
if oracleResource == nil || pool == nil {
// skip test as oracle is not running
return nil
}
tempPort := oracleResource.GetPort("1521/tcp")
finalURL := "localhost:" + tempPort
defer purge(oracleResource)
errs := []error{}
for i := 0; i < defaultRetry; i++ {
results := []string{}
var err error
_ = pool.Retry(func() error {
// let oracle server start
time.Sleep(3 * time.Second)
results, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)
return nil
})
if err != nil {
return err
}
if err := expectResultsCount(results, 1); err == nil {
return nil
} else {
errs = append(errs, err)
}
}
return multierr.Combine(errs...)
}
type javascriptVncPassBrute struct{}
func (j *javascriptVncPassBrute) Execute(filePath string) error {
if vncResource == nil || pool == nil {
// skip test as vnc is not running
return nil
}
tempPort := vncResource.GetPort("5900/tcp")
finalURL := "localhost:" + tempPort
defer purge(vncResource)
errs := []error{}
for i := 0; i < defaultRetry; i++ {
results := []string{}
var err error
_ = pool.Retry(func() error {
//let ssh server start
time.Sleep(3 * time.Second)
results, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)
return nil
})
if err != nil {
return err
}
if err := expectResultsCount(results, 1); err == nil {
return nil
} else {
errs = append(errs, err)
}
}
return multierr.Combine(errs...)
}
type javascriptPostgresPassBrute struct{}
func (j *javascriptPostgresPassBrute) Execute(filePath string) error {
if postgresResource == nil || pool == nil {
// skip test as postgres is not running
return nil
}
tempPort := postgresResource.GetPort("5432/tcp")
finalURL := "localhost:" + tempPort
defer purge(postgresResource)
errs := []error{}
for i := 0; i < defaultRetry; i++ {
results := []string{}
var err error
_ = pool.Retry(func() error {
//let postgres server start
time.Sleep(3 * time.Second)
results, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)
return nil
})
if err != nil {
return err
}
if err := expectResultsCount(results, 1); err == nil {
return nil
} else {
errs = append(errs, err)
}
}
return multierr.Combine(errs...)
}
type javascriptMySQLConnect struct{}
func (j *javascriptMySQLConnect) Execute(filePath string) error {
if mysqlResource == nil || pool == nil {
// skip test as mysql is not running
return nil
}
tempPort := mysqlResource.GetPort("3306/tcp")
finalURL := "localhost:" + tempPort
defer purge(mysqlResource)
errs := []error{}
for i := 0; i < defaultRetry; i++ {
results := []string{}
var err error
_ = pool.Retry(func() error {
//let mysql server start
time.Sleep(5 * time.Second)
results, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)
return nil
})
if err != nil {
return err
}
if err := expectResultsCount(results, 1); err == nil {
return nil
} else {
errs = append(errs, err)
}
}
return multierr.Combine(errs...)
}
type javascriptMultiPortsSSH struct{}
func (j *javascriptMultiPortsSSH) Execute(filePath string) error {
// use scanme.sh as target to ensure we match on the 2nd default port 22
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type javascriptNoPortArgs struct{}
func (j *javascriptNoPortArgs) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "yo.dawg", debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type javascriptRsyncTest struct{}
func (j *javascriptRsyncTest) Execute(filePath string) error {
if rsyncResource == nil || pool == nil {
// skip test as rsync is not running
return nil
}
tempPort := rsyncResource.GetPort("873/tcp")
finalURL := "localhost:" + tempPort
defer purge(rsyncResource)
errs := []error{}
for i := 0; i < defaultRetry; i++ {
results := []string{}
var err error
_ = pool.Retry(func() error {
//let rsync server start
time.Sleep(3 * time.Second)
results, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)
return nil
})
if err != nil {
return err
}
if err := expectResultsCount(results, 1); err == nil {
return nil
} else {
errs = append(errs, err)
}
}
return multierr.Combine(errs...)
}
type javascriptTelnetAuthTest struct{}
func (j *javascriptTelnetAuthTest) Execute(filePath string) error {
if telnetResource == nil || pool == nil {
// skip test as telnet is not running
return nil
}
tempPort := telnetResource.GetPort("23/tcp")
finalURL := "localhost:" + tempPort
defer purge(telnetResource)
errs := []error{}
for i := 0; i < defaultRetry; i++ {
results := []string{}
var err error
_ = pool.Retry(func() error {
//let telnet server start
time.Sleep(3 * time.Second)
results, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)
return nil
})
if err != nil {
return err
}
if err := expectResultsCount(results, 1); err == nil {
return nil
} else {
errs = append(errs, err)
}
}
return multierr.Combine(errs...)
}
// purge any given resource if it is not nil
func purge(resource *dockertest.Resource) {
if resource != nil && pool != nil {
containerName := resource.Container.Name
_ = pool.Client.StopContainer(resource.Container.ID, 0)
err := pool.Purge(resource)
if err != nil {
log.Printf("Could not purge resource: %s", err)
}
_ = pool.RemoveContainerByName(containerName)
}
}
func init() {
// uses a sensible default on windows (tcp/http) and linux/osx (socket)
pool, err := dockertest.NewPool("")
if err != nil {
log.Printf("something went wrong with dockertest: %s", err)
return
}
// uses pool to try to connect to Docker
err = pool.Client.Ping()
if err != nil {
log.Printf("Could not connect to Docker: %s", err)
}
// setup a temporary redis instance
redisResource, err = pool.RunWithOptions(&dockertest.RunOptions{
Repository: "redis",
Tag: "latest",
Cmd: []string{"redis-server", "--requirepass", "iamadmin"},
Platform: "linux/amd64",
})
if err != nil {
log.Printf("Could not start resource: %s", err)
return
}
// by default expire after 30 sec
if err := redisResource.Expire(30); err != nil {
log.Printf("Could not expire resource: %s", err)
}
// setup a temporary ssh server
sshResource, err = pool.RunWithOptions(&dockertest.RunOptions{
Repository: "lscr.io/linuxserver/openssh-server",
Tag: "latest",
Env: []string{
"PUID=1000",
"PGID=1000",
"TZ=Etc/UTC",
"PASSWORD_ACCESS=true",
"USER_NAME=admin",
"USER_PASSWORD=admin",
},
Platform: "linux/amd64",
})
if err != nil {
log.Printf("Could not start resource: %s", err)
return
}
// by default expire after 30 sec
if err := sshResource.Expire(30); err != nil {
log.Printf("Could not expire resource: %s", err)
}
// setup a temporary oracle instance
oracleResource, err = pool.RunWithOptions(&dockertest.RunOptions{
Repository: "gvenzl/oracle-xe",
Tag: "latest",
Env: []string{
"ORACLE_PASSWORD=mysecret",
},
Platform: "linux/amd64",
})
if err != nil {
log.Printf("Could not start Oracle resource: %s", err)
return
}
// by default expire after 30 sec
if err := oracleResource.Expire(30); err != nil {
log.Printf("Could not expire Oracle resource: %s", err)
}
// setup a temporary vnc server
vncResource, err = pool.RunWithOptions(&dockertest.RunOptions{
Repository: "dorowu/ubuntu-desktop-lxde-vnc",
Tag: "latest",
Env: []string{
"VNC_PASSWORD=mysecret",
},
Platform: "linux/amd64",
})
if err != nil {
log.Printf("Could not start resource: %s", err)
return
}
// by default expire after 30 sec
if err := vncResource.Expire(30); err != nil {
log.Printf("Could not expire resource: %s", err)
}
// setup a temporary postgres instance
postgresResource, err = pool.RunWithOptions(&dockertest.RunOptions{
Repository: "postgres",
Tag: "latest",
Env: []string{
"POSTGRES_PASSWORD=postgres",
"POSTGRES_USER=postgres",
},
Platform: "linux/amd64",
})
if err != nil {
log.Printf("Could not start postgres resource: %s", err)
return
}
// by default expire after 30 sec
if err := postgresResource.Expire(30); err != nil {
log.Printf("Could not expire postgres resource: %s", err)
}
// setup a temporary mysql instance
mysqlResource, err = pool.RunWithOptions(&dockertest.RunOptions{
Repository: "mysql",
Tag: "latest",
Env: []string{
"MYSQL_ROOT_PASSWORD=secret",
},
Platform: "linux/amd64",
})
if err != nil {
log.Printf("Could not start mysql resource: %s", err)
return
}
// by default expire after 30 sec
if err := mysqlResource.Expire(30); err != nil {
log.Printf("Could not expire mysql resource: %s", err)
}
// setup a temporary rsync server
rsyncResource, err = pool.RunWithOptions(&dockertest.RunOptions{
Repository: "alpine",
Tag: "latest",
Cmd: []string{"sh", "-c", "apk add --no-cache rsync shadow && useradd -m rsyncuser && echo 'rsyncuser:mysecret' | chpasswd && echo 'rsyncuser:MySecret123' > /etc/rsyncd.secrets && chmod 600 /etc/rsyncd.secrets && echo -e '[data]\\n path = /data\\n comment = Local Rsync Share\\n read only = false\\n auth users = rsyncuser\\n secrets file = /etc/rsyncd.secrets' > /etc/rsyncd.conf && mkdir -p /data && exec rsync --daemon --no-detach --config=/etc/rsyncd.conf"},
Platform: "linux/amd64",
})
if err != nil {
log.Printf("Could not start Rsync resource: %s", err)
return
}
// by default expire after 30 sec
if err := rsyncResource.Expire(30); err != nil {
log.Printf("Could not expire Rsync resource: %s", err)
}
// setup a temporary telnet server
// username: dev
// password: mysecret
telnetResource, err = pool.RunWithOptions(&dockertest.RunOptions{
Repository: "alpine",
Tag: "latest",
Cmd: []string{"sh", "-c", "apk add --no-cache busybox-extras shadow && useradd -m dev && echo 'dev:mysecret' | chpasswd && exec /usr/sbin/telnetd -F -p 23 -l /bin/login"},
Platform: "linux/amd64",
})
if err != nil {
log.Printf("Could not start Telnet resource: %s", err)
return
}
// by default expire after 30 sec
if err := telnetResource.Expire(30); err != nil {
log.Printf("Could not expire Telnet resource: %s", err)
}
}
================================================
FILE: cmd/integration-test/library.go
================================================
package main
import (
"context"
"fmt"
"log"
"net/http"
"net/http/httptest"
"os"
"path"
"strings"
"time"
"github.com/julienschmidt/httprouter"
"github.com/logrusorgru/aurora"
"github.com/pkg/errors"
"github.com/projectdiscovery/goflags"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
"github.com/projectdiscovery/nuclei/v3/pkg/core"
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
parsers "github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting"
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/ratelimit"
)
var libraryTestcases = []TestCaseInfo{
{Path: "library/test.yaml", TestCase: &goIntegrationTest{}},
{Path: "library/test.json", TestCase: &goIntegrationTest{}},
}
type goIntegrationTest struct{}
// Execute executes a test case and returns an error if occurred
//
// Execute the docs at ../DESIGN.md if the code stops working for integration.
func (h *goIntegrationTest) Execute(templatePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "This is test matcher text")
if strings.EqualFold(r.Header.Get("test"), "nuclei") {
_, _ = fmt.Fprintf(w, "This is test headers matcher text")
}
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := executeNucleiAsLibrary(templatePath, ts.URL)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
// executeNucleiAsLibrary contains an example
func executeNucleiAsLibrary(templatePath, templateURL string) ([]string, error) {
cache := hosterrorscache.New(30, hosterrorscache.DefaultMaxHostsCount, nil)
defer cache.Close()
defaultOpts := types.DefaultOptions()
defaultOpts.ExecutionId = "test"
defaultOpts.Logger = gologger.DefaultLogger
mockProgress := &testutils.MockProgressClient{}
reportingClient, err := reporting.New(&reporting.Options{ExecutionId: defaultOpts.ExecutionId}, "", false)
if err != nil {
return nil, err
}
defer reportingClient.Close()
_ = protocolstate.Init(defaultOpts)
_ = protocolinit.Init(defaultOpts)
defer protocolstate.Close(defaultOpts.ExecutionId)
defaultOpts.Templates = goflags.StringSlice{templatePath}
defaultOpts.ExcludeTags = config.ReadIgnoreFile().Tags
outputWriter := testutils.NewMockOutputWriter(defaultOpts.OmitTemplate)
var results []string
outputWriter.WriteCallback = func(event *output.ResultEvent) {
results = append(results, fmt.Sprintf("%v\n", event))
}
interactOpts := interactsh.DefaultOptions(outputWriter, reportingClient, mockProgress)
interactClient, err := interactsh.New(interactOpts)
if err != nil {
return nil, errors.Wrap(err, "could not create interact client")
}
defer interactClient.Close()
home, _ := os.UserHomeDir()
catalog := disk.NewCatalog(path.Join(home, "nuclei-templates"))
ratelimiter := ratelimit.New(context.Background(), 150, time.Second)
defer ratelimiter.Stop()
executerOpts := &protocols.ExecutorOptions{
Output: outputWriter,
Options: defaultOpts,
Progress: mockProgress,
Catalog: catalog,
IssuesClient: reportingClient,
RateLimiter: ratelimiter,
Interactsh: interactClient,
HostErrorsCache: cache,
Colorizer: aurora.NewAurora(true),
ResumeCfg: types.NewResumeCfg(),
Parser: templates.NewParser(),
}
engine := core.New(defaultOpts)
engine.SetExecuterOptions(executerOpts)
workflowLoader, err := parsers.NewLoader(executerOpts)
if err != nil {
log.Fatalf("Could not create workflow loader: %s\n", err)
}
executerOpts.WorkflowLoader = workflowLoader
store, err := loader.New(loader.NewConfig(defaultOpts, catalog, executerOpts))
if err != nil {
return nil, errors.Wrap(err, "could not create loader")
}
if err := store.Load(); err != nil {
return nil, errors.Wrap(err, "could not load templates")
}
_ = engine.Execute(context.Background(), store.Templates(), provider.NewSimpleInputProviderWithUrls(defaultOpts.ExecutionId, templateURL))
engine.WorkPool().Wait() // Wait for the scan to finish
return results, nil
}
================================================
FILE: cmd/integration-test/loader.go
================================================
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"os"
"strings"
"github.com/julienschmidt/httprouter"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
"github.com/projectdiscovery/utils/errkit"
permissionutil "github.com/projectdiscovery/utils/permission"
)
var loaderTestcases = []TestCaseInfo{
{Path: "loader/template-list.yaml", TestCase: &remoteTemplateList{}},
{Path: "loader/workflow-list.yaml", TestCase: &remoteWorkflowList{}},
{Path: "loader/excluded-template.yaml", TestCase: &excludedTemplate{}},
{Path: "loader/nonexistent-template-list.yaml", TestCase: &nonExistentTemplateList{}},
{Path: "loader/nonexistent-workflow-list.yaml", TestCase: &nonExistentWorkflowList{}},
{Path: "loader/template-list-not-allowed.yaml", TestCase: &remoteTemplateListNotAllowed{}},
{Path: "loader/load-template-with-id", TestCase: &loadTemplateWithID{}},
}
type remoteTemplateList struct{}
// Execute executes a test case and returns an error if occurred
func (h *remoteTemplateList) Execute(templateList string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "This is test matcher text")
if strings.EqualFold(r.Header.Get("test"), "nuclei") {
_, _ = fmt.Fprintf(w, "This is test headers matcher text")
}
})
router.GET("/template_list", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
file, err := os.ReadFile(templateList)
if err != nil {
w.WriteHeader(500)
}
_, err = w.Write(file)
if err != nil {
w.WriteHeader(500)
}
})
ts := httptest.NewServer(router)
defer ts.Close()
configFileData := `remote-template-domain: [ "` + ts.Listener.Addr().String() + `" ]`
err := os.WriteFile("test-config.yaml", []byte(configFileData), permissionutil.ConfigFilePermission)
if err != nil {
return err
}
defer func() {
_ = os.Remove("test-config.yaml")
}()
results, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, "-target", ts.URL, "-template-url", ts.URL+"/template_list", "-config", "test-config.yaml")
if err != nil {
return err
}
return expectResultsCount(results, 2)
}
type excludedTemplate struct{}
// Execute executes a test case and returns an error if occurred
func (h *excludedTemplate) Execute(templateList string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "This is test matcher text")
if strings.EqualFold(r.Header.Get("test"), "nuclei") {
_, _ = fmt.Fprintf(w, "This is test headers matcher text")
}
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, "-target", ts.URL, "-t", templateList, "-include-templates", templateList)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type remoteTemplateListNotAllowed struct{}
// Execute executes a test case and returns an error if occurred
func (h *remoteTemplateListNotAllowed) Execute(templateList string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "This is test matcher text")
if strings.EqualFold(r.Header.Get("test"), "nuclei") {
_, _ = fmt.Fprintf(w, "This is test headers matcher text")
}
})
router.GET("/template_list", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
file, err := os.ReadFile(templateList)
if err != nil {
w.WriteHeader(500)
}
_, err = w.Write(file)
if err != nil {
w.WriteHeader(500)
}
})
ts := httptest.NewServer(router)
defer ts.Close()
_, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, "-target", ts.URL, "-template-url", ts.URL+"/template_list")
if err == nil {
return fmt.Errorf("expected error for not allowed remote template list url")
}
return nil
}
type remoteWorkflowList struct{}
// Execute executes a test case and returns an error if occurred
func (h *remoteWorkflowList) Execute(workflowList string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "This is test matcher text")
if strings.EqualFold(r.Header.Get("test"), "nuclei") {
_, _ = fmt.Fprintf(w, "This is test headers matcher text")
}
})
router.GET("/workflow_list", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
file, err := os.ReadFile(workflowList)
if err != nil {
w.WriteHeader(500)
}
_, err = w.Write(file)
if err != nil {
w.WriteHeader(500)
}
})
ts := httptest.NewServer(router)
defer ts.Close()
configFileData := `remote-template-domain: [ "` + ts.Listener.Addr().String() + `" ]`
err := os.WriteFile("test-config.yaml", []byte(configFileData), permissionutil.ConfigFilePermission)
if err != nil {
return err
}
defer func() {
_ = os.Remove("test-config.yaml")
}()
results, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, "-target", ts.URL, "-workflow-url", ts.URL+"/workflow_list", "-config", "test-config.yaml")
if err != nil {
return err
}
return expectResultsCount(results, 3)
}
type nonExistentTemplateList struct{}
// Execute executes a test case and returns an error if occurred
func (h *nonExistentTemplateList) Execute(nonExistingTemplateList string) error {
router := httprouter.New()
ts := httptest.NewServer(router)
defer ts.Close()
configFileData := `remote-template-domain: [ "` + ts.Listener.Addr().String() + `" ]`
err := os.WriteFile("test-config.yaml", []byte(configFileData), permissionutil.ConfigFilePermission)
if err != nil {
return err
}
defer func() {
_ = os.Remove("test-config.yaml")
}()
_, err = testutils.RunNucleiBareArgsAndGetResults(debug, nil, "-target", ts.URL, "-template-url", ts.URL+"/404", "-config", "test-config.yaml")
if err == nil {
return fmt.Errorf("expected error for nonexisting workflow url")
}
return nil
}
type nonExistentWorkflowList struct{}
// Execute executes a test case and returns an error if occurred
func (h *nonExistentWorkflowList) Execute(nonExistingWorkflowList string) error {
router := httprouter.New()
ts := httptest.NewServer(router)
defer ts.Close()
configFileData := `remote-template-domain: [ "` + ts.Listener.Addr().String() + `" ]`
err := os.WriteFile("test-config.yaml", []byte(configFileData), permissionutil.ConfigFilePermission)
if err != nil {
return err
}
defer func() {
_ = os.Remove("test-config.yaml")
}()
_, err = testutils.RunNucleiBareArgsAndGetResults(debug, nil, "-target", ts.URL, "-workflow-url", ts.URL+"/404", "-config", "test-config.yaml")
if err == nil {
return fmt.Errorf("expected error for nonexisting workflow url")
}
return nil
}
type loadTemplateWithID struct{}
func (h *loadTemplateWithID) Execute(nooop string) error {
results, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, "-target", "scanme.sh", "-id", "self-signed-ssl")
if err != nil {
return errkit.Wrap(err, "failed to load template with id")
}
return expectResultsCount(results, 1)
}
================================================
FILE: cmd/integration-test/matcher-status.go
================================================
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"strings"
"github.com/julienschmidt/httprouter"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
)
var matcherStatusTestcases = []TestCaseInfo{
{Path: "protocols/http/get.yaml", TestCase: &httpNoAccess{}},
{Path: "protocols/network/net-https.yaml", TestCase: &networkNoAccess{}},
{Path: "protocols/headless/headless-basic.yaml", TestCase: &headlessNoAccess{}},
{Path: "protocols/javascript/net-https.yaml", TestCase: &javascriptNoAccess{}},
{Path: "protocols/websocket/basic.yaml", TestCase: &websocketNoAccess{}},
{Path: "protocols/dns/a.yaml", TestCase: &dnsNoAccess{}},
{Path: "protocols/http/matcher-status-and.yaml,protocols/http/matcher-status-and-cluster.yaml", TestCase: &httpMatcherStatusAnd{}},
}
type httpNoAccess struct{}
func (h *httpNoAccess) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "trust_me_bro.real", debug, "-ms", "-j")
if err != nil {
return err
}
event := &output.ResultEvent{}
_ = json.Unmarshal([]byte(results[0]), event)
expectedError := "no address found for host"
if !strings.Contains(event.Error, expectedError) {
return fmt.Errorf("unexpected result: expecting \"%s\" error but got \"%s\"", expectedError, event.Error)
}
return nil
}
type networkNoAccess struct{}
// Execute executes a test case and returns an error if occurred
func (h *networkNoAccess) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "trust_me_bro.real", debug, "-ms", "-j")
if err != nil {
return err
}
event := &output.ResultEvent{}
_ = json.Unmarshal([]byte(results[0]), event)
if event.Error != "no address found for host" {
return fmt.Errorf("unexpected result: expecting \"no address found for host\" error but got \"%s\"", event.Error)
}
return nil
}
type headlessNoAccess struct{}
// Execute executes a test case and returns an error if occurred
func (h *headlessNoAccess) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "trust_me_bro.real", debug, "-headless", "-ms", "-j")
if err != nil {
return err
}
event := &output.ResultEvent{}
_ = json.Unmarshal([]byte(results[0]), event)
if event.Error == "" {
return fmt.Errorf("unexpected result: expecting an error but got \"%s\"", event.Error)
}
return nil
}
type javascriptNoAccess struct{}
// Execute executes a test case and returns an error if occurred
func (h *javascriptNoAccess) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "trust_me_bro.real", debug, "-ms", "-j")
if err != nil {
return err
}
event := &output.ResultEvent{}
_ = json.Unmarshal([]byte(results[0]), event)
if event.Error == "" {
return fmt.Errorf("unexpected result: expecting an error but got \"%s\"", event.Error)
}
return nil
}
type websocketNoAccess struct{}
// Execute executes a test case and returns an error if occurred
func (h *websocketNoAccess) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "ws://trust_me_bro.real", debug, "-ms", "-j")
if err != nil {
return err
}
event := &output.ResultEvent{}
_ = json.Unmarshal([]byte(results[0]), event)
if event.Error == "" {
return fmt.Errorf("unexpected result: expecting an error but got \"%s\"", event.Error)
}
return nil
}
type dnsNoAccess struct{}
// Execute executes a test case and returns an error if occurred
func (h *dnsNoAccess) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "trust_me_bro.real", debug, "-ms", "-j")
if err != nil {
return err
}
event := &output.ResultEvent{}
_ = json.Unmarshal([]byte(results[0]), event)
if event.Error == "" {
return fmt.Errorf("unexpected result: expecting an error but got \"%s\"", event.Error)
}
return nil
}
type httpMatcherStatusAnd struct{}
// Execute verifies that clustered templates with matchers-condition: and
// produce failure events when -matcher-status is enabled.
func (h *httpMatcherStatusAnd) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = w.Write([]byte("ok"))
})
ts := httptest.NewServer(router)
defer ts.Close()
files := strings.Split(filePath, ",")
results, err := testutils.RunNucleiTemplateAndGetResults(files[0], ts.URL, debug, "-t", files[1], "-ms", "-j")
if err != nil {
return err
}
if len(results) != 2 {
return fmt.Errorf("unexpected number of results: %d (expected 2)", len(results))
}
return nil
}
================================================
FILE: cmd/integration-test/multi.go
================================================
package main
import (
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
)
var multiProtoTestcases = []TestCaseInfo{
{Path: "protocols/multi/dynamic-values.yaml", TestCase: &multiProtoDynamicExtractor{}},
{Path: "protocols/multi/evaluate-variables.yaml", TestCase: &multiProtoDynamicExtractor{}},
{Path: "protocols/multi/exported-response-vars.yaml", TestCase: &multiProtoDynamicExtractor{}},
}
type multiProtoDynamicExtractor struct{}
// Execute executes a test case and returns an error if occurred
func (h *multiProtoDynamicExtractor) Execute(templatePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(templatePath, "docs.projectdiscovery.io", debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
================================================
FILE: cmd/integration-test/network.go
================================================
package main
import (
"fmt"
"net"
"os"
"strings"
"time"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
osutils "github.com/projectdiscovery/utils/os"
"github.com/projectdiscovery/utils/reader"
)
var networkTestcases = []TestCaseInfo{
{Path: "protocols/network/basic.yaml", TestCase: &networkBasic{}, DisableOn: func() bool { return osutils.IsWindows() }},
{Path: "protocols/network/hex.yaml", TestCase: &networkBasic{}, DisableOn: func() bool { return osutils.IsWindows() }},
{Path: "protocols/network/multi-step.yaml", TestCase: &networkMultiStep{}},
{Path: "protocols/network/self-contained.yaml", TestCase: &networkRequestSelContained{}},
{Path: "protocols/network/variables.yaml", TestCase: &networkVariables{}},
{Path: "protocols/network/same-address.yaml", TestCase: &networkBasic{}},
{Path: "protocols/network/network-port.yaml", TestCase: &networkPort{}},
{Path: "protocols/network/net-https.yaml", TestCase: &networkhttps{}},
{Path: "protocols/network/net-https-timeout.yaml", TestCase: &networkhttps{}},
}
const defaultStaticPort = 5431
type networkBasic struct{}
// Execute executes a test case and returns an error if occurred
func (h *networkBasic) Execute(filePath string) error {
var routerErr error
ts := testutils.NewTCPServer(nil, defaultStaticPort, func(conn net.Conn) {
defer func() {
_ = conn.Close()
}()
data, err := reader.ConnReadNWithTimeout(conn, 4, time.Duration(5)*time.Second)
if err != nil {
routerErr = err
return
}
if string(data) == "PING" {
_, _ = conn.Write([]byte("PONG"))
} else {
routerErr = fmt.Errorf("invalid data received: %s", string(data))
}
})
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Could not run nuclei: %s\n", err)
return err
}
if routerErr != nil {
_, _ = fmt.Fprintf(os.Stderr, "routerErr: %s\n", routerErr)
return routerErr
}
return expectResultsCount(results, 1)
}
type networkMultiStep struct{}
// Execute executes a test case and returns an error if occurred
func (h *networkMultiStep) Execute(filePath string) error {
var routerErr error
ts := testutils.NewTCPServer(nil, defaultStaticPort, func(conn net.Conn) {
defer func() {
_ = conn.Close()
}()
data, err := reader.ConnReadNWithTimeout(conn, 5, time.Duration(5)*time.Second)
if err != nil {
routerErr = err
return
}
if string(data) == "FIRST" {
_, _ = conn.Write([]byte("PING"))
}
data, err = reader.ConnReadNWithTimeout(conn, 6, time.Duration(5)*time.Second)
if err != nil {
routerErr = err
return
}
if string(data) == "SECOND" {
_, _ = conn.Write([]byte("PONG"))
}
_, _ = conn.Write([]byte("NUCLEI"))
})
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
if routerErr != nil {
return routerErr
}
var expectedResultsSize int
if debug {
expectedResultsSize = 3
} else {
expectedResultsSize = 1
}
return expectResultsCount(results, expectedResultsSize)
}
type networkRequestSelContained struct{}
// Execute executes a test case and returns an error if occurred
func (h *networkRequestSelContained) Execute(filePath string) error {
ts := testutils.NewTCPServer(nil, defaultStaticPort, func(conn net.Conn) {
defer func() {
_ = conn.Close()
}()
_, _ = conn.Write([]byte("Authentication successful"))
})
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "", debug, "-esc")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type networkVariables struct{}
// Execute executes a test case and returns an error if occurred
func (h *networkVariables) Execute(filePath string) error {
var routerErr error
ts := testutils.NewTCPServer(nil, defaultStaticPort, func(conn net.Conn) {
defer func() {
_ = conn.Close()
}()
data, err := reader.ConnReadNWithTimeout(conn, 4, time.Duration(5)*time.Second)
if err != nil {
routerErr = err
return
}
if string(data) == "PING" {
_, _ = conn.Write([]byte("aGVsbG8="))
}
})
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
if routerErr != nil {
return routerErr
}
return expectResultsCount(results, 1)
}
type networkPort struct{}
func (n *networkPort) Execute(filePath string) error {
ts := testutils.NewTCPServer(nil, 23846, func(conn net.Conn) {
defer func() {
_ = conn.Close()
}()
data, err := reader.ConnReadNWithTimeout(conn, 4, time.Duration(5)*time.Second)
if err != nil {
return
}
if string(data) == "PING" {
_, _ = conn.Write([]byte("PONG"))
}
})
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
if err := expectResultsCount(results, 1); err != nil {
return err
}
// even though we passed port 443 in url it is ignored and port 23846 is used
results, err = testutils.RunNucleiTemplateAndGetResults(filePath, strings.ReplaceAll(ts.URL, "23846", "443"), debug)
if err != nil {
return err
}
if err := expectResultsCount(results, 1); err != nil {
return err
}
// this is positive test case where we expect port to be overridden and 34567 to be used
ts2 := testutils.NewTCPServer(nil, 34567, func(conn net.Conn) {
defer func() {
_ = conn.Close()
}()
data, err := reader.ConnReadNWithTimeout(conn, 4, time.Duration(5)*time.Second)
if err != nil {
return
}
if string(data) == "PING" {
_, _ = conn.Write([]byte("PONG"))
}
})
defer ts2.Close()
// even though we passed port 443 in url it is ignored and port 23846 is used
// instead of hardcoded port 23846 in template
results, err = testutils.RunNucleiTemplateAndGetResults(filePath, ts2.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type networkhttps struct{}
// Execute executes a test case and returns an error if occurred
func (h *networkhttps) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "scanme.sh", debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
================================================
FILE: cmd/integration-test/offline-http.go
================================================
package main
import (
"fmt"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
)
var offlineHttpTestcases = []TestCaseInfo{
{Path: "protocols/offlinehttp/rfc-req-resp.yaml", TestCase: &RfcRequestResponse{}},
{Path: "protocols/offlinehttp/offline-allowed-paths.yaml", TestCase: &RequestResponseWithAllowedPaths{}},
{Path: "protocols/offlinehttp/offline-raw.yaml", TestCase: &RawRequestResponse{}},
}
type RfcRequestResponse struct{}
// Execute executes a test case and returns an error if occurred
func (h *RfcRequestResponse) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "protocols/offlinehttp/data/", debug, "-passive")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type RequestResponseWithAllowedPaths struct{}
// Execute executes a test case and returns an error if occurred
func (h *RequestResponseWithAllowedPaths) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "protocols/offlinehttp/data/", debug, "-passive")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type RawRequestResponse struct{}
// Execute executes a test case and returns an error if occurred
func (h *RawRequestResponse) Execute(filePath string) error {
_, err := testutils.RunNucleiTemplateAndGetResults(filePath, "protocols/offlinehttp/data/", debug, "-passive")
if err == nil {
return fmt.Errorf("incorrect result: no error (actual) vs error expected")
}
return nil
}
================================================
FILE: cmd/integration-test/profile-loader.go
================================================
package main
import (
"fmt"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
"github.com/projectdiscovery/utils/errkit"
)
var profileLoaderTestcases = []TestCaseInfo{
{Path: "profile-loader/load-with-filename", TestCase: &profileLoaderByRelFile{}},
{Path: "profile-loader/load-with-id", TestCase: &profileLoaderById{}},
{Path: "profile-loader/basic.yml", TestCase: &customProfileLoader{}},
}
type profileLoaderByRelFile struct{}
func (h *profileLoaderByRelFile) Execute(testName string) error {
results, err := testutils.RunNucleiWithArgsAndGetResults(debug, "-tl", "-tp", "cloud.yml")
if err != nil {
return errkit.Wrap(err, "failed to load template with id")
}
if len(results) <= 10 {
return fmt.Errorf("incorrect result: expected more results than %d, got %v", 10, len(results))
}
return nil
}
type profileLoaderById struct{}
func (h *profileLoaderById) Execute(testName string) error {
results, err := testutils.RunNucleiWithArgsAndGetResults(debug, "-tl", "-tp", "cloud")
if err != nil {
return errkit.Wrap(err, "failed to load template with id")
}
if len(results) <= 10 {
return fmt.Errorf("incorrect result: expected more results than %d, got %v", 10, len(results))
}
return nil
}
// this profile with load kevs
type customProfileLoader struct{}
func (h *customProfileLoader) Execute(filepath string) error {
results, err := testutils.RunNucleiWithArgsAndGetResults(debug, "-tl", "-tp", filepath)
if err != nil {
return errkit.Wrap(err, "failed to load template with id")
}
if len(results) < 1 {
return fmt.Errorf("incorrect result: expected more results than %d, got %v", 1, len(results))
}
return nil
}
================================================
FILE: cmd/integration-test/ssl.go
================================================
package main
import (
"crypto/tls"
"net"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
)
var sslTestcases = []TestCaseInfo{
{Path: "protocols/ssl/basic.yaml", TestCase: &sslBasic{}},
{Path: "protocols/ssl/basic-ztls.yaml", TestCase: &sslBasicZtls{}},
{Path: "protocols/ssl/custom-cipher.yaml", TestCase: &sslCustomCipher{}},
{Path: "protocols/ssl/custom-version.yaml", TestCase: &sslCustomVersion{}},
{Path: "protocols/ssl/ssl-with-vars.yaml", TestCase: &sslWithVars{}},
{Path: "protocols/ssl/multi-req.yaml", TestCase: &sslMultiReq{}},
}
type sslBasic struct{}
// Execute executes a test case and returns an error if occurred
func (h *sslBasic) Execute(filePath string) error {
ts := testutils.NewTCPServer(&tls.Config{}, defaultStaticPort, func(conn net.Conn) {
defer func() {
_ = conn.Close()
}()
data := make([]byte, 4)
if _, err := conn.Read(data); err != nil {
return
}
})
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type sslBasicZtls struct{}
// Execute executes a test case and returns an error if occurred
func (h *sslBasicZtls) Execute(filePath string) error {
ts := testutils.NewTCPServer(&tls.Config{}, defaultStaticPort, func(conn net.Conn) {
defer func() {
_ = conn.Close()
}()
data := make([]byte, 4)
if _, err := conn.Read(data); err != nil {
return
}
})
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-ztls")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type sslCustomCipher struct{}
// Execute executes a test case and returns an error if occurred
func (h *sslCustomCipher) Execute(filePath string) error {
ts := testutils.NewTCPServer(&tls.Config{CipherSuites: []uint16{tls.TLS_AES_128_GCM_SHA256}}, defaultStaticPort, func(conn net.Conn) {
defer func() {
_ = conn.Close()
}()
data := make([]byte, 4)
if _, err := conn.Read(data); err != nil {
return
}
})
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type sslCustomVersion struct{}
// Execute executes a test case and returns an error if occurred
func (h *sslCustomVersion) Execute(filePath string) error {
ts := testutils.NewTCPServer(&tls.Config{MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS12}, defaultStaticPort, func(conn net.Conn) {
defer func() {
_ = conn.Close()
}()
data := make([]byte, 4)
if _, err := conn.Read(data); err != nil {
return
}
})
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type sslWithVars struct{}
func (h *sslWithVars) Execute(filePath string) error {
ts := testutils.NewTCPServer(&tls.Config{}, defaultStaticPort, func(conn net.Conn) {
defer func() {
_ = conn.Close()
}()
data := make([]byte, 4)
if _, err := conn.Read(data); err != nil {
return
}
})
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-V", "test=asdasdas")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type sslMultiReq struct{}
func (h *sslMultiReq) Execute(filePath string) error {
//nolint:staticcheck // SSLv3 is intentionally used for testing purposes
ts := testutils.NewTCPServer(&tls.Config{
MinVersion: tls.VersionSSL30,
MaxVersion: tls.VersionTLS11,
}, defaultStaticPort, func(conn net.Conn) {
defer func() {
_ = conn.Close()
}()
data := make([]byte, 4)
if _, err := conn.Read(data); err != nil {
return
}
})
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-V")
if err != nil {
return err
}
return expectResultsCount(results, 2)
}
================================================
FILE: cmd/integration-test/template-dir.go
================================================
package main
import (
"os"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
"github.com/projectdiscovery/utils/errkit"
)
var templatesDirTestCases = []TestCaseInfo{
{Path: "protocols/dns/cname-fingerprint.yaml", TestCase: &templateDirWithTargetTest{}},
}
type templateDirWithTargetTest struct{}
// Execute executes a test case and returns an error if occurred
func (h *templateDirWithTargetTest) Execute(filePath string) error {
tempdir, err := os.MkdirTemp("", "nuclei-update-dir-*")
if err != nil {
return errkit.Wrap(err, "failed to create temp dir")
}
defer func() {
_ = os.RemoveAll(tempdir)
}()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "8x8exch02.8x8.com", debug, "-ud", tempdir)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
================================================
FILE: cmd/integration-test/template-path.go
================================================
package main
import (
"fmt"
"strings"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
)
func getTemplatePath() string {
return config.DefaultConfig.TemplatesDirectory
}
var templatesPathTestCases = []TestCaseInfo{
//template folder path issue
{Path: "protocols/http/get.yaml", TestCase: &folderPathTemplateTest{}},
//cwd
{Path: "./dns/detect-dangling-cname.yaml", TestCase: &cwdTemplateTest{}},
//relative path
{Path: "dns/dns-saas-service-detection.yaml", TestCase: &relativePathTemplateTest{}},
//absolute path
{Path: fmt.Sprintf("%v/dns/dns-saas-service-detection.yaml", getTemplatePath()), TestCase: &absolutePathTemplateTest{}},
}
type cwdTemplateTest struct{}
// Execute executes a test case and returns an error if occurred
func (h *cwdTemplateTest) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "8x8exch02.8x8.com", debug, "-ms")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type relativePathTemplateTest struct{}
// Execute executes a test case and returns an error if occurred
func (h *relativePathTemplateTest) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "8x8exch02.8x8.com", debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type absolutePathTemplateTest struct{}
// Execute executes a test case and returns an error if occurred
func (h *absolutePathTemplateTest) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "8x8exch02.8x8.com", debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type folderPathTemplateTest struct{}
// Execute executes a test case and returns an error if occurred
func (h *folderPathTemplateTest) Execute(filePath string) error {
results, err := testutils.RunNucleiBinaryAndGetCombinedOutput(debug, []string{"-t", filePath, "-target", "http://example.com"})
if err != nil {
return err
}
if strings.Contains(results, "installing") {
return fmt.Errorf("couldn't find template path,re-installing")
}
return nil
}
================================================
FILE: cmd/integration-test/templates-dir-env.go
================================================
package main
import (
"os"
"path/filepath"
osutils "github.com/projectdiscovery/utils/os"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
"github.com/projectdiscovery/utils/errkit"
)
// isNotLinux returns true if not running on Linux (used to skip tests on non-Linux OS)
var isNotLinux = func() bool { return !osutils.IsLinux() }
var templatesDirEnvTestCases = []TestCaseInfo{
{Path: "protocols/dns/cname-fingerprint.yaml", TestCase: &templatesDirEnvBasicTest{}, DisableOn: isNotLinux},
{Path: "protocols/dns/cname-fingerprint.yaml", TestCase: &templatesDirEnvAbsolutePathTest{}, DisableOn: isNotLinux},
{Path: "protocols/dns/cname-fingerprint.yaml", TestCase: &templatesDirEnvRelativePathTest{}, DisableOn: isNotLinux},
{Path: "protocols/dns/cname-fingerprint.yaml", TestCase: &templatesDirEnvPrecedenceTest{}, DisableOn: isNotLinux},
{Path: "protocols/dns/cname-fingerprint.yaml", TestCase: &templatesDirEnvCustomTemplatesTest{}, DisableOn: isNotLinux},
}
// copyTemplateToDir copies a template file to a destination directory, preserving the directory structure
func copyTemplateToDir(templatePath, destDir string) error {
// Read the template file
templateData, err := os.ReadFile(templatePath)
if err != nil {
return errkit.Wrap(err, "failed to read template file")
}
// Create the destination path preserving directory structure
destPath := filepath.Join(destDir, templatePath)
destDirPath := filepath.Dir(destPath)
// Create the destination directory if it doesn't exist
if err := os.MkdirAll(destDirPath, 0755); err != nil {
return errkit.Wrap(err, "failed to create destination directory")
}
// Write the template file
if err := os.WriteFile(destPath, templateData, 0644); err != nil {
return errkit.Wrap(err, "failed to write template file")
}
return nil
}
// templatesDirEnvBasicTest tests basic functionality of NUCLEI_TEMPLATES_DIR
type templatesDirEnvBasicTest struct{}
// Execute executes a test case and returns an error if occurred
func (h *templatesDirEnvBasicTest) Execute(filePath string) error {
tempdir, err := os.MkdirTemp("", "nuclei-templates-dir-env-*")
if err != nil {
return errkit.Wrap(err, "failed to create temp dir")
}
defer func() {
_ = os.RemoveAll(tempdir)
}()
// Copy template to temp directory
if err := copyTemplateToDir(filePath, tempdir); err != nil {
return err
}
// Set NUCLEI_TEMPLATES_DIR and run nuclei
envVars := []string{"NUCLEI_TEMPLATES_DIR=" + tempdir}
results, err := testutils.RunNucleiBareArgsAndGetResults(debug, envVars, "-t", filePath, "-u", "8x8exch02.8x8.com")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
// templatesDirEnvAbsolutePathTest tests that absolute paths work correctly
type templatesDirEnvAbsolutePathTest struct{}
// Execute executes a test case and returns an error if occurred
func (h *templatesDirEnvAbsolutePathTest) Execute(filePath string) error {
tempdir, err := os.MkdirTemp("", "nuclei-templates-dir-env-abs-*")
if err != nil {
return errkit.Wrap(err, "failed to create temp dir")
}
defer func() {
_ = os.RemoveAll(tempdir)
}()
// Get absolute path
absTempDir, err := filepath.Abs(tempdir)
if err != nil {
return errkit.Wrap(err, "failed to get absolute path")
}
// Copy template to temp directory
if err := copyTemplateToDir(filePath, absTempDir); err != nil {
return err
}
// Set NUCLEI_TEMPLATES_DIR with absolute path and run nuclei
envVars := []string{"NUCLEI_TEMPLATES_DIR=" + absTempDir}
results, err := testutils.RunNucleiBareArgsAndGetResults(debug, envVars, "-t", filePath, "-u", "8x8exch02.8x8.com")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
// templatesDirEnvRelativePathTest tests that relative paths are resolved correctly
type templatesDirEnvRelativePathTest struct{}
// Execute executes a test case and returns an error if occurred
func (h *templatesDirEnvRelativePathTest) Execute(filePath string) error {
// Create temp directory in current working directory
tempdir, err := os.MkdirTemp(".", "nuclei-templates-dir-env-rel-*")
if err != nil {
return errkit.Wrap(err, "failed to create temp dir")
}
defer func() {
_ = os.RemoveAll(tempdir)
}()
// Get relative path (just the directory name)
relPath := filepath.Base(tempdir)
// Copy template to temp directory
if err := copyTemplateToDir(filePath, tempdir); err != nil {
return err
}
// Set NUCLEI_TEMPLATES_DIR with relative path and run nuclei
// Note: The implementation should convert relative paths to absolute
envVars := []string{"NUCLEI_TEMPLATES_DIR=" + relPath}
results, err := testutils.RunNucleiBareArgsAndGetResults(debug, envVars, "-t", filePath, "-u", "8x8exch02.8x8.com")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
// templatesDirEnvPrecedenceTest tests that -ud flag takes precedence over NUCLEI_TEMPLATES_DIR
type templatesDirEnvPrecedenceTest struct{}
// Execute executes a test case and returns an error if occurred
func (h *templatesDirEnvPrecedenceTest) Execute(filePath string) error {
// Create two temp directories
envTempDir, err := os.MkdirTemp("", "nuclei-templates-dir-env-*")
if err != nil {
return errkit.Wrap(err, "failed to create env temp dir")
}
defer func() {
_ = os.RemoveAll(envTempDir)
}()
flagTempDir, err := os.MkdirTemp("", "nuclei-templates-dir-flag-*")
if err != nil {
return errkit.Wrap(err, "failed to create flag temp dir")
}
defer func() {
_ = os.RemoveAll(flagTempDir)
}()
// Copy template to flag temp directory (this should be used due to precedence)
if err := copyTemplateToDir(filePath, flagTempDir); err != nil {
return err
}
// Set NUCLEI_TEMPLATES_DIR to envTempDir (should be ignored due to -ud flag)
envVars := []string{"NUCLEI_TEMPLATES_DIR=" + envTempDir}
// Use -ud flag which should take precedence
results, err := testutils.RunNucleiBareArgsAndGetResults(debug, envVars, "-t", filePath, "-u", "8x8exch02.8x8.com", "-ud", flagTempDir)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
// templatesDirEnvCustomTemplatesTest tests that custom template subdirectories are correctly set
type templatesDirEnvCustomTemplatesTest struct{}
// Execute executes a test case and returns an error if occurred
func (h *templatesDirEnvCustomTemplatesTest) Execute(filePath string) error {
tempdir, err := os.MkdirTemp("", "nuclei-templates-dir-custom-*")
if err != nil {
return errkit.Wrap(err, "failed to create temp dir")
}
defer func() {
_ = os.RemoveAll(tempdir)
}()
// Create custom template subdirectories structure
customDirs := []string{"github", "s3", "gitlab", "azure"}
for _, dir := range customDirs {
customDirPath := filepath.Join(tempdir, dir)
if err := os.MkdirAll(customDirPath, 0755); err != nil {
return errkit.Wrap(err, "failed to create custom template directory")
}
}
// Copy template to temp directory
if err := copyTemplateToDir(filePath, tempdir); err != nil {
return err
}
// Set NUCLEI_TEMPLATES_DIR and run nuclei
envVars := []string{"NUCLEI_TEMPLATES_DIR=" + tempdir}
results, err := testutils.RunNucleiBareArgsAndGetResults(debug, envVars, "-t", filePath, "-u", "8x8exch02.8x8.com")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
================================================
FILE: cmd/integration-test/websocket.go
================================================
package main
import (
"net"
"strings"
"github.com/gobwas/ws/wsutil"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
)
var websocketTestCases = []TestCaseInfo{
{Path: "protocols/websocket/basic.yaml", TestCase: &websocketBasic{}},
{Path: "protocols/websocket/cswsh.yaml", TestCase: &websocketCswsh{}},
{Path: "protocols/websocket/no-cswsh.yaml", TestCase: &websocketNoCswsh{}},
{Path: "protocols/websocket/path.yaml", TestCase: &websocketWithPath{}},
}
type websocketBasic struct{}
// Execute executes a test case and returns an error if occurred
func (h *websocketBasic) Execute(filePath string) error {
connHandler := func(conn net.Conn) {
for {
msg, op, _ := wsutil.ReadClientData(conn)
if string(msg) != "hello" {
return
}
_ = wsutil.WriteServerMessage(conn, op, []byte("world"))
}
}
originValidate := func(origin string) bool {
return true
}
ts := testutils.NewWebsocketServer("", connHandler, originValidate)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, strings.ReplaceAll(ts.URL, "http", "ws"), debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type websocketCswsh struct{}
// Execute executes a test case and returns an error if occurred
func (h *websocketCswsh) Execute(filePath string) error {
connHandler := func(conn net.Conn) {
}
originValidate := func(origin string) bool {
return true
}
ts := testutils.NewWebsocketServer("", connHandler, originValidate)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, strings.ReplaceAll(ts.URL, "http", "ws"), debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type websocketNoCswsh struct{}
// Execute executes a test case and returns an error if occurred
func (h *websocketNoCswsh) Execute(filePath string) error {
connHandler := func(conn net.Conn) {
}
originValidate := func(origin string) bool {
return origin == "https://google.com"
}
ts := testutils.NewWebsocketServer("", connHandler, originValidate)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, strings.ReplaceAll(ts.URL, "http", "ws"), debug)
if err != nil {
return err
}
return expectResultsCount(results, 0)
}
type websocketWithPath struct{}
// Execute executes a test case and returns an error if occurred
func (h *websocketWithPath) Execute(filePath string) error {
connHandler := func(conn net.Conn) {
}
originValidate := func(origin string) bool {
return origin == "https://google.com"
}
ts := testutils.NewWebsocketServer("/test", connHandler, originValidate)
defer ts.Close()
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, strings.ReplaceAll(ts.URL, "http", "ws"), debug)
if err != nil {
return err
}
return expectResultsCount(results, 0)
}
================================================
FILE: cmd/integration-test/whois.go
================================================
package main
import (
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
)
var whoisTestCases = []TestCaseInfo{
{Path: "protocols/whois/basic.yaml", TestCase: &whoisBasic{}},
}
type whoisBasic struct{}
// Execute executes a test case and returns an error if occurred
func (h *whoisBasic) Execute(filePath string) error {
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "https://example.com", debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
================================================
FILE: cmd/integration-test/workflow.go
================================================
package main
import (
"fmt"
"io"
"log"
"net/http"
"net/http/httptest"
"strings"
"github.com/julienschmidt/httprouter"
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
"github.com/projectdiscovery/nuclei/v3/pkg/templates/signer"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils"
sliceutil "github.com/projectdiscovery/utils/slice"
)
var workflowTestcases = []TestCaseInfo{
{Path: "workflow/basic.yaml", TestCase: &workflowBasic{}},
{Path: "workflow/condition-matched.yaml", TestCase: &workflowConditionMatched{}},
{Path: "workflow/condition-unmatched.yaml", TestCase: &workflowConditionUnmatch{}},
{Path: "workflow/matcher-name.yaml", TestCase: &workflowMatcherName{}},
{Path: "workflow/complex-conditions.yaml", TestCase: &workflowComplexConditions{}},
{Path: "workflow/http-value-share-workflow.yaml", TestCase: &workflowHttpKeyValueShare{}},
{Path: "workflow/dns-value-share-workflow.yaml", TestCase: &workflowDnsKeyValueShare{}},
{Path: "workflow/code-value-share-workflow.yaml", TestCase: &workflowCodeKeyValueShare{}, DisableOn: isCodeDisabled}, // isCodeDisabled declared in code.go
{Path: "workflow/multiprotocol-value-share-workflow.yaml", TestCase: &workflowMultiProtocolKeyValueShare{}},
{Path: "workflow/multimatch-value-share-workflow.yaml", TestCase: &workflowMultiMatchKeyValueShare{}},
{Path: "workflow/shared-cookie.yaml", TestCase: &workflowSharedCookies{}},
}
func init() {
// sign code templates (unless they are disabled)
if !isCodeDisabled() {
// allow local file access to load content of file references in template
// in order to sign them for testing purposes
templates.TemplateSignerLFA()
// testCertFile and testKeyFile are declared in code.go
tsigner, err := signer.NewTemplateSignerFromFiles(testCertFile, testKeyFile)
if err != nil {
panic(err)
}
// only the code templates are necessary to be signed
var templatesToSign = []string{
"workflow/code-template-1.yaml",
"workflow/code-template-2.yaml",
}
for _, templatePath := range templatesToSign {
if err := templates.SignTemplate(tsigner, templatePath); err != nil {
log.Fatalf("Could not sign template %v got: %s\n", templatePath, err)
}
}
}
}
type workflowBasic struct{}
// Execute executes a test case and returns an error if occurred
func (h *workflowBasic) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "This is test matcher text")
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 2)
}
type workflowConditionMatched struct{}
// Execute executes a test case and returns an error if occurred
func (h *workflowConditionMatched) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "This is test matcher text")
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type workflowConditionUnmatch struct{}
// Execute executes a test case and returns an error if occurred
func (h *workflowConditionUnmatch) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "This is test matcher text")
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 0)
}
type workflowMatcherName struct{}
// Execute executes a test case and returns an error if occurred
func (h *workflowMatcherName) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "This is test matcher text")
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type workflowComplexConditions struct{}
// Execute executes a test case and returns an error if occurred
func (h *workflowComplexConditions) Execute(filePath string) error {
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "This is test matcher text")
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
for _, result := range results {
if !strings.Contains(result, "test-matcher-3") {
return fmt.Errorf("incorrect result: the \"basic-get-third:test-matcher-3\" and only that should be matched!\nResults:\n\t%s", strings.Join(results, "\n\t"))
}
}
return expectResultsCount(results, 2)
}
type workflowHttpKeyValueShare struct{}
// Execute executes a test case and returns an error if occurred
func (h *workflowHttpKeyValueShare) Execute(filePath string) error {
router := httprouter.New()
router.GET("/path1", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "href=\"test-value\"")
})
router.GET("/path2", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
body, _ := io.ReadAll(r.Body)
_, _ = fmt.Fprintf(w, "%s", body)
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type workflowDnsKeyValueShare struct{}
// Execute executes a test case and returns an error if occurred
func (h *workflowDnsKeyValueShare) Execute(filePath string) error {
results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, "http://scanme.sh", debug)
if err != nil {
return err
}
// no results - ensure that the variable sharing works
return expectResultsCount(results, 1)
}
type workflowCodeKeyValueShare struct{}
// Execute executes a test case and returns an error if occurred
func (h *workflowCodeKeyValueShare) Execute(filePath string) error {
// provide the Certificate File that the code templates are signed with
certEnvVar := signer.CertEnvVarName + "=" + testCertFile
results, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, []string{certEnvVar}, "-workflows", filePath, "-target", "input", "-code")
if err != nil {
return err
}
return expectResultsCount(results, 1)
}
type workflowMultiProtocolKeyValueShare struct{}
// Execute executes a test case and returns an error if occurred
func (h *workflowMultiProtocolKeyValueShare) Execute(filePath string) error {
router := httprouter.New()
// the response of path1 contains a domain that will be extracted and shared with the second template
router.GET("/path1", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "href=\"blog.projectdiscovery.io\"")
})
// path2 responds with the value of the "extracted" query parameter, e.g.: /path2?extracted=blog.projectdiscovery.io => blog.projectdiscovery.io
router.GET("/path2", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "%s", r.URL.Query().Get("extracted"))
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
return expectResultsCount(results, 2)
}
type workflowMultiMatchKeyValueShare struct{}
// Execute executes a test case and returns an error if occurred
func (h *workflowMultiMatchKeyValueShare) Execute(filePath string) error {
var receivedData []string
router := httprouter.New()
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "This is test matcher text")
})
router.GET("/path1", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
_, _ = fmt.Fprintf(w, "href=\"test-value-%s\"", r.URL.Query().Get("v"))
})
router.GET("/path2", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
body, _ := io.ReadAll(r.Body)
receivedData = append(receivedData, string(body))
_, _ = fmt.Fprintf(w, "test-value")
})
ts := httptest.NewServer(router)
defer ts.Close()
results, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)
if err != nil {
return err
}
// Check if we received the data from both request to /path1 and it is not overwritten by the later one.
// They will appear in brackets because of another bug: https://github.com/orgs/projectdiscovery/discussions/3766
if !sliceutil.Contains(receivedData, "[test-value-1]") || !sliceutil.Contains(receivedData, "[test-value-2]") {
return fmt.Errorf(
"incorrect data: did not receive both extracted data from the first request!\nReceived Data:\n\t%s\nResults:\n\t%s",
strings.Join(receivedData, "\n\t"),
strings.Join(results, "\n\t"),
)
}
// The number of expected results is 3: the workflow's Matcher Name based condition check forwards both match, and the other branch with simple subtemplates goes with one
return expectResultsCount(results, 3)
}
type workflowSharedCookies struct{}
// Execute executes a test case and returns an error if occurred
func (h *workflowSharedCookies) Execute(filePath string) error {
handleFunc := func(name string, w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
cookie := &http.Cookie{Name: name, Value: name}
http.SetCookie(w, cookie)
}
var gotCookies []string
router := httprouter.New()
router.GET("/http1", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
handleFunc("http1", w, r, p)
})
router.GET("/http2", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
handleFunc("http2", w, r, p)
})
router.GET("/headless1", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
handleFunc("headless1", w, r, p)
})
router.GET("/http3", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
for _, cookie := range r.Cookies() {
gotCookies = append(gotCookies, cookie.Name)
}
})
ts := httptest.NewServer(router)
defer ts.Close()
_, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug, "-headless")
if err != nil {
return err
}
return expectResultsCount(gotCookies, 3)
}
================================================
FILE: cmd/memogen/function.tpl
================================================
// Warning - This is generated code
package {{.SourcePackage}}
import (
"github.com/projectdiscovery/utils/memoize"
{{range .Imports}}
{{.Name}} {{.Path}}
{{end}}
)
{{range .Functions}}
{{ .SignatureWithPrefix "memoized" }} {
hash := "{{ .Name }}" {{range .Params}} + ":" + fmt.Sprint({{.Name}}) {{end}}
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return {{.Name}}({{.ParamsNames}})
})
if err != nil {
return {{.ResultFirstFieldDefaultValue}}, err
}
if value, ok := v.({{.ResultFirstFieldType}}); ok {
return value, nil
}
return {{.ResultFirstFieldDefaultValue}}, errors.New("could not convert cached result")
}
{{end}}
================================================
FILE: cmd/memogen/memogen.go
================================================
// this small cli tool is specific for those functions with arbitrary parameters and with result-error tuple as return values
// func(x,y) => result, error
// it works by creating a new memoized version of the functions in the same path as memo.original.file.go
// some parts are specific for nuclei and hardcoded within the template
package main
import (
"flag"
"io/fs"
"log"
"os"
"path/filepath"
"github.com/projectdiscovery/utils/memoize"
stringsutil "github.com/projectdiscovery/utils/strings"
)
var (
srcPath = flag.String("src", "", "nuclei source path")
tplPath = flag.String("tpl", "function.tpl", "template path")
tplSrc []byte
)
func main() {
flag.Parse()
var err error
tplSrc, err = os.ReadFile(*tplPath)
if err != nil {
log.Fatal(err)
}
err = filepath.Walk(*srcPath, walk)
if err != nil {
log.Fatal(err)
}
}
func walk(path string, info fs.FileInfo, err error) error {
if info.IsDir() {
return nil
}
if err != nil {
return err
}
ext := filepath.Ext(path)
base := filepath.Base(path)
if !stringsutil.EqualFoldAny(ext, ".go") {
return nil
}
basePath := filepath.Dir(path)
outPath := filepath.Join(basePath, "memo."+base)
// filename := filepath.Base(path)
data, err := os.ReadFile(path)
if err != nil {
return err
}
if !stringsutil.ContainsAnyI(string(data), "@memo") {
return nil
}
log.Println("processing:", path)
out, err := memoize.Src(string(tplSrc), path, data, "")
if err != nil {
return err
}
if err := os.WriteFile(outPath, out, os.ModePerm); err != nil {
return err
}
return nil
}
================================================
FILE: cmd/nuclei/issue-tracker-config.yaml
================================================
# global allow/deny list. this will affect both exporters
# as well as issue trackers. you can filter trackers with
# a tracker level filter on top of an exporter by setting
# allow-list/deny-list per tracker.
#
#allow-list:
# severity: high, critical
#deny-list:
# severity: low
#
# GitHub contains configuration options for GitHub issue tracker
#github:
# # base-url is the optional self-hosted GitHub application url
# base-url: https://localhost:8443/github
# # username is the username of the GitHub user
# username: test-username
# # owner is the owner name of the repository for issues
# owner: test-owner
# # token is the token for GitHub account
# token: test-token
# # project-name is the name of the repository
# project-name: test-project
# # issue-label is the label of the created issue type
# issue-label: bug
# # allow-list sets a tracker level filter to only create issues for templates with
# # these severity labels or tags (does not affect exporters. set those globally)
# allow-list:
# severity: high, critical
# tags: network
# # deny-list sets a tracker level filter to never create issues for templates with
# # these severity labels or tags (does not affect exporters. set those globally)
# deny-list:
# severity: low
# # duplicate-issue-check flag to enable duplicate tracking issue check.
# duplicate-issue-check: false
#
# GitLab contains configuration options for gitlab issue tracker
#gitlab:
# # base-url is the optional self-hosted GitLab application url
# base-url: https://localhost:8443/gitlab
# # username is the username of the GitLab user
# username: test-username
# # token is the token for GitLab account
# token: test-token
# # project-name is the name/id of the project(repository)
# project-name: "1234"
# # issue-label is the label of the created issue type
# issue-label: bug
# # allow-list sets a tracker level filter to only create issues for templates with
# # these severity labels or tags (does not affect exporters. set those globally)
# allow-list:
# severity: high, critical
# tags: network
# # deny-list sets a tracker level filter to never create issues for templates with
# # these severity labels or tags (does not affect exporters. set those globally)
# deny-list:
# severity: low
# # duplicate-issue-check (optional) flag to enable duplicate tracking issue check
# duplicate-issue-check: false
# # duplicate-issue-page-size (optional) controls how many issues to fetch per page when searching for duplicates
# duplicate-issue-page-size: 100
# # duplicate-issue-max-pages (optional) limits how many pages to fetch when searching for duplicates (0 = no limit)
# duplicate-issue-max-pages: 0
#
# Gitea contains configuration options for a gitea issue tracker
#gitea:
# # base-url is the optional self-hosted Gitea application url (defaults to https://gitea.com)
# base-url: https://localhost:8443/
# # token is the token for a Gitea account to use
# token: test-token
# # project-owner is the owner (user or org) of the repository
# project-owner: "1234"
# # project-name is the name of the repository
# project-name: "1234"
# # issue-label is a custom label to add to created issues
# issue-label: bug
# # severity-as-label (optional) adds the severity as a label of the created issue
# severity-as-label: true
# # allow-list sets a tracker level filter to only create issues for templates with
# # these severity labels or tags (does not affect exporters. set those globally)
# allow-list:
# severity: high, critical
# tags: network
# # deny-list sets a tracker level filter to never create issues for templates with
# # these severity labels or tags (does not affect exporters. set those globally)
# deny-list:
# severity: low
# # duplicate-issue-check (optional) flag to enable duplicate tracking issue check
# duplicate-issue-check: false
# # duplicate-issue-page-size (optional) controls how many issues to fetch per page when searching for duplicates
# duplicate-issue-page-size: 100
# # duplicate-issue-max-pages (optional) limits how many pages to fetch when searching for duplicates (0 = no limit)
# duplicate-issue-max-pages: 0
#
# Jira contains configuration options for Jira issue tracker
#jira:
# # cloud is the boolean which tells if Jira instance is running in the cloud or on-prem version is used
# cloud: true
# # update-existing is the boolean which tells if the existing, opened issue should be updated or new one should be created
# update-existing: false
# # URL is the jira application url
# url: https://localhost/jira
# # site-url is the browsable URL for the Jira instance (optional)
# # If not provided, issue.Self will be used. Useful for OAuth where issue.Self contains api.atlassian.com
# site-url: https://your-company.atlassian.net
# # account-id is the account-id of the Jira user or username in case of on-prem Jira
# account-id: test-account-id
# # email is the email of the user for Jira instance
# email: test@test.com
# # token is the token for Jira instance or password in case of on-prem Jira
# token: test-token
# # project-name is the name of the project.
# project-name: test-project-name
# # issue-type is the name of the created issue type (case sensitive)
# issue-type: Bug
# # SeverityAsLabel (optional) sends the severity as the label of the created issue
# # User custom fields for Jira Cloud instead
# severity-as-label: true
# # allow-list sets a tracker level filter to only create issues for templates with
# # these severity labels or tags (does not affect exporters. set those globally)
# allow-list:
# severity: high, critical
# tags: network
# # deny-list sets a tracker level filter to never create issues for templates with
# # these severity labels or tags (does not affect exporters. set those globally)
# deny-list:
# severity: low
# # Whatever your final status is that you want to use as a closed ticket - Closed, Done, Remediated, etc
# # When checking for duplicates, the JQL query will filter out status's that match this.
# # If it finds a match _and_ the ticket does have this status, a new one will be created.
# status-not: Closed
# # Customfield supports name, id and freeform. name and id are to be used when the custom field is a dropdown.
# # freeform can be used if the custom field is just a text entry
# # Variables can be used to pull various pieces of data from the finding itself.
# # Supported variables: $CVSSMetrics, $CVEID, $CWEID, $Host, $Severity, $CVSSScore, $Name
# custom-fields:
# customfield_00001:
# name: "Nuclei"
# customfield_00002:
# freeform: $CVSSMetrics
# customfield_00003:
# freeform: $CVSSScore
# elasticsearch contains configuration options for elasticsearch exporter
#elasticsearch:
# # IP for elasticsearch instance
# ip: 127.0.0.1
# # Port is the port of elasticsearch instance
# port: 9200
# # IndexName is the name of the elasticsearch index
# index-name: nuclei
# # SSL enables ssl for elasticsearch connection
# ssl: false
# # SSLVerification disables SSL verification for elasticsearch
# ssl-verification: false
# # Username for the elasticsearch instance
# username: test
# # Password is the password for elasticsearch instance
# password: test
#linear:
# # api-key is the API key for the linear account
# api-key: ""
# # allow-list sets a tracker level filter to only create issues for templates with
# # these severity labels or tags (does not affect exporters. set those globally)
# deny-list:
# severity: critical
# # deny-list sets a tracker level filter to never create issues for templates with
# # these severity labels or tags (does not affect exporters. set those globally)
# deny-list:
# severity: low
# # team-id is the ID of the team in Linear
# team-id: ""
# # project-id is the ID of the project in Linear
# project-id: ""
# # duplicate-issue-check flag to enable duplicate tracking issue check
# duplicate-issue-check: false
# # open-state-id is the ID of the open state in Linear
# open-state-id: ""
#mongodb:
# # the connection string to the MongoDB database
# # (e.g., mongodb://root:example@localhost:27017/nuclei?ssl=false&authSource=admin)
# connection-string: ""
# # the name of the collection to store the issues
# collection-name: ""
# # excludes the Request and Response from the results (helps with filesize)
# omit-raw: false
# # determines the number of results to be kept in memory before writing it to the database or 0 to
# # persist all in memory and write all results at the end (default)
# batch-size: 0
================================================
FILE: cmd/nuclei/main.go
================================================
package main
import (
"bufio"
"fmt"
"io/fs"
"os"
"os/signal"
"path/filepath"
"runtime"
"runtime/pprof"
"runtime/trace"
"strings"
"time"
"github.com/projectdiscovery/gologger"
_pdcp "github.com/projectdiscovery/nuclei/v3/internal/pdcp"
"github.com/projectdiscovery/utils/auth/pdcp"
"github.com/projectdiscovery/utils/env"
_ "github.com/projectdiscovery/utils/pprof"
stringsutil "github.com/projectdiscovery/utils/strings"
"github.com/rs/xid"
"gopkg.in/yaml.v2"
"github.com/projectdiscovery/goflags"
"github.com/projectdiscovery/gologger/levels"
"github.com/projectdiscovery/interactsh/pkg/client"
"github.com/projectdiscovery/nuclei/v3/internal/runner"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
"github.com/projectdiscovery/nuclei/v3/pkg/installer"
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/uncover"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http"
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
"github.com/projectdiscovery/nuclei/v3/pkg/templates/extensions"
"github.com/projectdiscovery/nuclei/v3/pkg/templates/signer"
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/nuclei/v3/pkg/types/scanstrategy"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/monitor"
"github.com/projectdiscovery/utils/errkit"
fileutil "github.com/projectdiscovery/utils/file"
unitutils "github.com/projectdiscovery/utils/unit"
updateutils "github.com/projectdiscovery/utils/update"
)
var (
cfgFile string
templateProfile string
memProfile string // optional profile file path
options = &types.Options{}
)
func main() {
options.Logger = gologger.DefaultLogger
// enables CLI specific configs mostly interactive behavior
config.CurrentAppMode = config.AppModeCLI
if err := runner.ConfigureOptions(); err != nil {
options.Logger.Fatal().Msgf("Could not initialize options: %s\n", err)
}
_ = readConfig()
if options.ListDslSignatures {
options.Logger.Info().Msgf("The available custom DSL functions are:")
fmt.Println(dsl.GetPrintableDslFunctionSignatures(options.NoColor))
return
}
// sign the templates if requested - only glob syntax is supported
if options.SignTemplates {
// use parsed options when initializing signer instead of default options
templates.UseOptionsForSigner(options)
tsigner, err := signer.NewTemplateSigner(nil, nil) // will read from env , config or generate new keys
if err != nil {
options.Logger.Fatal().Msgf("couldn't initialize signer crypto engine: %s\n", err)
}
successCounter := 0
errorCounter := 0
for _, item := range options.Templates {
err := filepath.WalkDir(item, func(iterItem string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() || !strings.HasSuffix(iterItem, extensions.YAML) {
// skip non yaml files
return nil
}
if err := templates.SignTemplate(tsigner, iterItem); err != nil {
if err != templates.ErrNotATemplate {
// skip warnings and errors as given items are not templates
errorCounter++
options.Logger.Error().Msgf("could not sign '%s': %s\n", iterItem, err)
}
} else {
successCounter++
}
return nil
})
if err != nil {
options.Logger.Error().Msgf("%s\n", err)
}
}
options.Logger.Info().Msgf("All templates signatures were elaborated success=%d failed=%d\n", successCounter, errorCounter)
return
}
// Profiling & tracing related code
if memProfile != "" {
memProfile = strings.TrimSuffix(memProfile, filepath.Ext(memProfile))
createProfileFile := func(ext, profileType string) *os.File {
f, err := os.Create(memProfile + ext)
if err != nil {
options.Logger.Fatal().Msgf("profile: could not create %s profile %q file: %v", profileType, f.Name(), err)
}
return f
}
memProfileFile := createProfileFile(".mem", "memory")
cpuProfileFile := createProfileFile(".cpu", "CPU")
traceFile := createProfileFile(".trace", "trace")
oldMemProfileRate := runtime.MemProfileRate
runtime.MemProfileRate = 4096
// Start tracing
if err := trace.Start(traceFile); err != nil {
options.Logger.Fatal().Msgf("profile: could not start trace: %v", err)
}
// Start CPU profiling
if err := pprof.StartCPUProfile(cpuProfileFile); err != nil {
options.Logger.Fatal().Msgf("profile: could not start CPU profile: %v", err)
}
defer func() {
// Start heap memory snapshot
if err := pprof.WriteHeapProfile(memProfileFile); err != nil {
options.Logger.Fatal().Msgf("profile: could not write memory profile: %v", err)
}
pprof.StopCPUProfile()
_ = memProfileFile.Close()
_ = traceFile.Close()
trace.Stop()
runtime.MemProfileRate = oldMemProfileRate
options.Logger.Info().Msgf("CPU profile saved at %q", cpuProfileFile.Name())
options.Logger.Info().Msgf("Memory usage snapshot saved at %q", memProfileFile.Name())
options.Logger.Info().Msgf("Traced at %q", traceFile.Name())
}()
}
options.ExecutionId = xid.New().String()
runner.ParseOptions(options)
if options.ScanUploadFile != "" {
if err := runner.UploadResultsToCloud(options); err != nil {
options.Logger.Fatal().Msgf("could not upload scan results to cloud dashboard: %s\n", err)
}
return
}
nucleiRunner, err := runner.New(options)
if err != nil {
options.Logger.Fatal().Msgf("Could not create runner: %s\n", err)
}
if nucleiRunner == nil {
return
}
if options.HangMonitor {
stackMonitor := monitor.NewStackMonitor()
cancel := stackMonitor.Start(10 * time.Second)
defer cancel()
stackMonitor.RegisterCallback(func(dumpID string) error {
resumeFileName := fmt.Sprintf("crash-resume-file-%s.dump", dumpID)
if options.EnableCloudUpload {
options.Logger.Info().Msgf("Uploading scan results to cloud...")
}
nucleiRunner.Close()
options.Logger.Info().Msgf("Creating resume file: %s\n", resumeFileName)
err := nucleiRunner.SaveResumeConfig(resumeFileName)
if err != nil {
return errkit.Wrap(err, "couldn't create crash resume file")
}
return nil
})
}
// Setup filename for graceful exits
resumeFileName := types.DefaultResumeFilePath()
if options.Resume != "" {
resumeFileName = options.Resume
}
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
<-c
options.Logger.Info().Msgf("CTRL+C pressed: Exiting\n")
if options.DASTServer {
nucleiRunner.Close()
os.Exit(1)
}
options.Logger.Info().Msgf("Attempting graceful shutdown...")
if options.EnableCloudUpload {
options.Logger.Info().Msgf("Uploading scan results to cloud...")
}
nucleiRunner.Close()
if options.ShouldSaveResume() {
options.Logger.Info().Msgf("Creating resume file: %s\n", resumeFileName)
err := nucleiRunner.SaveResumeConfig(resumeFileName)
if err != nil {
options.Logger.Error().Msgf("Couldn't create resume file: %s\n", err)
}
}
os.Exit(1)
}()
if err := nucleiRunner.RunEnumeration(); err != nil {
if options.Validate {
options.Logger.Fatal().Msgf("Could not validate templates: %s\n", err)
} else {
options.Logger.Fatal().Msgf("Could not run nuclei: %s\n", err)
}
}
nucleiRunner.Close()
// on successful execution remove the resume file in case it exists
if fileutil.FileExists(resumeFileName) {
_ = os.Remove(resumeFileName)
}
}
func readConfig() *goflags.FlagSet {
// when true updates nuclei binary to latest version
var updateNucleiBinary bool
var pdcpauth string
var fuzzFlag bool
flagSet := goflags.NewFlagSet()
flagSet.CaseSensitive = true
flagSet.SetDescription(`Nuclei is a fast, template based vulnerability scanner focusing
on extensive configurability, massive extensibility and ease of use.`)
/* TODO Important: The defined default values, especially for slice/array types are NOT DEFAULT VALUES, but rather implicit values to which the user input is appended.
This can be very confusing and should be addressed
*/
flagSet.CreateGroup("input", "Target",
flagSet.StringSliceVarP(&options.Targets, "target", "u", nil, "target URLs/hosts to scan", goflags.CommaSeparatedStringSliceOptions),
flagSet.StringVarP(&options.TargetsFilePath, "list", "l", "", "path to file containing a list of target URLs/hosts to scan (one per line)"),
flagSet.StringSliceVarP(&options.ExcludeTargets, "exclude-hosts", "eh", nil, "hosts to exclude to scan from the input list (ip, cidr, hostname)", goflags.FileCommaSeparatedStringSliceOptions),
flagSet.StringVar(&options.Resume, "resume", "", "resume scan from and save to specified file (clustering will be disabled)"),
flagSet.BoolVarP(&options.ScanAllIPs, "scan-all-ips", "sa", false, "scan all the IP's associated with dns record"),
flagSet.StringSliceVarP(&options.IPVersion, "ip-version", "iv", nil, "IP version to scan of hostname (4,6) - (default 4)", goflags.CommaSeparatedStringSliceOptions),
)
flagSet.CreateGroup("target-format", "Target-Format",
flagSet.StringVarP(&options.InputFileMode, "input-mode", "im", "list", fmt.Sprintf("mode of input file (%v)", provider.SupportedInputFormats())),
flagSet.BoolVarP(&options.FormatUseRequiredOnly, "required-only", "ro", false, "use only required fields in input format when generating requests"),
flagSet.BoolVarP(&options.SkipFormatValidation, "skip-format-validation", "sfv", false, "skip format validation (like missing vars) when parsing input file"),
flagSet.BoolVarP(&options.VarsTextTemplating, "vars-text-templating", "vtt", false, "enable text templating for vars in input file (only for yaml input mode)"),
flagSet.StringSliceVarP(&options.VarsFilePaths, "var-file-paths", "vfp", nil, "list of yaml file contained vars to inject into yaml input", goflags.CommaSeparatedStringSliceOptions),
)
flagSet.CreateGroup("templates", "Templates",
flagSet.BoolVarP(&options.NewTemplates, "new-templates", "nt", false, "run only new templates added in latest nuclei-templates release"),
flagSet.StringSliceVarP(&options.NewTemplatesWithVersion, "new-templates-version", "ntv", nil, "run new templates added in specific version", goflags.CommaSeparatedStringSliceOptions),
flagSet.BoolVarP(&options.AutomaticScan, "automatic-scan", "as", false, "automatic web scan using wappalyzer technology detection to tags mapping"),
flagSet.StringSliceVarP(&options.Templates, "templates", "t", nil, "list of template or template directory to run (comma-separated, file)", goflags.FileCommaSeparatedStringSliceOptions),
flagSet.StringSliceVarP(&options.TemplateURLs, "template-url", "turl", nil, "template url or list containing template urls to run (comma-separated, file)", goflags.FileCommaSeparatedStringSliceOptions),
flagSet.StringVarP(&options.AITemplatePrompt, "prompt", "ai", "", "generate and run template using ai prompt"),
flagSet.StringSliceVarP(&options.Workflows, "workflows", "w", nil, "list of workflow or workflow directory to run (comma-separated, file)", goflags.FileCommaSeparatedStringSliceOptions),
flagSet.StringSliceVarP(&options.WorkflowURLs, "workflow-url", "wurl", nil, "workflow url or list containing workflow urls to run (comma-separated, file)", goflags.FileCommaSeparatedStringSliceOptions),
flagSet.BoolVar(&options.Validate, "validate", false, "validate the passed templates to nuclei"),
flagSet.BoolVarP(&options.NoStrictSyntax, "no-strict-syntax", "nss", false, "disable strict syntax check on templates"),
flagSet.BoolVarP(&options.TemplateDisplay, "template-display", "td", false, "displays the templates content"),
flagSet.BoolVar(&options.TemplateList, "tl", false, "list all templates matching current filters"),
flagSet.BoolVar(&options.TagList, "tgl", false, "list all available tags"),
flagSet.StringSliceVarConfigOnly(&options.RemoteTemplateDomainList, "remote-template-domain", []string{"cloud.projectdiscovery.io"}, "allowed domain list to load remote templates from"),
flagSet.BoolVar(&options.SignTemplates, "sign", false, "signs the templates with the private key defined in NUCLEI_SIGNATURE_PRIVATE_KEY env variable"),
flagSet.BoolVar(&options.EnableCodeTemplates, "code", false, "enable loading code protocol-based templates"),
flagSet.BoolVarP(&options.DisableUnsignedTemplates, "disable-unsigned-templates", "dut", false, "disable running unsigned templates or templates with mismatched signature"),
flagSet.BoolVarP(&options.EnableSelfContainedTemplates, "enable-self-contained", "esc", false, "enable loading self-contained templates"),
flagSet.BoolVarP(&options.EnableGlobalMatchersTemplates, "enable-global-matchers", "egm", false, "enable loading global matchers templates"),
flagSet.BoolVar(&options.EnableFileTemplates, "file", false, "enable loading file templates"),
)
flagSet.CreateGroup("filters", "Filtering",
flagSet.StringSliceVarP(&options.Authors, "author", "a", nil, "templates to run based on authors (comma-separated, file)", goflags.FileNormalizedStringSliceOptions),
flagSet.StringSliceVar(&options.Tags, "tags", nil, "templates to run based on tags (comma-separated, file)", goflags.FileNormalizedStringSliceOptions),
flagSet.StringSliceVarP(&options.ExcludeTags, "exclude-tags", "etags", nil, "templates to exclude based on tags (comma-separated, file)", goflags.FileNormalizedStringSliceOptions),
flagSet.StringSliceVarP(&options.IncludeTags, "include-tags", "itags", nil, "tags to be executed even if they are excluded either by default or configuration", goflags.FileNormalizedStringSliceOptions), // TODO show default deny list
flagSet.StringSliceVarP(&options.IncludeIds, "template-id", "id", nil, "templates to run based on template ids (comma-separated, file, allow-wildcard)", goflags.FileNormalizedStringSliceOptions),
flagSet.StringSliceVarP(&options.ExcludeIds, "exclude-id", "eid", nil, "templates to exclude based on template ids (comma-separated, file)", goflags.FileNormalizedStringSliceOptions),
flagSet.StringSliceVarP(&options.IncludeTemplates, "include-templates", "it", nil, "path to template file or directory to be executed even if they are excluded either by default or configuration", goflags.FileCommaSeparatedStringSliceOptions),
flagSet.StringSliceVarP(&options.ExcludedTemplates, "exclude-templates", "et", nil, "path to template file or directory to exclude (comma-separated, file)", goflags.FileCommaSeparatedStringSliceOptions),
flagSet.StringSliceVarP(&options.ExcludeMatchers, "exclude-matchers", "em", nil, "template matchers to exclude in result", goflags.FileCommaSeparatedStringSliceOptions),
flagSet.VarP(&options.Severities, "severity", "s", fmt.Sprintf("templates to run based on severity. Possible values: %s", severity.GetSupportedSeverities().String())),
flagSet.VarP(&options.ExcludeSeverities, "exclude-severity", "es", fmt.Sprintf("templates to exclude based on severity. Possible values: %s", severity.GetSupportedSeverities().String())),
flagSet.VarP(&options.Protocols, "type", "pt", fmt.Sprintf("templates to run based on protocol type. Possible values: %s", templateTypes.GetSupportedProtocolTypes())),
flagSet.VarP(&options.ExcludeProtocols, "exclude-type", "ept", fmt.Sprintf("templates to exclude based on protocol type. Possible values: %s", templateTypes.GetSupportedProtocolTypes())),
flagSet.StringSliceVarP(&options.IncludeConditions, "template-condition", "tc", nil, "templates to run based on expression condition", goflags.StringSliceOptions),
)
flagSet.CreateGroup("output", "Output",
flagSet.StringVarP(&options.Output, "output", "o", "", "output file to write found issues/vulnerabilities"),
flagSet.BoolVarP(&options.StoreResponse, "store-resp", "sresp", false, "store all request/response passed through nuclei to output directory"),
flagSet.StringVarP(&options.StoreResponseDir, "store-resp-dir", "srd", runner.DefaultDumpTrafficOutputFolder, "store all request/response passed through nuclei to custom directory"),
flagSet.BoolVar(&options.Silent, "silent", false, "display findings only"),
flagSet.BoolVarP(&options.NoColor, "no-color", "nc", false, "disable output content coloring (ANSI escape codes)"),
flagSet.BoolVarP(&options.JSONL, "jsonl", "j", false, "write output in JSONL(ines) format"),
flagSet.BoolVarP(&options.JSONRequests, "include-rr", "irr", true, "include request/response pairs in the JSON, JSONL, and Markdown outputs (for findings only) [DEPRECATED use `-omit-raw`]"),
flagSet.BoolVarP(&options.OmitRawRequests, "omit-raw", "or", false, "omit request/response pairs in the JSON, JSONL, Markdown, and PDF outputs (for findings only)"),
flagSet.BoolVarP(&options.OmitTemplate, "omit-template", "ot", false, "omit encoded template in the JSON, JSONL output"),
flagSet.BoolVarP(&options.NoMeta, "no-meta", "nm", false, "disable printing result metadata in cli output"),
flagSet.BoolVarP(&options.Timestamp, "timestamp", "ts", false, "enables printing timestamp in cli output"),
flagSet.StringVarP(&options.ReportingDB, "report-db", "rdb", "", "nuclei reporting database (always use this to persist report data)"),
flagSet.BoolVarP(&options.MatcherStatus, "matcher-status", "ms", false, "display match failure status"),
flagSet.StringVarP(&options.MarkdownExportDirectory, "markdown-export", "me", "", "directory to export results in markdown format"),
flagSet.StringVarP(&options.SarifExport, "sarif-export", "se", "", "file to export results in SARIF format"),
flagSet.StringVarP(&options.JSONExport, "json-export", "je", "", "file to export results in JSON format"),
flagSet.StringVarP(&options.JSONLExport, "jsonl-export", "jle", "", "file to export results in JSONL(ine) format"),
flagSet.StringVarP(&options.PDFExport, "pdf-export", "pe", "", "file to export results in PDF format"),
flagSet.StringSliceVarP(&options.Redact, "redact", "rd", nil, "redact given list of keys from query parameter, request header and body", goflags.CommaSeparatedStringSliceOptions),
)
flagSet.CreateGroup("configs", "Configurations",
flagSet.StringVar(&cfgFile, "config", "", "path to the nuclei configuration file"),
flagSet.StringVarP(&templateProfile, "profile", "tp", "", "template profile config file to run"),
flagSet.BoolVarP(&options.ListTemplateProfiles, "profile-list", "tpl", false, "list community template profiles"),
flagSet.BoolVarP(&options.FollowRedirects, "follow-redirects", "fr", false, "enable following redirects for http templates"),
flagSet.BoolVarP(&options.FollowHostRedirects, "follow-host-redirects", "fhr", false, "follow redirects on the same host"),
flagSet.IntVarP(&options.MaxRedirects, "max-redirects", "mr", 10, "max number of redirects to follow for http templates"),
flagSet.BoolVarP(&options.DisableRedirects, "disable-redirects", "dr", false, "disable redirects for http templates"),
flagSet.StringVarP(&options.ReportingConfig, "report-config", "rc", "", "nuclei reporting module configuration file"), // TODO merge into the config file or rename to issue-tracking
flagSet.StringSliceVarP(&options.CustomHeaders, "header", "H", nil, "custom header/cookie to include in all http request in header:value format (cli, file)", goflags.FileStringSliceOptions),
flagSet.RuntimeMapVarP(&options.Vars, "var", "V", nil, "custom vars in key=value format"),
flagSet.StringVarP(&options.ResolversFile, "resolvers", "r", "", "file containing resolver list for nuclei"),
flagSet.BoolVarP(&options.SystemResolvers, "system-resolvers", "sr", false, "use system DNS resolving as error fallback"),
flagSet.BoolVarP(&options.DisableClustering, "disable-clustering", "dc", false, "disable clustering of requests"),
flagSet.BoolVar(&options.OfflineHTTP, "passive", false, "enable passive HTTP response processing mode"),
flagSet.BoolVarP(&options.ForceAttemptHTTP2, "force-http2", "fh2", false, "force http2 connection on requests"),
flagSet.BoolVarP(&options.EnvironmentVariables, "env-vars", "ev", false, "enable environment variables to be used in template"),
flagSet.StringVarP(&options.ClientCertFile, "client-cert", "cc", "", "client certificate file (PEM-encoded) used for authenticating against scanned hosts"),
flagSet.StringVarP(&options.ClientKeyFile, "client-key", "ck", "", "client key file (PEM-encoded) used for authenticating against scanned hosts"),
flagSet.StringVarP(&options.ClientCAFile, "client-ca", "ca", "", "client certificate authority file (PEM-encoded) used for authenticating against scanned hosts"),
flagSet.BoolVarP(&options.ShowMatchLine, "show-match-line", "sml", false, "show match lines for file templates, works with extractors only"),
flagSet.BoolVar(&options.ZTLS, "ztls", false, "use ztls library with autofallback to standard one for tls13 [Deprecated] autofallback to ztls is enabled by default"), //nolint:all
flagSet.StringVar(&options.SNI, "sni", "", "tls sni hostname to use (default: input domain name)"),
flagSet.DurationVarP(&options.DialerKeepAlive, "dialer-keep-alive", "dka", 0, "keep-alive duration for network requests."),
flagSet.BoolVarP(&options.AllowLocalFileAccess, "allow-local-file-access", "lfa", false, "allows file (payload) access anywhere on the system"),
flagSet.BoolVarP(&options.RestrictLocalNetworkAccess, "restrict-local-network-access", "lna", false, "blocks connections to the local / private network"),
flagSet.StringVarP(&options.Interface, "interface", "i", "", "network interface to use for network scan"),
flagSet.StringVarP(&options.AttackType, "attack-type", "at", "", "type of payload combinations to perform (batteringram,pitchfork,clusterbomb)"),
flagSet.StringVarP(&options.SourceIP, "source-ip", "sip", "", "source ip address to use for network scan"),
flagSet.IntVarP(&options.ResponseReadSize, "response-size-read", "rsr", 0, "max response size to read in bytes"),
flagSet.IntVarP(&options.ResponseSaveSize, "response-size-save", "rss", unitutils.Mega, "max response size to read in bytes"),
flagSet.CallbackVar(resetCallback, "reset", "reset removes all nuclei configuration and data files (including nuclei-templates)"),
flagSet.BoolVarP(&options.TlsImpersonate, "tls-impersonate", "tlsi", false, "enable experimental client hello (ja3) tls randomization"),
flagSet.StringVarP(&options.HttpApiEndpoint, "http-api-endpoint", "hae", "", "experimental http api endpoint"),
)
flagSet.CreateGroup("interactsh", "interactsh",
flagSet.StringVarP(&options.InteractshURL, "interactsh-server", "iserver", "", fmt.Sprintf("interactsh server url for self-hosted instance (default: %s)", client.DefaultOptions.ServerURL)),
flagSet.StringVarP(&options.InteractshToken, "interactsh-token", "itoken", "", "authentication token for self-hosted interactsh server"),
flagSet.IntVar(&options.InteractionsCacheSize, "interactions-cache-size", 5000, "number of requests to keep in the interactions cache"),
flagSet.IntVar(&options.InteractionsEviction, "interactions-eviction", 60, "number of seconds to wait before evicting requests from cache"),
flagSet.IntVar(&options.InteractionsPollDuration, "interactions-poll-duration", 5, "number of seconds to wait before each interaction poll request"),
flagSet.IntVar(&options.InteractionsCoolDownPeriod, "interactions-cooldown-period", 5, "extra time for interaction polling before exiting"),
flagSet.BoolVarP(&options.NoInteractsh, "no-interactsh", "ni", false, "disable interactsh server for OAST testing, exclude OAST based templates"),
)
flagSet.CreateGroup("fuzzing", "Fuzzing",
flagSet.StringVarP(&options.FuzzingType, "fuzzing-type", "ft", "", "overrides fuzzing type set in template (replace, prefix, postfix, infix)"),
flagSet.StringVarP(&options.FuzzingMode, "fuzzing-mode", "fm", "", "overrides fuzzing mode set in template (multiple, single)"),
flagSet.BoolVar(&fuzzFlag, "fuzz", false, "enable loading fuzzing templates (Deprecated: use -dast instead)"),
flagSet.BoolVar(&options.DAST, "dast", false, "enable / run dast (fuzz) nuclei templates"),
flagSet.BoolVarP(&options.DASTServer, "dast-server", "dts", false, "enable dast server mode (live fuzzing)"),
flagSet.BoolVarP(&options.DASTReport, "dast-report", "dtr", false, "write dast scan report to file"),
flagSet.StringVarP(&options.DASTServerToken, "dast-server-token", "dtst", "", "dast server token (optional)"),
flagSet.StringVarP(&options.DASTServerAddress, "dast-server-address", "dtsa", "localhost:9055", "dast server address"),
flagSet.BoolVarP(&options.DisplayFuzzPoints, "display-fuzz-points", "dfp", false, "display fuzz points in the output for debugging"),
flagSet.IntVar(&options.FuzzParamFrequency, "fuzz-param-frequency", 10, "frequency of uninteresting parameters for fuzzing before skipping"),
flagSet.StringVarP(&options.FuzzAggressionLevel, "fuzz-aggression", "fa", "low", "fuzzing aggression level controls payload count for fuzz (low, medium, high)"),
flagSet.StringSliceVarP(&options.Scope, "fuzz-scope", "cs", nil, "in scope url regex to be followed by fuzzer", goflags.FileCommaSeparatedStringSliceOptions),
flagSet.StringSliceVarP(&options.OutOfScope, "fuzz-out-scope", "cos", nil, "out of scope url regex to be excluded by fuzzer", goflags.FileCommaSeparatedStringSliceOptions),
)
flagSet.CreateGroup("uncover", "Uncover",
flagSet.BoolVarP(&options.Uncover, "uncover", "uc", false, "enable uncover engine"),
flagSet.StringSliceVarP(&options.UncoverQuery, "uncover-query", "uq", nil, "uncover search query", goflags.FileStringSliceOptions),
flagSet.StringSliceVarP(&options.UncoverEngine, "uncover-engine", "ue", nil, fmt.Sprintf("uncover search engine (%s) (default shodan)", uncover.GetUncoverSupportedAgents()), goflags.FileStringSliceOptions),
flagSet.StringVarP(&options.UncoverField, "uncover-field", "uf", "ip:port", "uncover fields to return (ip,port,host)"),
flagSet.IntVarP(&options.UncoverLimit, "uncover-limit", "ul", 100, "uncover results to return"),
flagSet.IntVarP(&options.UncoverRateLimit, "uncover-ratelimit", "ur", 60, "override ratelimit of engines with unknown ratelimit (default 60 req/min)"),
)
flagSet.CreateGroup("rate-limit", "Rate-Limit",
flagSet.IntVarP(&options.RateLimit, "rate-limit", "rl", 150, "maximum number of requests to send per second"),
flagSet.DurationVarP(&options.RateLimitDuration, "rate-limit-duration", "rld", time.Second, "maximum number of requests to send per second"),
flagSet.IntVarP(&options.RateLimitMinute, "rate-limit-minute", "rlm", 0, "maximum number of requests to send per minute (DEPRECATED)"),
flagSet.IntVarP(&options.BulkSize, "bulk-size", "bs", 25, "maximum number of hosts to be analyzed in parallel per template"),
flagSet.IntVarP(&options.TemplateThreads, "concurrency", "c", 25, "maximum number of templates to be executed in parallel"),
flagSet.IntVarP(&options.HeadlessBulkSize, "headless-bulk-size", "hbs", 10, "maximum number of headless hosts to be analyzed in parallel per template"),
flagSet.IntVarP(&options.HeadlessTemplateThreads, "headless-concurrency", "headc", 10, "maximum number of headless templates to be executed in parallel"),
flagSet.IntVarP(&options.JsConcurrency, "js-concurrency", "jsc", 120, "maximum number of javascript runtimes to be executed in parallel"),
flagSet.IntVarP(&options.PayloadConcurrency, "payload-concurrency", "pc", 25, "max payload concurrency for each template"),
flagSet.IntVarP(&options.ProbeConcurrency, "probe-concurrency", "prc", 50, "http probe concurrency with httpx"),
flagSet.IntVarP(&options.TemplateLoadingConcurrency, "template-loading-concurrency", "tlc", types.DefaultTemplateLoadingConcurrency, "maximum number of concurrent template loading operations"),
)
flagSet.CreateGroup("optimization", "Optimizations",
flagSet.IntVar(&options.Timeout, "timeout", 10, "time to wait in seconds before timeout"),
flagSet.IntVar(&options.Retries, "retries", 1, "number of times to retry a failed request"),
flagSet.BoolVarP(&options.LeaveDefaultPorts, "leave-default-ports", "ldp", false, "leave default HTTP/HTTPS ports (eg. host:80,host:443)"),
flagSet.IntVarP(&options.MaxHostError, "max-host-error", "mhe", 30, "max errors for a host before skipping from scan"),
flagSet.StringSliceVarP(&options.TrackError, "track-error", "te", nil, "adds given error to max-host-error watchlist (standard, file)", goflags.FileStringSliceOptions),
flagSet.BoolVarP(&options.NoHostErrors, "no-mhe", "nmhe", false, "disable skipping host from scan based on errors"),
flagSet.BoolVar(&options.Project, "project", false, "use a project folder to avoid sending same request multiple times"),
flagSet.StringVar(&options.ProjectPath, "project-path", os.TempDir(), "set a specific project path"),
flagSet.BoolVarP(&options.StopAtFirstMatch, "stop-at-first-match", "spm", false, "stop processing HTTP requests after the first match (may break template/workflow logic)"),
flagSet.BoolVar(&options.Stream, "stream", false, "stream mode - start elaborating without sorting the input"),
flagSet.EnumVarP(&options.ScanStrategy, "scan-strategy", "ss", goflags.EnumVariable(0), "strategy to use while scanning(auto/host-spray/template-spray)", goflags.AllowdTypes{
scanstrategy.Auto.String(): goflags.EnumVariable(0),
scanstrategy.HostSpray.String(): goflags.EnumVariable(1),
scanstrategy.TemplateSpray.String(): goflags.EnumVariable(2),
}),
flagSet.DurationVarP(&options.InputReadTimeout, "input-read-timeout", "irt", time.Duration(3*time.Minute), "timeout on input read"),
flagSet.BoolVarP(&options.DisableHTTPProbe, "no-httpx", "nh", false, "disable httpx probing for non-url input"),
flagSet.BoolVar(&options.DisableStdin, "no-stdin", false, "disable stdin processing"),
)
flagSet.CreateGroup("headless", "Headless",
flagSet.BoolVar(&options.Headless, "headless", false, "enable templates that require headless browser support (root user on Linux will disable sandbox)"),
flagSet.IntVar(&options.PageTimeout, "page-timeout", 20, "seconds to wait for each page in headless mode"),
flagSet.BoolVarP(&options.ShowBrowser, "show-browser", "sb", false, "show the browser on the screen when running templates with headless mode"),
flagSet.StringSliceVarP(&options.HeadlessOptionalArguments, "headless-options", "ho", nil, "start headless chrome with additional options", goflags.FileCommaSeparatedStringSliceOptions),
flagSet.BoolVarP(&options.UseInstalledChrome, "system-chrome", "sc", false, "use local installed Chrome browser instead of nuclei installed"),
flagSet.StringVarP(&options.CDPEndpoint, "cdp-endpoint", "cdpe", "", "use remote browser via Chrome DevTools Protocol (CDP) endpoint"),
flagSet.BoolVarP(&options.ShowActions, "list-headless-action", "lha", false, "list available headless actions"),
)
flagSet.CreateGroup("debug", "Debug",
flagSet.BoolVar(&options.Debug, "debug", false, "show all requests and responses"),
flagSet.BoolVarP(&options.DebugRequests, "debug-req", "dreq", false, "show all sent requests"),
flagSet.BoolVarP(&options.DebugResponse, "debug-resp", "dresp", false, "show all received responses"),
flagSet.StringSliceVarP(&options.Proxy, "proxy", "p", nil, "list of http/socks5 proxy to use (comma separated or file input)", goflags.FileCommaSeparatedStringSliceOptions),
flagSet.BoolVarP(&options.ProxyInternal, "proxy-internal", "pi", false, "proxy all internal requests"),
flagSet.BoolVarP(&options.ListDslSignatures, "list-dsl-function", "ldf", false, "list all supported DSL function signatures"),
flagSet.StringVarP(&options.TraceLogFile, "trace-log", "tlog", "", "file to write sent requests trace log"),
flagSet.StringVarP(&options.ErrorLogFile, "error-log", "elog", "", "file to write sent requests error log"),
flagSet.CallbackVar(printVersion, "version", "show nuclei version"),
flagSet.BoolVarP(&options.HangMonitor, "hang-monitor", "hm", false, "enable nuclei hang monitoring"),
flagSet.BoolVarP(&options.Verbose, "verbose", "v", false, "show verbose output"),
flagSet.StringVar(&memProfile, "profile-mem", "", "generate memory (heap) profile & trace files"),
flagSet.BoolVar(&options.VerboseVerbose, "vv", false, "display templates loaded for scan"),
flagSet.BoolVarP(&options.ShowVarDump, "show-var-dump", "svd", false, "show variables dump for debugging"),
flagSet.IntVarP(&options.VarDumpLimit, "var-dump-limit", "vdl", 255, "limit the number of characters displayed in var dump"),
flagSet.BoolVarP(&options.EnablePprof, "enable-pprof", "ep", false, "enable pprof debugging server"),
flagSet.CallbackVarP(printTemplateVersion, "templates-version", "tv", "shows the version of the installed nuclei-templates"),
flagSet.BoolVarP(&options.HealthCheck, "health-check", "hc", false, "run diagnostic check up"),
)
flagSet.CreateGroup("update", "Update",
flagSet.BoolVarP(&updateNucleiBinary, "update", "up", false, "update nuclei engine to the latest released version"),
flagSet.BoolVarP(&options.UpdateTemplates, "update-templates", "ut", false, "update nuclei-templates to latest released version"),
flagSet.StringVarP(&options.NewTemplatesDirectory, "update-template-dir", "ud", "", "custom directory to install / update nuclei-templates"),
flagSet.CallbackVarP(disableUpdatesCallback, "disable-update-check", "duc", "disable automatic nuclei/templates update check"),
)
flagSet.CreateGroup("stats", "Statistics",
flagSet.BoolVar(&options.EnableProgressBar, "stats", false, "display statistics about the running scan"),
flagSet.BoolVarP(&options.StatsJSON, "stats-json", "sj", false, "display statistics in JSONL(ines) format"),
flagSet.IntVarP(&options.StatsInterval, "stats-interval", "si", 5, "number of seconds to wait between showing a statistics update"),
flagSet.IntVarP(&options.MetricsPort, "metrics-port", "mp", 9092, "port to expose nuclei metrics on"),
flagSet.BoolVarP(&options.HTTPStats, "http-stats", "hps", false, "enable http status capturing (experimental)"),
)
flagSet.CreateGroup("cloud", "Cloud",
flagSet.DynamicVar(&pdcpauth, "auth", "true", "configure projectdiscovery cloud (pdcp) api key"),
flagSet.StringVarP(&options.TeamID, "team-id", "tid", _pdcp.TeamIDEnv, "upload scan results to given team id (optional)"),
flagSet.BoolVarP(&options.EnableCloudUpload, "cloud-upload", "cup", false, "upload scan results to pdcp dashboard [DEPRECATED use -dashboard]"),
flagSet.StringVarP(&options.ScanID, "scan-id", "sid", "", "upload scan results to existing scan id (optional)"),
flagSet.StringVarP(&options.ScanName, "scan-name", "sname", "", "scan name to set (optional)"),
flagSet.BoolVarP(&options.EnableCloudUpload, "dashboard", "pd", false, "upload / view nuclei results in projectdiscovery cloud (pdcp) UI dashboard"),
flagSet.StringVarP(&options.ScanUploadFile, "dashboard-upload", "pdu", "", "upload / view nuclei results file (jsonl) in projectdiscovery cloud (pdcp) UI dashboard"),
)
flagSet.CreateGroup("Authentication", "Authentication",
flagSet.StringSliceVarP(&options.SecretsFile, "secret-file", "sf", nil, "path to config file containing secrets for nuclei authenticated scan", goflags.CommaSeparatedStringSliceOptions),
flagSet.BoolVarP(&options.PreFetchSecrets, "prefetch-secrets", "ps", false, "prefetch secrets from the secrets file"),
)
flagSet.SetCustomHelpText(`EXAMPLES:
Run nuclei on single host:
$ nuclei -target example.com
Run nuclei with specific template directories:
$ nuclei -target example.com -t http/cves/ -t ssl
Run nuclei against a list of hosts:
$ nuclei -list hosts.txt
Run nuclei with a JSON output:
$ nuclei -target example.com -json-export output.json
Run nuclei with sorted Markdown outputs (with environment variables):
$ MARKDOWN_EXPORT_SORT_MODE=template nuclei -target example.com -markdown-export nuclei_report/
Additional documentation is available at: https://docs.nuclei.sh/getting-started/running
`)
// nuclei has multiple migrations
// ex: resume.cfg moved to platform standard cache dir from config dir
// ex: config.yaml moved to platform standard config dir from linux specific config dir
// and hence it will be attempted in config package during init
goflags.DisableAutoConfigMigration = true
_ = flagSet.Parse()
// when fuzz flag is enabled, set the dast flag to true
if fuzzFlag {
// backwards compatibility for fuzz flag
options.DAST = true
}
// All cloud-based templates depend on both code and self-contained templates.
if options.EnableCodeTemplates {
options.EnableSelfContainedTemplates = true
}
// api key hierarchy: cli flag > env var > .pdcp/credential file
if pdcpauth == "true" {
runner.AuthWithPDCP()
} else if len(pdcpauth) == 36 {
ph := pdcp.PDCPCredHandler{}
if _, err := ph.GetCreds(); err == pdcp.ErrNoCreds {
apiServer := env.GetEnvOrDefault("PDCP_API_SERVER", pdcp.DefaultApiServer)
if validatedCreds, err := ph.ValidateAPIKey(pdcpauth, apiServer, config.BinaryName); err == nil {
_ = ph.SaveCreds(validatedCreds)
}
}
}
// guard cloud services with credentials
if options.AITemplatePrompt != "" {
h := &pdcp.PDCPCredHandler{}
_, err := h.GetCreds()
if err != nil {
options.Logger.Fatal().Msg("To utilize the `-ai` flag, please configure your API key with the `-auth` flag or set the `PDCP_API_KEY` environment variable")
}
}
options.Logger.SetTimestamp(options.Timestamp, levels.LevelDebug)
if options.VerboseVerbose {
// hide release notes if silent mode is enabled
installer.HideReleaseNotes = false
}
if options.Timeout > 30 {
// default github binary/template download timeout is 30 sec
updateutils.DownloadUpdateTimeout = time.Duration(options.Timeout) * time.Second
}
if updateNucleiBinary {
runner.NucleiToolUpdateCallback()
}
if options.LeaveDefaultPorts {
http.LeaveDefaultPorts = true
}
if customConfigDir := os.Getenv(config.NucleiConfigDirEnv); customConfigDir != "" {
config.DefaultConfig.SetConfigDir(customConfigDir)
readFlagsConfig(flagSet)
}
if cfgFile != "" {
if !fileutil.FileExists(cfgFile) {
options.Logger.Fatal().Msgf("given config file '%s' does not exist", cfgFile)
}
// merge config file with flags
if err := flagSet.MergeConfigFile(cfgFile); err != nil {
options.Logger.Fatal().Msgf("Could not read config: %s\n", err)
}
if !options.Vars.IsEmpty() {
// Maybe we should add vars to the config file as well even if they are set via flags?
file, err := os.Open(cfgFile)
if err != nil {
gologger.Fatal().Msgf("Could not open config file: %s\n", err)
}
defer func() {
_ = file.Close()
}()
data := make(map[string]interface{})
err = yaml.NewDecoder(file).Decode(&data)
if err != nil {
gologger.Fatal().Msgf("Could not decode config file: %s\n", err)
}
variables := data["var"]
if variables != nil {
if varSlice, ok := variables.([]interface{}); ok {
for _, value := range varSlice {
if strVal, ok := value.(string); ok {
err = options.Vars.Set(strVal)
if err != nil {
gologger.Warning().Msgf("Could not set variable from config file: %s\n", err)
}
} else {
gologger.Warning().Msgf("Skipping non-string variable in config: %#v", value)
}
}
} else {
gologger.Warning().Msgf("No 'var' section found in config file: %s", cfgFile)
}
}
}
}
templatesDir := options.NewTemplatesDirectory
if templatesDir == "" {
templatesDir = os.Getenv(config.NucleiTemplatesDirEnv)
}
if templatesDir != "" {
config.DefaultConfig.SetTemplatesDir(templatesDir)
}
defaultProfilesPath := filepath.Join(config.DefaultConfig.GetTemplateDir(), "profiles")
if templateProfile != "" {
if filepath.Ext(templateProfile) == "" {
if tp := findProfilePathById(templateProfile, defaultProfilesPath); tp != "" {
templateProfile = tp
} else {
options.Logger.Fatal().Msgf("'%s' is not a profile-id or profile path", templateProfile)
}
}
if !filepath.IsAbs(templateProfile) {
if filepath.Dir(templateProfile) == "profiles" {
defaultProfilesPath = filepath.Join(config.DefaultConfig.GetTemplateDir())
}
currentDir, err := os.Getwd()
if err == nil && fileutil.FileExists(filepath.Join(currentDir, templateProfile)) {
templateProfile = filepath.Join(currentDir, templateProfile)
} else {
templateProfile = filepath.Join(defaultProfilesPath, templateProfile)
}
}
if !fileutil.FileExists(templateProfile) {
options.Logger.Fatal().Msgf("given template profile file '%s' does not exist", templateProfile)
}
if err := flagSet.MergeConfigFile(templateProfile); err != nil {
options.Logger.Fatal().Msgf("Could not read template profile: %s\n", err)
}
}
if len(options.SecretsFile) > 0 {
for _, secretFile := range options.SecretsFile {
if !fileutil.FileExists(secretFile) {
options.Logger.Fatal().Msgf("given secrets file '%s' does not exist", secretFile)
}
}
}
cleanupOldResumeFiles()
return flagSet
}
// cleanupOldResumeFiles cleans up resume files older than 10 days.
func cleanupOldResumeFiles() {
root := config.DefaultConfig.GetCacheDir()
filter := fileutil.FileFilters{
OlderThan: 24 * time.Hour * 10, // cleanup on the 10th day
Prefix: "resume-",
}
_ = fileutil.DeleteFilesOlderThan(root, filter)
}
// readFlagsConfig reads the config file from the default config dir and copies it to the current config dir.
func readFlagsConfig(flagset *goflags.FlagSet) {
// check if config.yaml file exists
defaultCfgFile, err := flagset.GetConfigFilePath()
if err != nil {
// something went wrong either dir is not readable or something else went wrong upstream in `goflags`
// warn and exit in this case
options.Logger.Warning().Msgf("Could not read config file: %s\n", err)
return
}
cfgFile := config.DefaultConfig.GetFlagsConfigFilePath()
if !fileutil.FileExists(cfgFile) {
if !fileutil.FileExists(defaultCfgFile) {
// if default config does not exist, warn and exit
options.Logger.Warning().Msgf("missing default config file : %s", defaultCfgFile)
return
}
// if does not exist copy it from the default config
if err = fileutil.CopyFile(defaultCfgFile, cfgFile); err != nil {
options.Logger.Warning().Msgf("Could not copy config file: %s\n", err)
}
return
}
// if config file exists, merge it with the default config
if err = flagset.MergeConfigFile(cfgFile); err != nil {
options.Logger.Warning().Msgf("failed to merge configfile with flags got: %s\n", err)
}
}
// disableUpdatesCallback disables the update check.
func disableUpdatesCallback() {
config.DefaultConfig.DisableUpdateCheck()
}
// printVersion prints the nuclei version and exits.
func printVersion() {
options.Logger.Info().Msgf("Nuclei Engine Version: %s", config.Version)
options.Logger.Info().Msgf("Nuclei Config Directory: %s", config.DefaultConfig.GetConfigDir())
options.Logger.Info().Msgf("Nuclei Cache Directory: %s", config.DefaultConfig.GetCacheDir()) // cache dir contains resume files
options.Logger.Info().Msgf("PDCP Directory: %s", pdcp.PDCPDir)
os.Exit(0)
}
// printTemplateVersion prints the nuclei template version and exits.
func printTemplateVersion() {
cfg := config.DefaultConfig
options.Logger.Info().Msgf("Public nuclei-templates version: %s (%s)\n", cfg.TemplateVersion, cfg.TemplatesDirectory)
if fileutil.FolderExists(cfg.CustomS3TemplatesDirectory) {
options.Logger.Info().Msgf("Custom S3 templates location: %s\n", cfg.CustomS3TemplatesDirectory)
}
if fileutil.FolderExists(cfg.CustomGitHubTemplatesDirectory) {
options.Logger.Info().Msgf("Custom GitHub templates location: %s ", cfg.CustomGitHubTemplatesDirectory)
}
if fileutil.FolderExists(cfg.CustomGitLabTemplatesDirectory) {
options.Logger.Info().Msgf("Custom GitLab templates location: %s ", cfg.CustomGitLabTemplatesDirectory)
}
if fileutil.FolderExists(cfg.CustomAzureTemplatesDirectory) {
options.Logger.Info().Msgf("Custom Azure templates location: %s ", cfg.CustomAzureTemplatesDirectory)
}
os.Exit(0)
}
func resetCallback() {
warning := fmt.Sprintf(`
Using '-reset' will delete all nuclei configurations files and all nuclei-templates
Following files will be deleted:
1. All config files at %v
2. All cache files (including resume state) at %v
3. All nuclei-templates at %v
Note: Make sure you have backup of your custom nuclei-templates before proceeding
`, config.DefaultConfig.GetConfigDir(), config.DefaultConfig.GetCacheDir(), config.DefaultConfig.TemplatesDirectory)
options.Logger.Print().Msg(warning)
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("Are you sure you want to continue? [y/n]: ")
resp, err := reader.ReadString('\n')
if err != nil {
options.Logger.Fatal().Msgf("could not read response: %s", err)
}
resp = strings.TrimSpace(resp)
if stringsutil.EqualFoldAny(resp, "y", "yes") {
break
}
if stringsutil.EqualFoldAny(resp, "n", "no", "") {
fmt.Println("Exiting...")
os.Exit(0)
}
}
err := os.RemoveAll(config.DefaultConfig.GetConfigDir())
if err != nil {
options.Logger.Fatal().Msgf("could not delete config dir: %s", err)
}
err = os.RemoveAll(config.DefaultConfig.GetCacheDir())
if err != nil {
options.Logger.Fatal().Msgf("could not delete cache dir: %s", err)
}
err = os.RemoveAll(config.DefaultConfig.TemplatesDirectory)
if err != nil {
options.Logger.Fatal().Msgf("could not delete templates dir: %s", err)
}
options.Logger.Info().Msgf("Successfully deleted all nuclei configurations files and nuclei-templates")
os.Exit(0)
}
func findProfilePathById(profileId, templatesDir string) string {
var profilePath string
err := filepath.WalkDir(templatesDir, func(iterItem string, d fs.DirEntry, err error) error {
ext := filepath.Ext(iterItem)
isYaml := ext == extensions.YAML || ext == extensions.YML
if err != nil || d.IsDir() || !isYaml {
// skip non yaml files
return nil
}
if strings.TrimSuffix(filepath.Base(iterItem), ext) == profileId {
profilePath = iterItem
return fmt.Errorf("FOUND")
}
return nil
})
if err != nil && err.Error() != "FOUND" {
options.Logger.Error().Msgf("%s\n", err)
}
return profilePath
}
================================================
FILE: cmd/nuclei/main_benchmark_test.go
================================================
package main_test
import (
"fmt"
"net/http"
"net/http/httptest"
"os"
"runtime"
"runtime/pprof"
"strings"
"testing"
"time"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/gologger/levels"
"github.com/projectdiscovery/nuclei/v3/internal/runner"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
)
var (
projectPath string
targetURL string
)
func TestMain(m *testing.M) {
// Set up
gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent)
_ = os.Setenv("DISABLE_STDOUT", "true")
var err error
projectPath, err = os.MkdirTemp("", "nuclei-benchmark-")
if err != nil {
panic(err)
}
dummyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}))
targetURL = dummyServer.URL
// Execute tests
exitCode := m.Run()
// Tear down
dummyServer.Close()
_ = os.RemoveAll(projectPath)
_ = os.Unsetenv("DISABLE_STDOUT")
os.Exit(exitCode)
}
// getUniqFilename generates a unique filename by appending .N if file exists
// Similar to wget's behavior: file.cpu.prof, file.cpu.1.prof, file.cpu.2.prof, etc.
func getUniqFilename(basePath string) string {
if _, err := os.Stat(basePath); os.IsNotExist(err) {
return basePath
}
lastDot := strings.LastIndex(basePath, ".")
var name, ext string
if lastDot != -1 {
name = basePath[:lastDot]
ext = basePath[lastDot:]
} else {
name = basePath
ext = ""
}
for i := 1; ; i++ {
newPath := fmt.Sprintf("%s.%d%s", name, i, ext)
if _, err := os.Stat(newPath); os.IsNotExist(err) {
return newPath
}
}
}
func getDefaultOptions() *types.Options {
return &types.Options{
RemoteTemplateDomainList: []string{"cloud.projectdiscovery.io"},
ProjectPath: projectPath,
StatsInterval: 5,
MetricsPort: 9092,
MaxHostError: 30,
NoHostErrors: true,
BulkSize: 25,
TemplateThreads: 25,
HeadlessBulkSize: 10,
HeadlessTemplateThreads: 10,
Timeout: 10,
Retries: 1,
RateLimit: 150,
RateLimitDuration: time.Duration(time.Second),
RateLimitMinute: 0,
PageTimeout: 20,
InteractionsCacheSize: 5000,
InteractionsPollDuration: 5,
InteractionsEviction: 60,
InteractionsCoolDownPeriod: 5,
MaxRedirects: 10,
Silent: true,
VarDumpLimit: 255,
JSONRequests: true,
StoreResponseDir: "output",
InputFileMode: "list",
ResponseReadSize: 0,
ResponseSaveSize: 1048576,
InputReadTimeout: time.Duration(3 * time.Minute),
UncoverField: "ip:port",
UncoverLimit: 100,
UncoverRateLimit: 60,
ScanStrategy: "auto",
FuzzAggressionLevel: "low",
FuzzParamFrequency: 10,
TeamID: "none",
JsConcurrency: 120,
PayloadConcurrency: 25,
ProbeConcurrency: 50,
LoadHelperFileFunction: types.DefaultOptions().LoadHelperFileFunction,
// DialerKeepAlive: time.Duration(0),
// DASTServerAddress: "localhost:9055",
ExecutionId: "test",
Logger: gologger.DefaultLogger,
}
}
func runEnumBenchmark(b *testing.B, options *types.Options) {
runner.ParseOptions(options)
nucleiRunner, err := runner.New(options)
if err != nil {
b.Fatalf("failed to create runner: %s", err)
}
defer nucleiRunner.Close()
benchNameSlug := strings.ReplaceAll(b.Name(), "/", "-")
// Start CPU profiling
cpuProfileBase := fmt.Sprintf("%s.cpu.prof", benchNameSlug)
cpuProfilePath := getUniqFilename(cpuProfileBase)
cpuProfile, err := os.Create(cpuProfilePath)
if err != nil {
b.Fatalf("failed to create CPU profile: %s", err)
}
defer func() { _ = cpuProfile.Close() }()
if err := pprof.StartCPUProfile(cpuProfile); err != nil {
b.Fatalf("failed to start CPU profile: %s", err)
}
defer pprof.StopCPUProfile()
b.ReportAllocs()
for b.Loop() {
if err := nucleiRunner.RunEnumeration(); err != nil {
b.Fatalf("%s failed: %s", b.Name(), err)
}
}
b.StopTimer()
// Write heap profile
heapProfileBase := fmt.Sprintf("%s.heap.prof", benchNameSlug)
heapProfilePath := getUniqFilename(heapProfileBase)
heapProfile, err := os.Create(heapProfilePath)
if err != nil {
b.Fatalf("failed to create heap profile: %s", err)
}
defer func() { _ = heapProfile.Close() }()
runtime.GC() // Force GC before heap profile
if err := pprof.WriteHeapProfile(heapProfile); err != nil {
b.Fatalf("failed to write heap profile: %s", err)
}
}
func BenchmarkRunEnumeration(b *testing.B) {
// Default case: run enumeration with default options == all nuclei-templates
b.Run("Default", func(b *testing.B) {
options := getDefaultOptions()
options.Targets = []string{targetURL}
runEnumBenchmark(b, options)
})
// Case: https://github.com/projectdiscovery/nuclei/pull/6258
b.Run("Multiproto", func(b *testing.B) {
options := getDefaultOptions()
options.Targets = []string{targetURL}
options.Templates = []string{"./cmd/nuclei/testdata/benchmark/multiproto/"}
runEnumBenchmark(b, options)
})
}
================================================
FILE: cmd/nuclei/testdata/benchmark/multiproto/basic-template-multiproto-mixed.yaml
================================================
id: basic-template-multiproto-mixed
info:
name: Test Template Multiple Protocols (Mixed)
author: pdteam
severity: info
http:
- method: GET
id: first_iter_http
path:
- '{{BaseURL}}/1'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/2'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/3'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/4'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/5'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/6'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/7'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/8'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/9'
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /10 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /11 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /12 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /13 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /14 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET / HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /15 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /16 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /17 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /18 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
================================================
FILE: cmd/nuclei/testdata/benchmark/multiproto/basic-template-multiproto-raw.yaml
================================================
id: basic-template-multiproto-raw
info:
name: Test Template Multiple Protocols RAW
author: pdteam
severity: info
http:
- raw:
- |
GET /1 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /2 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /3 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /4 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /5 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /6 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /7 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /8 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /9 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /10 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /11 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /12 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /13 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /14 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET / HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /15 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /16 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /17 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
- raw:
- |
GET /18 HTTP/1.1
Host: {{Hostname}}
Origin: {{BaseURL}}
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
matchers:
- type: word
words:
- "Test is test matcher text"
================================================
FILE: cmd/nuclei/testdata/benchmark/multiproto/basic-template-multiproto.yaml
================================================
id: basic-template-multiproto
info:
name: Test Template Multiple Protocols
author: pdteam
severity: info
http:
- method: GET
id: first_iter_http
path:
- '{{BaseURL}}/1'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/2'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/3'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/4'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/5'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/6'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/7'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/8'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/9'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/10'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/11'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/12'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/13'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/14'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/15'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/16'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/17'
matchers:
- type: word
words:
- "Test is test matcher text"
- method: GET
path:
- '{{BaseURL}}/18'
matchers:
- type: word
words:
- "Test is test matcher text"
================================================
FILE: cmd/scan-charts/main.go
================================================
package main
import (
"flag"
"github.com/projectdiscovery/nuclei/v3/pkg/scan/charts"
)
var (
dir string
address string
output string
)
func main() {
flag.StringVar(&dir, "dir", "", "directory to scan")
flag.StringVar(&address, "address", ":9000", "address to run the server on")
flag.StringVar(&output, "output", "", "output filename of generated html file")
flag.Parse()
if dir == "" {
flag.Usage()
return
}
server, err := charts.NewScanEventsCharts(dir)
if err != nil {
panic(err)
}
server.PrintInfo()
if output != "" {
if err = server.GenerateHTML(output); err != nil {
panic(err)
}
return
}
server.Start(address)
}
================================================
FILE: cmd/tmc/main.go
================================================
package main
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"reflect"
"regexp"
"sort"
"strings"
"github.com/projectdiscovery/goflags"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/gologger/levels"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
"github.com/projectdiscovery/retryablehttp-go"
"github.com/projectdiscovery/utils/errkit"
"gopkg.in/yaml.v3"
)
const (
yamlIndentSpaces = 2
// templateman api base url
tmBaseUrlDefault = "https://tm.nuclei.sh"
)
var tmBaseUrl string
func init() {
tmBaseUrl = os.Getenv("TEMPLATEMAN_SERVER")
if tmBaseUrl == "" {
tmBaseUrl = tmBaseUrlDefault
}
}
// allTagsRegex is a list of all tags in nuclei templates except id, info, and -
var allTagsRegex []*regexp.Regexp
var defaultOpts = types.DefaultOptions()
func init() {
var tm templates.Template
t := reflect.TypeOf(tm)
for i := 0; i < t.NumField(); i++ {
tag := t.Field(i).Tag.Get("yaml")
if strings.Contains(tag, ",") {
tag = strings.Split(tag, ",")[0]
}
// ignore these tags
if tag == "id" || tag == "info" || tag == "" || tag == "-" {
continue
}
re := regexp.MustCompile(tag + `:\s*\n`)
if t.Field(i).Type.Kind() == reflect.Bool {
re = regexp.MustCompile(tag + `:\s*(true|false)\s*\n`)
}
allTagsRegex = append(allTagsRegex, re)
}
// need to set headless to true for headless templates
defaultOpts.Headless = true
defaultOpts.EnableCodeTemplates = true
defaultOpts.EnableSelfContainedTemplates = true
if err := protocolstate.Init(defaultOpts); err != nil {
gologger.Fatal().Msgf("Could not initialize protocol state: %s\n", err)
}
if err := protocolinit.Init(defaultOpts); err != nil {
gologger.Fatal().Msgf("Could not initialize protocol state: %s\n", err)
}
}
type options struct {
input string
errorLogFile string
lint bool
validate bool
format bool
enhance bool
maxRequest bool
debug bool
}
func main() {
opts := options{}
flagSet := goflags.NewFlagSet()
flagSet.SetDescription(`TemplateMan CLI is basic utility built on the TemplateMan API to standardize nuclei templates.`)
flagSet.CreateGroup("Input", "input",
flagSet.StringVarP(&opts.input, "input", "i", "", "Templates to annotate"),
)
flagSet.CreateGroup("Config", "config",
flagSet.BoolVarP(&opts.lint, "lint", "l", false, "lint given nuclei template"),
flagSet.BoolVarP(&opts.validate, "validate", "v", false, "validate given nuclei template"),
flagSet.BoolVarP(&opts.format, "format", "f", false, "format given nuclei template"),
flagSet.BoolVarP(&opts.enhance, "enhance", "e", false, "enhance given nuclei template"),
flagSet.BoolVarP(&opts.maxRequest, "max-request", "mr", false, "add / update max request counter"),
flagSet.StringVarP(&opts.errorLogFile, "error-log", "el", "", "file to write failed template update"),
flagSet.BoolVarP(&opts.debug, "debug", "d", false, "show debug message"),
)
if err := flagSet.Parse(); err != nil {
gologger.Fatal().Msgf("Error parsing flags: %s\n", err)
}
if opts.input == "" {
gologger.Fatal().Msg("input template path/directory is required")
}
if strings.HasPrefix(opts.input, "~/") {
home, err := os.UserHomeDir()
if err != nil {
log.Fatalf("Failed to read UserHomeDir: %v, provide absolute template path/directory\n", err)
}
opts.input = filepath.Join(home, (opts.input)[2:])
}
gologger.DefaultLogger.SetMaxLevel(levels.LevelInfo)
if opts.debug {
gologger.DefaultLogger.SetMaxLevel(levels.LevelDebug)
}
if err := process(opts); err != nil {
gologger.Error().Msgf("could not process: %s\n", err)
}
}
func process(opts options) error {
tempDir, err := os.MkdirTemp("", "nuclei-nvd")
if err != nil {
return err
}
defer func() {
_ = os.RemoveAll(tempDir)
}()
var errFile *os.File
if opts.errorLogFile != "" {
errFile, err = os.OpenFile(opts.errorLogFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
gologger.Fatal().Msgf("could not open error log file: %s\n", err)
}
defer func() {
_ = errFile.Close()
}()
}
templateCatalog := disk.NewCatalog(filepath.Dir(opts.input))
paths, err := templateCatalog.GetTemplatePath(opts.input)
if err != nil {
return err
}
for _, path := range paths {
data, err := os.ReadFile(path)
if err != nil {
return err
}
dataString := string(data)
if opts.maxRequest {
var updated bool // if max-requests is updated
dataString, updated, err = parseAndAddMaxRequests(templateCatalog, path, dataString)
if err != nil {
gologger.Info().Label("max-request").Msg(logErrMsg(path, err, opts.debug, errFile))
} else {
if updated {
gologger.Info().Label("max-request").Msgf("✅ updated template: %s\n", path)
}
// do not print if max-requests is not updated
}
}
if opts.lint {
lint, err := lintTemplate(dataString)
if err != nil {
gologger.Info().Label("lint").Msg(logErrMsg(path, err, opts.debug, errFile))
}
if lint {
gologger.Info().Label("lint").Msgf("✅ lint template: %s\n", path)
}
}
if opts.validate {
validate, err := validateTemplate(dataString)
if err != nil {
gologger.Info().Label("validate").Msg(logErrMsg(path, err, opts.debug, errFile))
}
if validate {
gologger.Info().Label("validate").Msgf("✅ validated template: %s\n", path)
}
}
if opts.format {
formattedTemplateData, isFormatted, err := formatTemplate(dataString)
if err != nil {
gologger.Info().Label("format").Msg(logErrMsg(path, err, opts.debug, errFile))
} else {
if isFormatted {
_ = os.WriteFile(path, []byte(formattedTemplateData), 0644)
dataString = formattedTemplateData
gologger.Info().Label("format").Msgf("✅ formatted template: %s\n", path)
}
}
}
if opts.enhance {
enhancedTemplateData, isEnhanced, err := enhanceTemplate(dataString)
if err != nil {
gologger.Info().Label("enhance").Msg(logErrMsg(path, err, opts.debug, errFile))
continue
} else {
if isEnhanced {
_ = os.WriteFile(path, []byte(enhancedTemplateData), 0644)
gologger.Info().Label("enhance").Msgf("✅ updated template: %s\n", path)
}
}
}
}
return nil
}
func logErrMsg(path string, err error, debug bool, errFile *os.File) string {
msg := fmt.Sprintf("❌ template: %s\n", path)
if debug {
msg = fmt.Sprintf("❌ template: %s err: %s\n", path, err)
}
if errFile != nil {
_, _ = fmt.Fprintf(errFile, "❌ template: %s err: %s\n", path, err)
}
return msg
}
// enhanceTemplate enhances template data using templateman
// ref: https://github.com/projectdiscovery/templateman/blob/main/templateman-rest-api/README.md#enhance-api
func enhanceTemplate(data string) (string, bool, error) {
resp, err := retryablehttp.DefaultClient().Post(fmt.Sprintf("%s/enhance", tmBaseUrl), "application/x-yaml", strings.NewReader(data))
if err != nil {
return data, false, err
}
if resp.StatusCode != 200 {
return data, false, errkit.New("unexpected status code: %v", resp.Status)
}
var templateResp TemplateResp
if err := json.NewDecoder(resp.Body).Decode(&templateResp); err != nil {
return data, false, err
}
if strings.TrimSpace(templateResp.Enhanced) != "" {
return templateResp.Enhanced, templateResp.Enhance, nil
}
if templateResp.ValidateErrorCount > 0 {
if len(templateResp.ValidateError) > 0 {
return data, false, errkit.New(templateResp.ValidateError[0].Message+": at line %v", templateResp.ValidateError[0].Mark.Line, "tag", "validate")
}
return data, false, errkit.New("validation failed", "tag", "validate")
}
if templateResp.Error.Name != "" {
return data, false, errkit.New("%s", templateResp.Error.Name)
}
if strings.TrimSpace(templateResp.Enhanced) == "" && !templateResp.Lint {
if templateResp.LintError.Reason != "" {
return data, false, errkit.New(templateResp.LintError.Reason+" : at line %v", templateResp.LintError.Mark.Line, "tag", "lint")
}
return data, false, errkit.New("at line: %v", templateResp.LintError.Mark.Line, "tag", "lint")
}
return data, false, errkit.New("template enhance failed")
}
// formatTemplate formats template data using templateman format api
func formatTemplate(data string) (string, bool, error) {
resp, err := retryablehttp.DefaultClient().Post(fmt.Sprintf("%s/format", tmBaseUrl), "application/x-yaml", strings.NewReader(data))
if err != nil {
return data, false, err
}
if resp.StatusCode != 200 {
return data, false, errkit.New("unexpected status code: %v", resp.Status)
}
var templateResp TemplateResp
if err := json.NewDecoder(resp.Body).Decode(&templateResp); err != nil {
return data, false, err
}
if strings.TrimSpace(templateResp.Updated) != "" {
return templateResp.Updated, templateResp.Format, nil
}
if templateResp.ValidateErrorCount > 0 {
if len(templateResp.ValidateError) > 0 {
return data, false, errkit.New(templateResp.ValidateError[0].Message+": at line %v", templateResp.ValidateError[0].Mark.Line, "tag", "validate")
}
return data, false, errkit.New("validation failed", "tag", "validate")
}
if templateResp.Error.Name != "" {
return data, false, errkit.New("%s", templateResp.Error.Name)
}
if strings.TrimSpace(templateResp.Updated) == "" && !templateResp.Lint {
if templateResp.LintError.Reason != "" {
return data, false, errkit.New(templateResp.LintError.Reason+" : at line %v", templateResp.LintError.Mark.Line, "tag", "lint")
}
return data, false, errkit.New("at line: %v", templateResp.LintError.Mark.Line, "tag", "lint")
}
return data, false, errkit.New("template format failed")
}
// lintTemplate lints template data using templateman lint api
func lintTemplate(data string) (bool, error) {
resp, err := retryablehttp.DefaultClient().Post(fmt.Sprintf("%s/lint", tmBaseUrl), "application/x-yaml", strings.NewReader(data))
if err != nil {
return false, err
}
if resp.StatusCode != 200 {
return false, errkit.New("unexpected status code: %v", resp.Status)
}
var lintResp TemplateLintResp
if err := json.NewDecoder(resp.Body).Decode(&lintResp); err != nil {
return false, err
}
if lintResp.Lint {
return true, nil
}
if lintResp.LintError.Reason != "" {
return false, errkit.New(lintResp.LintError.Reason+" : at line %v", lintResp.LintError.Mark.Line, "tag", "lint")
}
return false, errkit.New("at line: %v", lintResp.LintError.Mark.Line, "tag", "lint")
}
// validateTemplate validates template data using templateman validate api
func validateTemplate(data string) (bool, error) {
resp, err := retryablehttp.DefaultClient().Post(fmt.Sprintf("%s/validate", tmBaseUrl), "application/x-yaml", strings.NewReader(data))
if err != nil {
return false, err
}
if resp.StatusCode != 200 {
return false, errkit.New("unexpected status code: %v", resp.Status)
}
var validateResp TemplateResp
if err := json.NewDecoder(resp.Body).Decode(&validateResp); err != nil {
return false, err
}
if validateResp.Validate {
return true, nil
}
if validateResp.ValidateErrorCount > 0 {
if len(validateResp.ValidateError) > 0 {
return false, errkit.New(validateResp.ValidateError[0].Message+": at line %v", validateResp.ValidateError[0].Mark.Line, "tag", "validate")
}
return false, errkit.New("validation failed", "tag", "validate")
}
if validateResp.Error.Name != "" {
return false, errkit.New("%s", validateResp.Error.Name)
}
return false, errkit.New("template validation failed")
}
// parseAndAddMaxRequests parses and adds max requests to templates
func parseAndAddMaxRequests(catalog catalog.Catalog, path, data string) (string, bool, error) {
template, err := parseTemplate(catalog, path)
if err != nil {
return data, false, err
}
if template.TotalRequests < 1 {
return data, false, nil
}
// Marshal the updated info block back to YAML.
infoBlockStart, infoBlockEnd := getInfoStartEnd(data)
infoBlockOrig := data[infoBlockStart:infoBlockEnd]
infoBlockOrig = strings.TrimRight(infoBlockOrig, "\n")
infoBlock := InfoBlock{}
err = yaml.Unmarshal([]byte(data), &infoBlock)
if err != nil {
return data, false, err
}
// if metadata is nil, create a new map
if infoBlock.Info.Metadata == nil {
infoBlock.Info.Metadata = make(map[string]interface{})
}
// do not update if it is already present and equal
if mr, ok := infoBlock.Info.Metadata["max-request"]; ok && mr.(int) == template.TotalRequests {
return data, false, nil
}
infoBlock.Info.Metadata["max-request"] = template.TotalRequests
var newInfoBlock bytes.Buffer
yamlEncoder := yaml.NewEncoder(&newInfoBlock)
yamlEncoder.SetIndent(yamlIndentSpaces)
err = yamlEncoder.Encode(infoBlock)
if err != nil {
return data, false, err
}
newInfoBlockData := strings.TrimSuffix(newInfoBlock.String(), "\n")
// replace old info block with new info block
newTemplate := strings.ReplaceAll(data, infoBlockOrig, newInfoBlockData)
err = os.WriteFile(path, []byte(newTemplate), 0644)
if err == nil {
return newTemplate, true, nil
}
return newTemplate, false, err
}
// parseTemplate parses a template and returns the template object
func parseTemplate(catalog catalog.Catalog, templatePath string) (*templates.Template, error) {
executorOpts := &protocols.ExecutorOptions{
Catalog: catalog,
Options: defaultOpts,
}
reader, err := executorOpts.Catalog.OpenFile(templatePath)
if err != nil {
return nil, err
}
template, err := templates.ParseTemplateFromReader(reader, nil, executorOpts)
if err != nil {
return nil, err
}
return template, nil
}
// find the start and end of the info block
func getInfoStartEnd(data string) (int, int) {
info := strings.Index(data, "info:")
var indices []int
for _, re := range allTagsRegex {
// find the first occurrence of the label
match := re.FindStringIndex(data)
if match != nil {
indices = append(indices, match[0])
}
}
// find the first one after info block
sort.Ints(indices)
return info, indices[0] - 1
}
================================================
FILE: cmd/tmc/types.go
================================================
package main
type Mark struct {
Name string `json:"name,omitempty"`
Position int `json:"position,omitempty"`
Line int `json:"line,omitempty"`
Column int `json:"column,omitempty"`
Snippet string `json:"snippet,omitempty"`
}
type Error struct {
Name string `json:"name"`
Mark Mark `json:"mark"`
}
type LintError struct {
Name string `json:"name,omitempty"`
Reason string `json:"reason,omitempty"`
Mark Mark `json:"mark,omitempty"`
}
type TemplateLintResp struct {
Input string `json:"template_input,omitempty"`
Lint bool `json:"template_lint,omitempty"`
LintError LintError `json:"lint_error,omitempty"`
}
type ValidateError struct {
Location string `json:"location,omitempty"`
Message string `json:"message,omitempty"`
Name string `json:"name,omitempty"`
Argument interface{} `json:"argument,omitempty"`
Stack string `json:"stack,omitempty"`
Mark struct {
Line int `json:"line,omitempty"`
Column int `json:"column,omitempty"`
Pos int `json:"pos,omitempty"`
} `json:"mark,omitempty"`
}
// TemplateResponse from templateman to be used for enhancing and formatting
type TemplateResp struct {
Input string `json:"template_input,omitempty"`
Format bool `json:"template_format,omitempty"`
Updated string `json:"updated_template,omitempty"`
Enhance bool `json:"template_enhance,omitempty"`
Enhanced string `json:"enhanced_template,omitempty"`
Lint bool `json:"template_lint,omitempty"`
LintError LintError `json:"lint_error,omitempty"`
Validate bool `json:"template_validate,omitempty"`
ValidateErrorCount int `json:"validate_error_count,omitempty"`
ValidateError []ValidateError `json:"validate_error,omitempty"`
Error Error `json:"error,omitempty"`
}
// InfoBlock Cloning struct from nuclei as we don't want any validation
type InfoBlock struct {
Info TemplateInfo `yaml:"info"`
}
type TemplateClassification struct {
CvssMetrics string `yaml:"cvss-metrics,omitempty"`
CvssScore float64 `yaml:"cvss-score,omitempty"`
CveId string `yaml:"cve-id,omitempty"`
CweId string `yaml:"cwe-id,omitempty"`
Cpe string `yaml:"cpe,omitempty"`
EpssScore float64 `yaml:"epss-score,omitempty"`
}
type TemplateInfo struct {
Name string `yaml:"name"`
Author string `yaml:"author"`
Severity string `yaml:"severity,omitempty"`
Description string `yaml:"description,omitempty"`
Reference interface{} `yaml:"reference,omitempty"`
Remediation string `yaml:"remediation,omitempty"`
Classification TemplateClassification `yaml:"classification,omitempty"`
Metadata map[string]interface{} `yaml:"metadata,omitempty"`
Tags string `yaml:"tags,omitempty"`
}
================================================
FILE: cmd/tools/fuzzplayground/main.go
================================================
package main
import (
"flag"
_ "github.com/mattn/go-sqlite3"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/testutils/fuzzplayground"
)
var (
addr string
)
func main() {
flag.StringVar(&addr, "addr", "localhost:8082", "playground server address")
flag.Parse()
defer fuzzplayground.Cleanup()
server := fuzzplayground.GetPlaygroundServer()
defer func() {
_ = server.Close()
}()
// Start the server
if err := server.Start(addr); err != nil {
gologger.Fatal().Msgf("Could not start server: %s\n", err)
}
}
================================================
FILE: cmd/tools/signer/main.go
================================================
package main
import (
"crypto/sha256"
"encoding/hex"
"flag"
"os"
"path/filepath"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/gologger/levels"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
"github.com/projectdiscovery/nuclei/v3/pkg/templates/signer"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
fileutil "github.com/projectdiscovery/utils/file"
folderutil "github.com/projectdiscovery/utils/folder"
)
var (
appConfigDir = folderutil.AppConfigDirOrDefault(".config", "nuclei")
defaultCertFile = filepath.Join(appConfigDir, "keys", "nuclei-user.crt")
defaultPrivKey = filepath.Join(appConfigDir, "keys", "nuclei-user-private-key.pem")
)
var (
template string
cert string
privKey string
)
func main() {
flag.StringVar(&template, "template", "", "template to sign (file only)")
flag.StringVar(&cert, "cert", defaultCertFile, "certificate file")
flag.StringVar(&privKey, "priv-key", defaultPrivKey, "private key file")
flag.Parse()
config.DefaultConfig.LogAllEvents = true
gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose)
if template == "" {
gologger.Fatal().Msg("template is required")
}
if !fileutil.FileExists(template) {
gologger.Fatal().Msgf("template file %s does not exist or not a file", template)
}
// get signer
tmplSigner, err := signer.NewTemplateSignerFromFiles(cert, privKey)
if err != nil {
gologger.Fatal().Msgf("failed to create signer: %s", err)
}
gologger.Info().Msgf("Template Signer: %v\n", tmplSigner.Identifier())
// read file
bin, err := os.ReadFile(template)
if err != nil {
gologger.Fatal().Msgf("failed to read template file %s: %s", template, err)
}
// extract signature and content
sig, content := signer.ExtractSignatureAndContent(bin)
hash := sha256.Sum256(content)
gologger.Info().Msgf("Signature Details:")
gologger.Info().Msgf("----------------")
gologger.Info().Msgf("Signature: %s", sig)
gologger.Info().Msgf("Content Hash (SHA256): %s\n", hex.EncodeToString(hash[:]))
execOpts := defaultExecutorOpts(template)
tmpl, err := templates.Parse(template, nil, execOpts)
if err != nil {
gologger.Fatal().Msgf("failed to parse template: %s", err)
}
gologger.Info().Msgf("Template Verified: %v\n", tmpl.Verified)
if !tmpl.Verified {
gologger.Info().Msgf("------------------------")
gologger.Info().Msg("Template is not verified, signing template")
if err := templates.SignTemplate(tmplSigner, template); err != nil {
gologger.Fatal().Msgf("Failed to sign template: %s", err)
}
// verify again by reading file what the new signature and hash is
bin2, err := os.ReadFile(template)
if err != nil {
gologger.Fatal().Msgf("failed to read signed template file %s: %s", template, err)
}
sig2, content2 := signer.ExtractSignatureAndContent(bin2)
hash2 := sha256.Sum256(content2)
gologger.Info().Msgf("Updated Signature Details:")
gologger.Info().Msgf("------------------------")
gologger.Info().Msgf("Signature: %s", sig2)
gologger.Info().Msgf("Content Hash (SHA256): %s\n", hex.EncodeToString(hash2[:]))
}
gologger.Info().Msgf("✓ Template signed & verified successfully")
}
func defaultExecutorOpts(templatePath string) *protocols.ExecutorOptions {
// use parsed options when initializing signer instead of default options
options := types.DefaultOptions()
templates.UseOptionsForSigner(options)
catalog := disk.NewCatalog(filepath.Dir(templatePath))
executerOpts := &protocols.ExecutorOptions{
Catalog: catalog,
Options: options,
TemplatePath: templatePath,
Parser: templates.NewParser(),
}
return executerOpts
}
================================================
FILE: examples/advanced/advanced.go
================================================
package main
import (
"context"
nuclei "github.com/projectdiscovery/nuclei/v3/lib"
"github.com/projectdiscovery/nuclei/v3/pkg/installer"
syncutil "github.com/projectdiscovery/utils/sync"
)
func main() {
ctx := context.Background()
// when running nuclei in parallel for first time it is a good practice to make sure
// templates exists first
tm := installer.TemplateManager{}
if err := tm.FreshInstallIfNotExists(); err != nil {
panic(err)
}
// create nuclei engine with options
ne, err := nuclei.NewThreadSafeNucleiEngineCtx(ctx)
if err != nil {
panic(err)
}
// setup sizedWaitgroup to handle concurrency
sg, err := syncutil.New(syncutil.WithSize(10))
if err != nil {
panic(err)
}
// scan 1 = run dns templates on scanme.sh
sg.Add()
go func() {
defer sg.Done()
err = ne.ExecuteNucleiWithOpts([]string{"scanme.sh"},
nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "dns"}),
nuclei.WithHeaders([]string{"X-Bug-Bounty: pdteam"}),
nuclei.EnablePassiveMode(),
)
if err != nil {
panic(err)
}
}()
// scan 2 = run templates with oast tags on honey.scanme.sh
sg.Add()
go func() {
defer sg.Done()
err = ne.ExecuteNucleiWithOpts([]string{"http://honey.scanme.sh"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{Tags: []string{"oast"}}))
if err != nil {
panic(err)
}
}()
// wait for all scans to finish
sg.Wait()
defer ne.Close()
// Output:
// [dns-saas-service-detection] scanme.sh
// [nameserver-fingerprint] scanme.sh
// [dns-saas-service-detection] honey.scanme.sh
}
================================================
FILE: examples/simple/simple.go
================================================
package main
import (
"context"
nuclei "github.com/projectdiscovery/nuclei/v3/lib"
)
func main() {
ne, err := nuclei.NewNucleiEngineCtx(context.Background(),
nuclei.WithTemplateFilters(nuclei.TemplateFilters{Tags: []string{"oast"}}),
nuclei.EnableStatsWithOpts(nuclei.StatsOptions{MetricServerPort: 6064}), // optionally enable metrics server for better observability
)
if err != nil {
panic(err)
}
// load targets and optionally probe non http/https targets
ne.LoadTargets([]string{"http://honey.scanme.sh"}, false)
err = ne.ExecuteWithCallback(nil)
if err != nil {
panic(err)
}
defer ne.Close()
}
================================================
FILE: examples/with_speed_control/main.go
================================================
package main
import (
"context"
"log"
"sync"
"time"
nuclei "github.com/projectdiscovery/nuclei/v3/lib"
"github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
)
func main() {
ne, err := initializeNucleiEngine()
if err != nil {
panic(err)
}
defer ne.Close()
ne.LoadTargets([]string{"http://honey.scanme.sh"}, false)
var wg sync.WaitGroup
wg.Add(3)
go testRateLimit(&wg, ne)
go testThreadsAndBulkSize(&wg, ne)
go testPayloadConcurrency(&wg, ne)
err = ne.ExecuteWithCallback(nil)
if err != nil {
panic(err)
}
wg.Wait()
}
func initializeNucleiEngine() (*nuclei.NucleiEngine, error) {
return nuclei.NewNucleiEngineCtx(context.TODO(),
nuclei.WithTemplateFilters(nuclei.TemplateFilters{Tags: []string{"oast"}}),
nuclei.EnableStatsWithOpts(nuclei.StatsOptions{MetricServerPort: 6064}),
nuclei.WithGlobalRateLimit(1, time.Second),
nuclei.WithConcurrency(nuclei.Concurrency{
TemplateConcurrency: 1,
HostConcurrency: 1,
HeadlessHostConcurrency: 1,
HeadlessTemplateConcurrency: 1,
JavascriptTemplateConcurrency: 1,
TemplatePayloadConcurrency: 1,
ProbeConcurrency: 1,
}),
)
}
func testRateLimit(wg *sync.WaitGroup, ne *nuclei.NucleiEngine) {
defer wg.Done()
verifyRateLimit(ne, 1, 5000)
}
func testThreadsAndBulkSize(wg *sync.WaitGroup, ne *nuclei.NucleiEngine) {
defer wg.Done()
initialTemplateThreads, initialBulkSize := 1, 1
verifyThreadsAndBulkSize(ne, initialTemplateThreads, initialBulkSize, 25, 25)
}
func testPayloadConcurrency(wg *sync.WaitGroup, ne *nuclei.NucleiEngine) {
defer wg.Done()
verifyPayloadConcurrency(ne, 1, 500)
}
func verifyRateLimit(ne *nuclei.NucleiEngine, initialRate, finalRate int) {
if ne.GetExecuterOptions().RateLimiter.GetLimit() != uint(initialRate) {
panic("wrong initial rate limit")
}
time.Sleep(5 * time.Second)
ne.Options().RateLimit = finalRate
time.Sleep(20 * time.Second)
if ne.GetExecuterOptions().RateLimiter.GetLimit() != uint(finalRate) {
panic("wrong final rate limit")
}
}
func verifyThreadsAndBulkSize(ne *nuclei.NucleiEngine, initialThreads, initialBulk, finalThreads, finalBulk int) {
if ne.Options().TemplateThreads != initialThreads || ne.Options().BulkSize != initialBulk {
panic("wrong initial standard concurrency")
}
time.Sleep(5 * time.Second)
ne.Options().TemplateThreads = finalThreads
ne.Options().BulkSize = finalBulk
time.Sleep(20 * time.Second)
if ne.Engine().GetWorkPool().InputPool(types.HTTPProtocol).Size != finalBulk || ne.Engine().WorkPool().Default.Size != finalThreads {
log.Fatal("wrong final concurrency", ne.Engine().WorkPool().Default.Size, finalThreads, ne.Engine().GetWorkPool().InputPool(types.HTTPProtocol).Size, finalBulk)
}
}
func verifyPayloadConcurrency(ne *nuclei.NucleiEngine, initialPayloadConcurrency, finalPayloadConcurrency int) {
if ne.Options().PayloadConcurrency != initialPayloadConcurrency {
panic("wrong initial payload concurrency")
}
time.Sleep(5 * time.Second)
ne.Options().PayloadConcurrency = finalPayloadConcurrency
time.Sleep(20 * time.Second)
if ne.GetExecuterOptions().GetThreadsForNPayloadRequests(100, 0) != finalPayloadConcurrency {
panic("wrong final payload concurrency")
}
}
================================================
FILE: gh_retry.sh
================================================
#!/bin/bash
# This script is used to retry failed workflows in github actions.
# It uses gh cli to fetch the failed workflows and then rerun them.
# It also checks the logs of the failed workflows to see if it is a flaky test.
# If it is a flaky test, it will rerun the failed jobs in the workflow.
# eg:
# ./gh_retry.sh -h to see the help.
# ./gh_retry.sh will run the script with default values.
# You can also pass the following arguments:
# ./gh_retry.sh -b master -l 30 -t "30 mins ago" -w "Build Test"
#Initialize variables to default values.
BRANCH=$(git symbolic-ref --short HEAD)
LIMIT=30
BEFORE="30 mins ago"
WORKFLOW="Build Test"
# You can add multiple patterns separated by |
GREP_ERROR_PATTERN='Test "http/interactsh.yaml" failed'
#Set fonts for Help.
NORM=$(tput sgr0)
BOLD=$(tput bold)
REV=$(tput smso)
HELP()
{
# Display Help
echo "Script to retry failed workflows in github actions."
echo
echo "Syntax: scriptTemplate [-b]"
echo "options:"
echo "${REV}-b${NORM} Branch to check failed workflows/jobs. Default is ${BOLD}$BRANCH${NORM}."
echo "${REV}-l${NORM} Maximum number of runs to fetch. Default is ${BOLD}$LIMIT${NORM}."
echo "${REV}-t${NORM} Time to filter the failed jobs. Default is ${BOLD}$BEFORE${NORM}."
echo "${REV}-w${NORM} Workflow to filter the failed jobs. Default is ${BOLD}$WORKFLOW${NORM}."
echo
}
while getopts :b:l:t:w:h FLAG; do
case $FLAG in
b)
BRANCH=$OPTARG
;;
l)
LIMIT=$OPTARG
;;
t)
BEFORE=$OPTARG
;;
w)
WORKFLOW=$OPTARG
;;
h) #show help
HELP
exit 0
;;
\?) #unrecognized option - show help
echo -e \\n"Option -${BOLD}$OPTARG${NORM} not allowed."
HELP
exit 0
;;
esac
done
shift $((OPTIND-1))
function print_bold() {
echo "${BOLD}$1${NORM}"
}
function retry_failed_jobs() {
print_bold "Checking failed workflows for branch $BRANCH before $BEFORE"
date=$(date +%Y-%m-%d'T'%H:%M'Z' -d "$BEFORE")
workflowIds=$(gh run list --limit "$LIMIT" --json headBranch,status,name,conclusion,databaseId,updatedAt | jq -c '.[] |
select ( .headBranch==$branch ) |
select ( .name | contains($workflow) ) |
select ( .conclusion=="failure" ) |
select ( .updatedAt > $date) ' --arg date "$date" --arg branch "$BRANCH" --arg workflow "$WORKFLOW" | jq .databaseId)
# convert line separated by space to array
eval "arr=($workflowIds)"
if [[ -z $arr ]]
then
print_bold "Could not find any failed workflows in the last $BEFORE"
exit 0
fi
for s in "${arr[@]}"; do
print_bold "Checking logs of failed workflow $s to see if it is a flaky test"
gh run view "$s" --log-failed | grep -E "$GREP_ERROR_PATTERN" > /dev/null
if [ $? == 0 ] ; then
print_bold "Retrying failed jobs $s"
gh run rerun "$s" --failed
sleep 10s
gh run view "$s"
fi
done
}
retry_failed_jobs
================================================
FILE: go.mod
================================================
module github.com/projectdiscovery/nuclei/v3
go 1.25.7
require (
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible
github.com/andygrunwald/go-jira v1.16.1
github.com/antchfx/htmlquery v1.3.5
github.com/bluele/gcache v0.0.2
github.com/go-playground/validator/v10 v10.26.0
github.com/go-rod/rod v0.116.2
github.com/gobwas/ws v1.4.0
github.com/google/go-github v17.0.0+incompatible
github.com/invopop/jsonschema v0.13.0
github.com/itchyny/gojq v0.12.17
github.com/json-iterator/go v1.1.12
github.com/julienschmidt/httprouter v1.3.0
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/miekg/dns v1.1.68
github.com/olekukonko/tablewriter v1.0.8
github.com/pkg/errors v0.9.1
github.com/projectdiscovery/clistats v0.1.1
github.com/projectdiscovery/fastdialer v0.5.4
github.com/projectdiscovery/hmap v0.0.100
github.com/projectdiscovery/interactsh v1.3.1
github.com/projectdiscovery/rawhttp v0.1.90
github.com/projectdiscovery/retryabledns v1.0.113
github.com/projectdiscovery/retryablehttp-go v1.3.6
github.com/projectdiscovery/yamldoc-go v1.0.6
github.com/remeh/sizedwaitgroup v1.0.0
github.com/rs/xid v1.6.0
github.com/segmentio/ksuid v1.0.4
github.com/shirou/gopsutil/v3 v3.24.5 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/cast v1.9.2
github.com/syndtr/goleveldb v1.0.0
github.com/valyala/fasttemplate v1.2.2
github.com/weppos/publicsuffix-go v0.50.3
go.uber.org/multierr v1.11.0
golang.org/x/net v0.51.0
golang.org/x/oauth2 v0.34.0
golang.org/x/text v0.34.0
gopkg.in/yaml.v2 v2.4.0
)
require (
carvel.dev/ytt v0.52.0
code.gitea.io/sdk/gitea v0.17.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0
github.com/Azure/go-ntlmssp v0.1.0
github.com/DataDog/gostackparse v0.7.0
github.com/Masterminds/semver/v3 v3.4.0
github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057
github.com/Mzack9999/go-rsync v0.0.0-20250821180103-81ffa574ef4d
github.com/Mzack9999/goja v0.0.0-20250507184235-e46100e9c697
github.com/Mzack9999/goja_nodejs v0.0.0-20250507184139-66bcbf65c883
github.com/alexsnet/go-vnc v0.1.0
github.com/alitto/pond v1.9.2
github.com/antchfx/xmlquery v1.4.4
github.com/antchfx/xpath v1.3.5
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/aws/aws-sdk-go-v2 v1.36.5
github.com/aws/aws-sdk-go-v2/config v1.29.17
github.com/aws/aws-sdk-go-v2/credentials v1.17.70
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.82
github.com/aws/aws-sdk-go-v2/service/s3 v1.82.0
github.com/bytedance/sonic v1.15.0
github.com/cespare/xxhash v1.1.0
github.com/charmbracelet/glamour v0.10.0
github.com/clbanning/mxj/v2 v2.7.0
github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c
github.com/docker/go-units v0.5.0
github.com/fatih/structs v1.1.0
github.com/getkin/kin-openapi v0.132.0
github.com/go-git/go-git/v5 v5.16.5
github.com/go-ldap/ldap/v3 v3.4.11
github.com/go-pdf/fpdf v0.9.0
github.com/go-pg/pg/v10 v10.15.0
github.com/go-sql-driver/mysql v1.9.3
github.com/goccy/go-json v0.10.5
github.com/google/uuid v1.6.0
github.com/h2non/filetype v1.1.3
github.com/invopop/yaml v0.3.1
github.com/jcmturner/gokrb5/v8 v8.4.4
github.com/kitabisa/go-ci v1.0.3
github.com/labstack/echo/v4 v4.13.4
github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa
github.com/lib/pq v1.11.2
github.com/mattn/go-sqlite3 v1.14.28
github.com/maypok86/otter/v2 v2.2.1
github.com/mholt/archives v0.1.5
github.com/microsoft/go-mssqldb v1.9.2
github.com/ory/dockertest/v3 v3.12.0
github.com/praetorian-inc/fingerprintx v1.1.15
github.com/projectdiscovery/dsl v0.8.13
github.com/projectdiscovery/fasttemplate v0.0.2
github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb
github.com/projectdiscovery/goflags v0.1.74
github.com/projectdiscovery/gologger v1.1.68
github.com/projectdiscovery/gostruct v0.0.2
github.com/projectdiscovery/gozero v0.1.1-0.20251027191944-a4ea43320b81
github.com/projectdiscovery/httpx v1.9.0
github.com/projectdiscovery/mapcidr v1.1.97
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5
github.com/projectdiscovery/networkpolicy v0.1.34
github.com/projectdiscovery/ratelimit v0.0.83
github.com/projectdiscovery/rdap v0.9.0
github.com/projectdiscovery/sarif v0.0.1
github.com/projectdiscovery/tlsx v1.2.2
github.com/projectdiscovery/uncover v1.2.0
github.com/projectdiscovery/useragent v0.0.107
github.com/projectdiscovery/utils v0.9.0
github.com/projectdiscovery/wappalyzergo v0.2.71
github.com/redis/go-redis/v9 v9.11.0
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466
github.com/sijms/go-ora/v2 v2.9.0
github.com/stretchr/testify v1.11.1
github.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9
github.com/testcontainers/testcontainers-go v0.38.0
github.com/testcontainers/testcontainers-go/modules/mongodb v0.37.0
github.com/yassinebenaid/godump v0.11.1
github.com/zmap/zgrab2 v0.1.8
gitlab.com/gitlab-org/api/client-go v0.130.1
go.mongodb.org/mongo-driver v1.17.9
golang.org/x/term v0.40.0
gopkg.in/yaml.v3 v3.0.1
moul.io/http2curl v1.0.0
)
require (
aead.dev/minisign v0.3.0 // indirect
dario.cat/mergo v1.0.2 // indirect
filippo.io/edwards25519 v1.1.1 // indirect
git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/PuerkitoBio/goquery v1.11.0 // indirect
github.com/STARRY-S/zip v0.2.3 // indirect
github.com/VividCortex/ewma v1.2.0 // indirect
github.com/akrylysov/pogreb v0.10.2 // indirect
github.com/alecthomas/chroma/v2 v2.20.0 // indirect
github.com/alecthomas/kingpin/v2 v2.4.0 // indirect
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 // indirect
github.com/aws/smithy-go v1.22.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/bits-and-blooms/bitset v1.22.0 // indirect
github.com/bits-and-blooms/bloom/v3 v3.5.0 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/sevenzip v1.6.1 // indirect
github.com/bodgit/windows v1.0.1 // indirect
github.com/brianvoe/gofakeit/v7 v7.2.1 // indirect
github.com/buger/jsonparser v1.1.2 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/caddyserver/certmagic v0.25.0 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/censys/censys-sdk-go v0.19.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/colorprofile v0.3.2 // indirect
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect
github.com/charmbracelet/x/ansi v0.10.1 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/exp/slice v0.0.0-20250908092851-c2208eb08494 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/cheggaaa/pb/v3 v3.1.7 // indirect
github.com/cloudflare/cfssl v1.6.4 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect
github.com/cyphar/filepath-securejoin v0.5.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/djherbis/times v1.6.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/docker/cli v29.2.0+incompatible // indirect
github.com/docker/docker v28.3.3+incompatible // indirect
github.com/docker/go-connections v0.6.0 // indirect
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.8.4 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/felixge/fgprof v0.9.5 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gaissmai/bart v0.26.0 // indirect
github.com/geoffgarside/ber v1.1.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.9.1 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-fed/httpsig v1.1.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-pg/zerochecker v0.2.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/certificate-transparency-go v1.3.2 // indirect
github.com/google/go-github/v30 v30.1.0 // indirect
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/gosimple/slug v1.15.0 // indirect
github.com/gosimple/unidecode v1.0.1 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf // indirect
github.com/hdm/jarm-go v0.0.7 // indirect
github.com/iangcarroll/cookiemonster v1.6.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/itchyny/timefmt-go v0.1.6 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
github.com/jcmturner/gofork v1.7.6 // indirect
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368 // indirect
github.com/kaiakz/ubuffer v0.0.0-20200803053910-dd1083087166 // indirect
github.com/kataras/jwt v0.1.10 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/libdns/libdns v1.1.1 // indirect
github.com/logrusorgru/aurora/v4 v4.0.0 // indirect
github.com/lor00x/goldap v0.0.0-20240304151906-8d785c64d1c8 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect
github.com/mackerelio/go-osstat v0.2.6 // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mholt/acmez/v3 v3.1.3 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
github.com/mikelolasagasti/xz v1.0.1 // indirect
github.com/minio/minlz v1.0.1 // indirect
github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.1.0 // indirect
github.com/moby/moby/api v1.53.0 // indirect
github.com/moby/moby/client v0.2.2 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/nwaples/rardecode/v2 v2.2.2 // indirect
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
github.com/olekukonko/errors v1.1.0 // indirect
github.com/olekukonko/ll v0.0.9 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runc v1.2.8 // indirect
github.com/openrdap/rdap v0.9.1 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pierrec/lz4/v4 v4.1.23 // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/projectdiscovery/asnmap v1.1.1 // indirect
github.com/projectdiscovery/blackrock v0.0.1 // indirect
github.com/projectdiscovery/cdncheck v1.2.26 // indirect
github.com/projectdiscovery/freeport v0.0.7 // indirect
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb // indirect
github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 // indirect
github.com/refraction-networking/utls v1.8.2 // indirect
github.com/sashabaranov/go-openai v1.37.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/shirou/gopsutil/v4 v4.25.7 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/tidwall/btree v1.8.1 // indirect
github.com/tidwall/buntdb v1.3.2 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/grect v0.1.4 // indirect
github.com/tidwall/match v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/rtred v0.1.2 // indirect
github.com/tidwall/tinyqueue v0.1.1 // indirect
github.com/tim-ywliu/nested-logrus-formatter v1.3.2 // indirect
github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/vmihailenco/bufpool v0.1.11 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect
github.com/vmihailenco/tagparser v0.1.2 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/vulncheck-oss/go-exploit v1.51.0 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
github.com/ysmood/fetchup v0.2.3 // indirect
github.com/ysmood/got v0.40.0 // indirect
github.com/yuin/goldmark v1.7.13 // indirect
github.com/yuin/goldmark-emoji v1.0.6 // indirect
github.com/zcalusic/sysinfo v1.1.3 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/sync v0.19.0 // indirect
mellium.im/sasl v0.3.2 // indirect
)
require (
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/goburrow/cache v0.1.4 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
github.com/trivago/tgo v1.0.7
github.com/ysmood/goob v0.4.0 // indirect
github.com/ysmood/gson v0.7.3 // indirect
github.com/ysmood/leakless v0.9.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 // indirect
github.com/zmap/zcrypto v0.0.0-20240803002437-3a861682ac77 // indirect
go.etcd.io/bbolt v1.4.3 // indirect
go.uber.org/zap v1.27.0 // indirect
goftp.io/server/v2 v2.0.1 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20250911091902-df9299821621
golang.org/x/mod v0.32.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.41.0
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
gopkg.in/corvus-ch/zbase32.v1 v1.0.0 // indirect
)
require (
github.com/alecthomas/chroma v0.10.0
github.com/go-echarts/go-echarts/v2 v2.6.0
gopkg.in/warnings.v0 v0.1.2 // indirect
)
// https://go.dev/ref/mod#go-mod-file-retract
retract v3.2.0 // retract due to broken js protocol issue
================================================
FILE: go.sum
================================================
aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ=
aead.dev/minisign v0.3.0 h1:8Xafzy5PEVZqYDNP60yJHARlW1eOQtsKNp/Ph2c0vRA=
aead.dev/minisign v0.3.0/go.mod h1:NLvG3Uoq3skkRMDuc3YHpWUTMTrSExqm+Ij73W13F6Y=
carvel.dev/ytt v0.52.0 h1:tkJPL8Gun5snVfypNXbmMKwnbwMyspcTi3Ypyso3nRY=
carvel.dev/ytt v0.52.0/go.mod h1:QgmuU7E15EXW1r2wxTt7zExVz14IHwEG4WNMmaFBkJo=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
code.gitea.io/sdk/gitea v0.17.0 h1:8JPBss4+Jf7AE1YcfyiGrngTXE8dFSG3si/bypsTH34=
code.gitea.io/sdk/gitea v0.17.0/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=
filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a h1:3i+FJ7IpSZHL+VAjtpQeZCRhrpP0odl5XfoLBY4fxJ8=
git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a/go.mod h1:C7hXLmFmPYPjIDGfQl1clsmQ5TMEQfmzWTrJk475bUs=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0 h1:Ma67P/GGprNwsslzEH6+Kb8nybI8jpDTm4Wmzu2ReK8=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0/go.mod h1:c+Lifp3EDEamAkPVzMooRNOK6CZjNSdEnf1A7jsI9u4=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 h1:Wgf5rZba3YZqeTNJPtvqZoBu1sBN/L4sry+u2U3Y75w=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1/go.mod h1:xxCBG/f/4Vbmh2XQJBsOmNdxWUY5j/s27jujKPbQf14=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 h1:nVocQV40OQne5613EeLayJiRAJuKlBGy+m22qWG+WRg=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0/go.mod h1:7QJP7dr2wznCMeqIrhMgWGf7XpAQnVrJqDm9nvV3Cu4=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A=
github.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4=
github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057 h1:KFac3SiGbId8ub47e7kd2PLZeACxc1LkiiNoDOFRClE=
github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057/go.mod h1:iLB2pivrPICvLOuROKmlqURtFIEsoJZaMidQfCG1+D4=
github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 h1:ZbFL+BDfBqegi+/Ssh7im5+aQfBRx6it+kHnC7jaDU8=
github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809/go.mod h1:upgc3Zs45jBDnBT4tVRgRcgm26ABpaP7MoTSdgysca4=
github.com/Mzack9999/go-rsync v0.0.0-20250821180103-81ffa574ef4d h1:DofPB5AcjTnOU538A/YD86/dfqSNTvQsAXgwagxmpu4=
github.com/Mzack9999/go-rsync v0.0.0-20250821180103-81ffa574ef4d/go.mod h1:uzdh/m6XQJI7qRvufeBPDa+lj5SVCJO8B9eLxTbtI5U=
github.com/Mzack9999/goja v0.0.0-20250507184235-e46100e9c697 h1:54I+OF5vS4a/rxnUrN5J3hi0VEYKcrTlpc8JosDyP+c=
github.com/Mzack9999/goja v0.0.0-20250507184235-e46100e9c697/go.mod h1:yNqYRqxYkSROY1J+LX+A0tOSA/6soXQs5m8hZSqYBac=
github.com/Mzack9999/goja_nodejs v0.0.0-20250507184139-66bcbf65c883 h1:+Is1AS20q3naP+qJophNpxuvx1daFOx9C0kLIuI0GVk=
github.com/Mzack9999/goja_nodejs v0.0.0-20250507184139-66bcbf65c883/go.mod h1:K+FhM7iKGKtalkeXGEviafPPwyVjDv1a/ehomabLF2w=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
github.com/RumbleDiscovery/rumble-tools v0.0.0-20201105153123-f2adbb3244d2/go.mod h1:jD2+mU+E2SZUuAOHZvZj4xP4frlOo+N/YrXDvASFhkE=
github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
github.com/akrylysov/pogreb v0.10.2 h1:e6PxmeyEhWyi2AKOBIJzAEi4HkiC+lKyCocRGlnDi78=
github.com/akrylysov/pogreb v0.10.2/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
github.com/alecthomas/kingpin/v2 v2.3.1/go.mod h1:oYL5vtsvEHZGHxU7DMp32Dvx+qL+ptGn6lWaot2vCNE=
github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY=
github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/alexsnet/go-vnc v0.1.0 h1:vBCwPNy79WEL8V/Z5A0ngEFCvTWBAjmS048lkR2rdmY=
github.com/alexsnet/go-vnc v0.1.0/go.mod h1:bbRsg41Sh3zvrnWsw+REKJVGZd8Of2+S0V1G0ZaBhlU=
github.com/alitto/pond v1.9.2 h1:9Qb75z/scEZVCoSU+osVmQ0I0JOeLfdTDafrbcJ8CLs=
github.com/alitto/pond v1.9.2/go.mod h1:xQn3P/sHTYcU/1BR3i86IGIrilcrGC2LiS+E2+CJWsI=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/andygrunwald/go-jira v1.16.1 h1:WoQEar5XoDRAibOgKzTFELlPNlKAtnfWr296R9zdFLA=
github.com/andygrunwald/go-jira v1.16.1/go.mod h1:UQH4IBVxIYWbgagc0LF/k9FRs9xjIiQ8hIcC6HfLwFU=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/antchfx/htmlquery v1.3.5 h1:aYthDDClnG2a2xePf6tys/UyyM/kRcsFRm+ifhFKoU0=
github.com/antchfx/htmlquery v1.3.5/go.mod h1:5oyIPIa3ovYGtLqMPNjBF2Uf25NPCKsMjCnQ8lvjaoA=
github.com/antchfx/xmlquery v1.4.4 h1:mxMEkdYP3pjKSftxss4nUHfjBhnMk4imGoR96FRY2dg=
github.com/antchfx/xmlquery v1.4.4/go.mod h1:AEPEEPYE9GnA2mj5Ur2L5Q5/2PycJ0N9Fusrx9b12fc=
github.com/antchfx/xpath v1.3.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/antchfx/xpath v1.3.5 h1:PqbXLC3TkfeZyakF5eeh3NTWEbYl4VHNVeufANzDbKQ=
github.com/antchfx/xpath v1.3.5/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/aws-sdk-go-v2 v1.36.5 h1:0OF9RiEMEdDdZEMqF9MRjevyxAQcf6gY+E7vwBILFj0=
github.com/aws/aws-sdk-go-v2 v1.36.5/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY=
github.com/aws/aws-sdk-go-v2/config v1.29.17 h1:jSuiQ5jEe4SAMH6lLRMY9OVC+TqJLP5655pBGjmnjr0=
github.com/aws/aws-sdk-go-v2/config v1.29.17/go.mod h1:9P4wwACpbeXs9Pm9w1QTh6BwWwJjwYvJ1iCt5QbCXh8=
github.com/aws/aws-sdk-go-v2/credentials v1.17.70 h1:ONnH5CM16RTXRkS8Z1qg7/s2eDOhHhaXVd72mmyv4/0=
github.com/aws/aws-sdk-go-v2/credentials v1.17.70/go.mod h1:M+lWhhmomVGgtuPOhO85u4pEa3SmssPTdcYpP/5J/xc=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 h1:KAXP9JSHO1vKGCr5f4O6WmlVKLFFXgWYAGoJosorxzU=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32/go.mod h1:h4Sg6FQdexC1yYG9RDnOvLbW1a/P986++/Y/a+GyEM8=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.82 h1:EO13QJTCD1Ig2IrQnoHTRrn981H9mB7afXsZ89WptI4=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.82/go.mod h1:AGh1NCg0SH+uyJamiJA5tTQcql4MMRDXGRdMmCxCXzY=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 h1:SsytQyTMHMDPspp+spo7XwXTP44aJZZAC7fBV2C5+5s=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36/go.mod h1:Q1lnJArKRXkenyog6+Y+zr7WDpk4e6XlR6gs20bbeNo=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 h1:i2vNHQiXUvKhs3quBR6aqlgJaiaexz/aNvdCktW/kAM=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36/go.mod h1:UdyGa7Q91id/sdyHPwth+043HhmP6yP9MBHgbZM0xo8=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 h1:GMYy2EOWfzdP3wfVAGXBNKY5vK4K8vMET4sYOYltmqs=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36/go.mod h1:gDhdAV6wL3PmPqBhiPbnlS447GoWs8HTTOYef9/9Inw=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 h1:nAP2GYbfh8dd2zGZqFRSMlq+/F6cMPBUuCsGAMkN074=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4/go.mod h1:LT10DsiGjLWh4GbjInf9LQejkYEhBgBCjLG5+lvk4EE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 h1:t0E6FzREdtCsiLIoLCWsYliNsRBgyGD/MCK571qk4MI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 h1:qcLWgdhq45sDM9na4cvXax9dyLitn8EYBRl8Ak4XtG4=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17/go.mod h1:M+jkjBFZ2J6DJrjMv2+vkBbuht6kxJYtJiwoVgX4p4U=
github.com/aws/aws-sdk-go-v2/service/s3 v1.82.0 h1:JubM8CGDDFaAOmBrd8CRYNr49ZNgEAiLwGwgNMdS0nw=
github.com/aws/aws-sdk-go-v2/service/s3 v1.82.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 h1:AIRJ3lfb2w/1/8wOOSqYb9fUKGwQbtysJ2H1MofRUPg=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5/go.mod h1:b7SiVprpU+iGazDUqvRSLf5XmCdn+JtT1on7uNL6Ipc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 h1:BpOxT3yhLwSJ77qIY3DoHAQjZsc4HEGfMCE4NGy3uFg=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3/go.mod h1:vq/GQR1gOFLquZMSrxUK/cpvKCNVYibNyJ1m7JrU88E=
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 h1:NFOJ/NXEGV4Rq//71Hs1jC/NvPs1ezajK+yQmkwnPV0=
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0/go.mod h1:7ph2tGpfQvwzgistp2+zga9f+bCjlQJPkPUmMgDSD7w=
github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw=
github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bloom/v3 v3.5.0 h1:AKDvi1V3xJCmSR6QhcBfHbCN4Vf8FfxeWkMNQfmAGhY=
github.com/bits-and-blooms/bloom/v3 v3.5.0/go.mod h1:Y8vrn7nk1tPIlmLtW2ZPV+W7StdVMor6bC1xgpjMZFs=
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4=
github.com/bodgit/sevenzip v1.6.1/go.mod h1:GVoYQbEVbOGT8n2pfqCIMRUaRjQ8F9oSqoBEqZh5fQ8=
github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
github.com/brianvoe/gofakeit/v7 v7.2.1 h1:AGojgaaCdgq4Adzrd2uWdbGNDyX6MWNhHdQBraNfOHI=
github.com/brianvoe/gofakeit/v7 v7.2.1/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx63i+F5ic=
github.com/caddyserver/certmagic v0.25.0/go.mod h1:m9yB7Mud24OQbPHOiipAoyKPn9pKHhpSJxXR1jydBxA=
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/censys/censys-sdk-go v0.19.1 h1:CG8rQKgwrKuoICd3oU0uddALMfJnboeMkDg/e74HYyc=
github.com/censys/censys-sdk-go v0.19.1/go.mod h1:DgPz5NgL+EfoueXLPG9UG1e7hS0OhtlywgpkIuu3ZRE=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI=
github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI=
github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY=
github.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk=
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE=
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA=
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/exp/slice v0.0.0-20250908092851-c2208eb08494 h1:O5se1NwLfawEafCaxy3HztOFWgXlYgtLDQnjTTuRsBI=
github.com/charmbracelet/x/exp/slice v0.0.0-20250908092851-c2208eb08494/go.mod h1:vI5nDVMWi6veaYH+0Fmvpbe/+cv/iJfMntdh+N0+Tms=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/cheggaaa/pb/v3 v3.1.7 h1:2FsIW307kt7A/rz/ZI2lvPO+v3wKazzE4K/0LtTWsOI=
github.com/cheggaaa/pb/v3 v3.1.7/go.mod h1:/Ji89zfVPeC/u5j8ukD0MBPHt2bzTYp74lQ7KlgFWTQ=
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cfssl v1.6.4 h1:NMOvfrEjFfC63K3SGXgAnFdsgkmiq4kATme5BfcqrO8=
github.com/cloudflare/cfssl v1.6.4/go.mod h1:8b3CQMxfWPAeom3zBnGJ6sd+G1NkL5TXqmDXacb+1J0=
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a h1:Ohw57yVY2dBTt+gsC6aZdteyxwlxfbtgkFEMTEkwgSw=
github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4=
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/cyphar/filepath-securejoin v0.5.1 h1:eYgfMq5yryL4fbWfkLpFFy2ukSELzaJOTaUTuh+oF48=
github.com/cyphar/filepath-securejoin v0.5.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c h1:+Zo5Ca9GH0RoeVZQKzFJcTLoAixx5s5Gq3pTIS+n354=
github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c/go.mod h1:HJGU9ULdREjOcVGZVPB5s6zYmHi1RxzT71l2wQyLmnE=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM=
github.com/docker/cli v29.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731 h1:R/ZjJpjQKsZ6L/+Gf9WHbt31GG8NMVcpRqUE+1mMIyo=
github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731/go.mod h1:M9R1FoZ3y//hwwnJtO51ypFGwm8ZfpxPT/ZLtO1mcgQ=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b h1:XMw3j+4AEXLeL/uyiZ7/qYE1X7Ul05RTwWBhzxCLi+0=
github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b/go.mod h1:l2Jrml4vojDomW5jdDJhIS60KdbrE9uPYhyAq/7OnF4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gaissmai/bart v0.26.0 h1:xOZ57E9hJLBiQaSyeZa9wgWhGuzfGACgqp4BE77OkO0=
github.com/gaissmai/bart v0.26.0/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c=
github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w=
github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc=
github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk=
github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-echarts/go-echarts/v2 v2.6.0 h1:4wEquGT/I7lipHnOCh/z3qa8E4dY0SYFdEEnaTzzzvU=
github.com/go-echarts/go-echarts/v2 v2.6.0/go.mod h1:56YlvzhW/a+du15f3S2qUGNDfKnFOeJSThBIrVFHDtI=
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s=
github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU=
github.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-pdf/fpdf v0.9.0 h1:PPvSaUuo1iMi9KkaAn90NuKi+P4gwMedWPHhj8YlJQw=
github.com/go-pdf/fpdf v0.9.0/go.mod h1:oO8N111TkmKb9D7VvWGLvLJlaZUQVPM+6V42pp3iV4Y=
github.com/go-pg/pg/v10 v10.15.0 h1:6DQwbaxJz/e4wvgzbxBkBLiL/Uuk87MGgHhkURtzx24=
github.com/go-pg/pg/v10 v10.15.0/go.mod h1:FIn/x04hahOf9ywQ1p68rXqaDVbTRLYlu4MQR0lhoB8=
github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU=
github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA=
github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg=
github.com/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q=
github.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goburrow/cache v0.1.4 h1:As4KzO3hgmzPlnaMniZU9+VmoNYseUhuELbxy9mRBfw=
github.com/goburrow/cache v0.1.4/go.mod h1:cDFesZDnIlrHoNlMYqqMpCRawuXulgx+y7mXU8HZ+/c=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/certificate-transparency-go v1.3.2 h1:9ahSNZF2o7SYMaKaXhAumVEzXB2QaayzII9C8rv7v+A=
github.com/google/certificate-transparency-go v1.3.2/go.mod h1:H5FpMUaGa5Ab2+KCYsxg6sELw3Flkl7pGZzWdBoYLXs=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo=
github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8=
github.com/google/go-github/v50 v50.2.0/go.mod h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H4m6xFZlT43q8Q=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo=
github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf h1:umfGUaWdFP2s6457fz1+xXYIWDxdGc7HdkLS9aJ1skk=
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf/go.mod h1:V99KdStnMHZsvVOwIvhfcUzYgYkRZeQWUtumtL+SKxA=
github.com/hdm/jarm-go v0.0.7 h1:Eq0geenHrBSYuKrdVhrBdMMzOmA+CAMLzN2WrF3eL6A=
github.com/hdm/jarm-go v0.0.7/go.mod h1:kinGoS0+Sdn1Rr54OtanET5E5n7AlD6T6CrJAKDjJSQ=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iangcarroll/cookiemonster v1.6.0 h1:NPFkn/ZZYZgzXhJ1awRnYhZ3fJK3hKWgbctfTW21kew=
github.com/iangcarroll/cookiemonster v1.6.0/go.mod h1:n3MvoAq56NkNyCEyhcYs3ZJMzTc9rL3w7IaITI0apMg=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
github.com/itchyny/gojq v0.12.17 h1:8av8eGduDb5+rvEdaOO+zQUjA04MS0m3Ps8HiD+fceg=
github.com/itchyny/gojq v0.12.17/go.mod h1:WBrEMkgAfAGO1LUcGOckBl5O726KPp+OlkKug0I/FEY=
github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q=
github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg=
github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf/go.mod h1:lli8NYPQOFy3O++YmYbqVgOcQ1JPCwdOy+5zSjKJ9qY=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/k14s/difflib v0.0.0-20201117154628-0c031775bf57 h1:CwBRArr+BWBopnUJhDjJw86rPL/jGbEjfHWKzTasSqE=
github.com/k14s/difflib v0.0.0-20201117154628-0c031775bf57/go.mod h1:B0xN2MiNBGWOWi9CcfAo9LBI8IU4J1utlbOIJCsmKr4=
github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368 h1:4bcRTTSx+LKSxMWibIwzHnDNmaN1x52oEpvnjCy+8vk=
github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368/go.mod h1:lKGj1op99m4GtQISxoD2t+K+WO/q2NzEPKvfXFQfbCA=
github.com/kaiakz/ubuffer v0.0.0-20200803053910-dd1083087166 h1:IAukUBAVLUWBcexOYgkTD/EjMkfnNos7g7LFpyIdHJI=
github.com/kaiakz/ubuffer v0.0.0-20200803053910-dd1083087166/go.mod h1:T4xUEny5PVedYIbkMAKYEBjMyDsOvvP0qK4s324AKA8=
github.com/kataras/jwt v0.1.10 h1:GBXOF9RVInDPhCFBiDumRG9Tt27l7ugLeLo8HL5SeKQ=
github.com/kataras/jwt v0.1.10/go.mod h1:xkimAtDhU/aGlQqjwvgtg+VyuPwMiyZHaY8LJRh0mYo=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kitabisa/go-ci v1.0.3 h1:JmIUIvcercRQc/9x/v02ydCCqU4MadSHaNaOF8T2pGA=
github.com/kitabisa/go-ci v1.0.3/go.mod h1:e3wBSzaJbcifXrr/Gw2ZBLn44MmeqP5WySwXyHlCK/U=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA=
github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa h1:KQKuQDgA3DZX6C396lt3WDYB9Um1gLITLbvficVbqXk=
github.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa/go.mod h1:HbwNE4XGwjgtUELkvQaAOjWrpianHYZdQVNqSdYW3UM=
github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=
github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/logrusorgru/aurora/v4 v4.0.0 h1:sRjfPpun/63iADiSvGGjgA1cAYegEWMPCJdUpJYn9JA=
github.com/logrusorgru/aurora/v4 v4.0.0/go.mod h1:lP0iIa2nrnT/qoFXcOZSrZQpJ1o6n2CUf/hyHi2Q4ZQ=
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3/go.mod h1:37YR9jabpiIxsb8X9VCIx8qFOjTDIIrIHHODa8C4gz0=
github.com/lor00x/goldap v0.0.0-20240304151906-8d785c64d1c8 h1:z9RDOBcFcf3f2hSfKuoM3/FmJpt8M+w0fOy4wKneBmc=
github.com/lor00x/goldap v0.0.0-20240304151906-8d785c64d1c8/go.mod h1:37YR9jabpiIxsb8X9VCIx8qFOjTDIIrIHHODa8C4gz0=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg=
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mackerelio/go-osstat v0.2.6 h1:gs4U8BZeS1tjrL08tt5VUliVvSWP26Ai2Ob8Lr7f2i0=
github.com/mackerelio/go-osstat v0.2.6/go.mod h1:lRy8V9ZuHpuRVZh+vyTkODeDPl3/d5MgXHtLSaqG8bA=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/maypok86/otter/v2 v2.2.1 h1:hnGssisMFkdisYcvQ8L019zpYQcdtPse+g0ps2i7cfI=
github.com/maypok86/otter/v2 v2.2.1/go.mod h1:1NKY9bY+kB5jwCXBJfE59u+zAwOt6C7ni1FTlFFMqVs=
github.com/mholt/acmez/v3 v3.1.3 h1:gUl789rjbJSuM5hYzOFnNaGgWPV1xVfnOs59o0dZEcc=
github.com/mholt/acmez/v3 v3.1.3/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=
github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/microsoft/go-mssqldb v1.9.2 h1:nY8TmFMQOHpm2qVWo6y4I2mAmVdZqlGiMGAYt64Ibbs=
github.com/microsoft/go-mssqldb v1.9.2/go.mod h1:GBbW9ASTiDC+mpgWDGKdm3FnFLTUsLYN3iFL90lQ+PA=
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
github.com/minio/minio-go/v6 v6.0.46/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=
github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A=
github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 h1:yRZGarbxsRytL6EGgbqK2mCY+Lk5MWKQYKJT2gEglhc=
github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
github.com/moby/moby/api v1.53.0 h1:PihqG1ncw4W+8mZs69jlwGXdaYBeb5brF6BL7mPIS/w=
github.com/moby/moby/api v1.53.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
github.com/moby/moby/client v0.2.2 h1:Pt4hRMCAIlyjL3cr8M5TrXCwKzguebPAc2do2ur7dEM=
github.com/moby/moby/client v0.2.2/go.mod h1:2EkIPVNCqR05CMIzL1mfA07t0HvVUUOl85pasRz/GmQ=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nwaples/rardecode/v2 v2.2.2 h1:/5oL8dzYivRM/tqX9VcTSWfbpwcbwKG1QtSJr3b3KcU=
github.com/nwaples/rardecode/v2 v2.2.2/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
github.com/olekukonko/tablewriter v1.0.8 h1:f6wJzHg4QUtJdvrVPKco4QTrAylgaU0+b9br/lJxEiQ=
github.com/olekukonko/tablewriter v1.0.8/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/opencontainers/runc v1.2.8 h1:RnEICeDReapbZ5lZEgHvj7E9Q3Eex9toYmaGBsbvU5Q=
github.com/opencontainers/runc v1.2.8/go.mod h1:cC0YkmZcuvr+rtBZ6T7NBoVbMGNAdLa/21vIElJDOzI=
github.com/openrdap/rdap v0.9.1 h1:Rv6YbanbiVPsKRvOLdUmlU1AL5+2OFuEFLjFN+mQsCM=
github.com/openrdap/rdap v0.9.1/go.mod h1:vKSiotbsENrjM/vaHXLddXbW8iQkBfa+ldEuYEjyLTQ=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw=
github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pierrec/lz4/v4 v4.1.23 h1:oJE7T90aYBGtFNrI8+KbETnPymobAhzRrR8Mu8n1yfU=
github.com/pierrec/lz4/v4 v4.1.23/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/praetorian-inc/fingerprintx v1.1.15 h1:CVIxrIQARbmdk5h8E9tIJZbvFoY2sGLLG9rpFVfQqpA=
github.com/praetorian-inc/fingerprintx v1.1.15/go.mod h1:hqRroITBwKpP8BOGF+n/A+qv9wSF7OSVinmu5NCyOUI=
github.com/projectdiscovery/asnmap v1.1.1 h1:ImJiKIaACOT7HPx4Pabb5dksolzaFYsD1kID2iwsDqI=
github.com/projectdiscovery/asnmap v1.1.1/go.mod h1:QT7jt9nQanj+Ucjr9BqGr1Q2veCCKSAVyUzLXfEcQ60=
github.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k0VbGJyft6LQ=
github.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss=
github.com/projectdiscovery/cdncheck v1.2.26 h1:0iLVppSfXDHWu/jPlDJGTsyX+qziQgO34qjctkXfGyc=
github.com/projectdiscovery/cdncheck v1.2.26/go.mod h1:Y1KQmACY+AifbuPX/W7o8lWssiWmAZ5d/KG8qkmFm9I=
github.com/projectdiscovery/clistats v0.1.1 h1:8mwbdbwTU4aT88TJvwIzTpiNeow3XnAB72JIg66c8wE=
github.com/projectdiscovery/clistats v0.1.1/go.mod h1:4LtTC9Oy//RiuT1+76MfTg8Hqs7FQp1JIGBM3nHK6a0=
github.com/projectdiscovery/dsl v0.8.13 h1:HjjHta7c02saH2tUGs8CN5vDeE2MyWvCV32koT8ZCWs=
github.com/projectdiscovery/dsl v0.8.13/go.mod h1:hgFaXhz/JuO+HqIXqBqYIR3ntPnqTo38MJJAzb5tIbg=
github.com/projectdiscovery/fastdialer v0.5.4 h1:+0oesDDqZcIPE5bNDmm/Xm9Xm3yjnhl4xwP+h5D1TE4=
github.com/projectdiscovery/fastdialer v0.5.4/go.mod h1:KCzt6WnSAj9umiUBRCaC0EJSEyeshxDoowfwjxodmQw=
github.com/projectdiscovery/fasttemplate v0.0.2 h1:h2cISk5xDhlJEinlBQS6RRx0vOlOirB2y3Yu4PJzpiA=
github.com/projectdiscovery/fasttemplate v0.0.2/go.mod h1:XYWWVMxnItd+r0GbjA1GCsUopMw1/XusuQxdyAIHMCw=
github.com/projectdiscovery/freeport v0.0.7 h1:Q6uXo/j8SaV/GlAHkEYQi8WQoPXyJWxyspx+aFmz9Qk=
github.com/projectdiscovery/freeport v0.0.7/go.mod h1:cOhWKvNBe9xM6dFJ3RrrLvJ5vXx2NQ36SecuwjenV2k=
github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c h1:s+lLAlrOrgwlPZQ9DFqNw+kia2nteKnJZ2Ek313yoUc=
github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c/go.mod h1:rN35/D3lVx2YDeENFFz06uj8j3XIqK1Ym9XcISF5fzg=
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb h1:rutG906Drtbpz4DwU5mhGIeOhRcktDH4cGQitGUMAsg=
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb/go.mod h1:FLjF1DmZ+POoGEiIQdWuYVwS++C/GwpX8YaCsTSm1RY=
github.com/projectdiscovery/goflags v0.1.74 h1:n85uTRj5qMosm0PFBfsvOL24I7TdWRcWq/1GynhXS7c=
github.com/projectdiscovery/goflags v0.1.74/go.mod h1:UMc9/7dFz2oln+10tv6cy+7WZKTHf9UGhaNkF95emh4=
github.com/projectdiscovery/gologger v1.1.68 h1:KfdIO/3X7BtHssWZuqhxPZ+A946epCCx2cz+3NnRAnU=
github.com/projectdiscovery/gologger v1.1.68/go.mod h1:Xae0t4SeqJVa0RQGK9iECx/+HfXhvq70nqOQp2BuW+o=
github.com/projectdiscovery/gostruct v0.0.2 h1:s8gP8ApugGM4go1pA+sVlPDXaWqNP5BBDDSv7VEdG1M=
github.com/projectdiscovery/gostruct v0.0.2/go.mod h1:H86peL4HKwMXcQQtEa6lmC8FuD9XFt6gkNR0B/Mu5PE=
github.com/projectdiscovery/gozero v0.1.1-0.20251027191944-a4ea43320b81 h1:yHh46pJovYbyiaHCV7oIDinFmy+Fyq36H1BowJgb0M0=
github.com/projectdiscovery/gozero v0.1.1-0.20251027191944-a4ea43320b81/go.mod h1:9lmGPBDGZVANzCGjQg+V32n8Y3Cgjo/4kT0E88lsVTI=
github.com/projectdiscovery/hmap v0.0.100 h1:DBZ3Req9lWf4P1YC9PRa4eiMvLY0Uxud43NRBcocPfs=
github.com/projectdiscovery/hmap v0.0.100/go.mod h1:2O06pR8pHOP9wSmxAoxuM45U7E+UqOqOdlSIeddM0bA=
github.com/projectdiscovery/httpx v1.9.0 h1:5yn4ik/LqZ+v3MLgU7+CZJQyND9osW9NmZ3squylxsc=
github.com/projectdiscovery/httpx v1.9.0/go.mod h1:jGTRyUHddo2WyK4klWIwQXgGF1Lu39XVyzlue4H3pX8=
github.com/projectdiscovery/interactsh v1.3.1 h1:5HzeVGVCAX/cjTguJ+7ClOmML5r97Ty7op9s+/F7BiM=
github.com/projectdiscovery/interactsh v1.3.1/go.mod h1:MXQ11EoBPROb4bEw+WP9e4DX4fMhrpS6EwfMfZomBsw=
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb h1:MGtI4oE12ruWv11ZlPXXd7hl/uAaQZrFvrIDYDeVMd8=
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb/go.mod h1:vmgC0DTFCfoCLp0RAfsfYTZZan0QMVs+cmTbH6blfjk=
github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 h1:eR+0HE//Ciyfwy3HC7fjRyKShSJHYoX2Pv7pPshjK/Q=
github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582/go.mod h1:3G3BRKui7nMuDFAZKR/M2hiOLtaOmyukT20g88qRQjI=
github.com/projectdiscovery/mapcidr v1.1.97 h1:7FkxNNVXp+m1rIu5Nv/2SrF9k4+LwP8QuWs2puwy+2w=
github.com/projectdiscovery/mapcidr v1.1.97/go.mod h1:9dgTJh1SP02gYZdpzMjm6vtYFkEHQHoTyaVNvaeJ7lA=
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5 h1:L/e8z8yw1pfT6bg35NiN7yd1XKtJap5Nk6lMwQ0RNi8=
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5/go.mod h1:pGW2ncnTxTxHtP9wzcIJAB+3/NMp6IiuQWd2NK7K+oc=
github.com/projectdiscovery/networkpolicy v0.1.34 h1:TRwNbgMwdx3NC190TKSLwtTvr0JAIZAlnWkOhW0yBME=
github.com/projectdiscovery/networkpolicy v0.1.34/go.mod h1:GJ20E7fJoA2vk8ZBSa1Cvc5WyP8RxglF5bZmYgK8jag=
github.com/projectdiscovery/ratelimit v0.0.83 h1:hfb36QvznBrjA4FNfpFE8AYRVBYrfJh8qHVROLQgl54=
github.com/projectdiscovery/ratelimit v0.0.83/go.mod h1:z076BrLkBb5yS7uhHNoCTf8X/BvFSGRxwQ8EzEL9afM=
github.com/projectdiscovery/rawhttp v0.1.90 h1:LOSZ6PUH08tnKmWsIwvwv1Z/4zkiYKYOSZ6n+8RFKtw=
github.com/projectdiscovery/rawhttp v0.1.90/go.mod h1:VZYAM25UI/wVB3URZ95ZaftgOnsbphxyAw/XnQRRz4Y=
github.com/projectdiscovery/rdap v0.9.0 h1:wPhHx5pQ2QI+WGhyNb2PjhTl0NtB39Nk7YFZ9cp8ZGA=
github.com/projectdiscovery/rdap v0.9.0/go.mod h1:zk4yrJFQ2Hy36Aqk+DvotYQxYAeALaCJ5ORySkff36Q=
github.com/projectdiscovery/retryabledns v1.0.113 h1:s+DAzdJ8XhLxRgt5636H0HG9OqHsGRjX9wTrLSTMqlQ=
github.com/projectdiscovery/retryabledns v1.0.113/go.mod h1:+DyanDr8naxQ2dRO9c4Ezo3NHHXhz8L0tTSRYWhiwyA=
github.com/projectdiscovery/retryablehttp-go v1.3.6 h1:dLb0/YVX+oX70gpWxN5GXT8pCKpn8fdXfwOq2TsXxNY=
github.com/projectdiscovery/retryablehttp-go v1.3.6/go.mod h1:tKVxmL4ixWy1MjYk5GJvFL0Cp10fnQgSp2F6bSBEypI=
github.com/projectdiscovery/sarif v0.0.1 h1:C2Tyj0SGOKbCLgHrx83vaE6YkzXEVrMXYRGLkKCr/us=
github.com/projectdiscovery/sarif v0.0.1/go.mod h1:cEYlDu8amcPf6b9dSakcz2nNnJsoz4aR6peERwV+wuQ=
github.com/projectdiscovery/stringsutil v0.0.2 h1:uzmw3IVLJSMW1kEg8eCStG/cGbYYZAja8BH3LqqJXMA=
github.com/projectdiscovery/stringsutil v0.0.2/go.mod h1:EJ3w6bC5fBYjVou6ryzodQq37D5c6qbAYQpGmAy+DC0=
github.com/projectdiscovery/tlsx v1.2.2 h1:Y96QBqeD2anpzEtBl4kqNbwzXh2TrzJuXfgiBLvK+SE=
github.com/projectdiscovery/tlsx v1.2.2/go.mod h1:ZJl9F1sSl0sdwE+lR0yuNHVX4Zx6tCSTqnNxnHCFZB4=
github.com/projectdiscovery/uncover v1.2.0 h1:31tjYa0v8FB8Ch8hJTxb+2t63vsljdOo0OSFylJcX4M=
github.com/projectdiscovery/uncover v1.2.0/go.mod h1:ozqKb++p39Kmh1SmwIpbQ9p0aVGPXuwsb4/X2Kvx6ms=
github.com/projectdiscovery/useragent v0.0.107 h1:45gSBda052fv2Gtxtnpx7cu2rWtUpZEQRGAoYGP6F5M=
github.com/projectdiscovery/useragent v0.0.107/go.mod h1:yv5ZZLDT/kq6P+NvBcCPq6sjEVQtZGgO+OvvHzZ+WtY=
github.com/projectdiscovery/utils v0.9.0 h1:eu9vdbP0VYXI9nGSLfnOpUqBeW9/B/iSli7U8gPKZw8=
github.com/projectdiscovery/utils v0.9.0/go.mod h1:zcVu1QTlMi5763qCol/L3ROnbd/UPSBP8fI5PmcnF6s=
github.com/projectdiscovery/wappalyzergo v0.2.71 h1:MdENrw/8a1qrxjqIJGbFktDiqVLeaMq7AEIJPMO0JGY=
github.com/projectdiscovery/wappalyzergo v0.2.71/go.mod h1:Oc+U2RPJObmpi6LW5lTMEDiKagcKZNkEfZfwrVMURa0=
github.com/projectdiscovery/yamldoc-go v1.0.6 h1:GCEdIRlQjDux28xTXKszM7n3jlMf152d5nqVpVoetas=
github.com/projectdiscovery/yamldoc-go v1.0.6/go.mod h1:R5lWrNzP+7Oyn77NDVPnBsxx2/FyQZBBkIAaSaCQFxw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs=
github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7Kyl5E=
github.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/sashabaranov/go-openai v1.37.0 h1:hQQowgYm4OXJ1Z/wTrE+XZaO20BYsL0R3uRPSpfNZkY=
github.com/sashabaranov/go-openai v1.37.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM=
github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0=
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE=
github.com/sijms/go-ora/v2 v2.9.0 h1:+iQbUeTeCOFMb5BsOMgUhV8KWyrv9yjKpcK4x7+MFrg=
github.com/sijms/go-ora/v2 v2.9.0/go.mod h1:QgFInVi3ZWyqAiJwzBQA+nbKYKH77tdp1PYoCqhR2dU=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9 h1:GXIyLuIJ5Qk46lI8WJ83qHBZKUI3zhmMmuoY9HICUIQ=
github.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9/go.mod h1:uQdBQGrE1fZ2EyOs0pLcCDd1bBV4rSThieuIIGhXZ50=
github.com/testcontainers/testcontainers-go v0.38.0 h1:d7uEapLcv2P8AvH8ahLqDMMxda2W9gQN1nRbHS28HBw=
github.com/testcontainers/testcontainers-go v0.38.0/go.mod h1:C52c9MoHpWO+C4aqmgSU+hxlR5jlEayWtgYrb8Pzz1w=
github.com/testcontainers/testcontainers-go/modules/mongodb v0.37.0 h1:drGy4LJOVkIKpKGm1YKTfVzb1qRhN/konVpmuUphq0k=
github.com/testcontainers/testcontainers-go/modules/mongodb v0.37.0/go.mod h1:e9/4dGJfSZW59/kXGf/ksrEvA+BqP/daax0Usp2cpsM=
github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI=
github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8=
github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA=
github.com/tidwall/btree v1.8.1/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A=
github.com/tidwall/buntdb v1.3.2 h1:qd+IpdEGs0pZci37G4jF51+fSKlkuUTMXuHhXL1AkKg=
github.com/tidwall/buntdb v1.3.2/go.mod h1:lZZrZUWzlyDJKlLQ6DKAy53LnG7m5kHyrEHvvcDmBpU=
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg=
github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q=
github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8=
github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8=
github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ=
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=
github.com/tim-ywliu/nested-logrus-formatter v1.3.2 h1:jugNJ2/CNCI79SxOJCOhwUHeN3O7/7/bj+ZRGOFlCSw=
github.com/tim-ywliu/nested-logrus-formatter v1.3.2/go.mod h1:oGPmcxZB65j9Wo7mCnQKSrKEJtVDqyjD666SGmyStXI=
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM=
github.com/trivago/tgo v1.0.7/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=
github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94=
github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ=
github.com/vmihailenco/msgpack/v5 v5.3.4 h1:qMKAwOV+meBw2Y8k9cVwAy7qErtYCwBzZ2ellBfvnqc=
github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/vulncheck-oss/go-exploit v1.51.0 h1:HTmJ4Q94tbEDPb35mQZn6qMg4rT+Sw9n+L7g3Pjr+3o=
github.com/vulncheck-oss/go-exploit v1.51.0/go.mod h1:J28w0dLnA6DnCrnBm9Sbt6smX8lvztnnN2wCXy7No6c=
github.com/weppos/publicsuffix-go v0.12.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
github.com/weppos/publicsuffix-go v0.30.0/go.mod h1:kBi8zwYnR0zrbm8RcuN1o9Fzgpnnn+btVN8uWPMyXAY=
github.com/weppos/publicsuffix-go v0.40.2/go.mod h1:XsLZnULC3EJ1Gvk9GVjuCTZ8QUu9ufE4TZpOizDShko=
github.com/weppos/publicsuffix-go v0.50.3 h1:eT5dcjHQcVDNc0igpFEsGHKIip30feuB2zuuI9eJxiE=
github.com/weppos/publicsuffix-go v0.50.3/go.mod h1:/rOa781xBykZhHK/I3QeHo92qdDKVmKZKF7s8qAEM/4=
github.com/weppos/publicsuffix-go/publicsuffix/generator v0.0.0-20220927085643-dc0d00c92642/go.mod h1:GHfoeIdZLdZmLjMlzBftbTDntahTttUMWjxZwQJhULE=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xhit/go-str2duration v1.2.0 h1:BcV5u025cITWxEQKGWr1URRzrcXtu7uk8+luz3Yuhwc=
github.com/xhit/go-str2duration v1.2.0/go.mod h1:3cPSlfZlUHVlneIVfePFWcJZsuwf+P1v2SRTV4cUmp4=
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yassinebenaid/godump v0.11.1 h1:SPujx/XaYqGDfmNh7JI3dOyCUVrG0bG2duhO3Eh2EhI=
github.com/yassinebenaid/godump v0.11.1/go.mod h1:dc/0w8wmg6kVIvNGAzbKH1Oa54dXQx8SNKh4dPRyW44=
github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU=
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ=
github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns=
github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ=
github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18=
github.com/ysmood/gop v0.2.0 h1:+tFrG0TWPxT6p9ZaZs+VY+opCvHU8/3Fk6BaNv6kqKg=
github.com/ysmood/gop v0.2.0/go.mod h1:rr5z2z27oGEbyB787hpEcx4ab8cCiPnKxn0SUHt6xzk=
github.com/ysmood/got v0.40.0 h1:ZQk1B55zIvS7zflRrkGfPDrPG3d7+JOza1ZkNxcc74Q=
github.com/ysmood/got v0.40.0/go.mod h1:W7DdpuX6skL3NszLmAsC5hT7JAhuLZhByVzHTq874Qg=
github.com/ysmood/gotrace v0.6.0 h1:SyI1d4jclswLhg7SWTL6os3L1WOKeNn/ZtzVQF8QmdY=
github.com/ysmood/gotrace v0.6.0/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM=
github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE=
github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg=
github.com/ysmood/leakless v0.9.0 h1:qxCG5VirSBvmi3uynXFkcnLMzkphdh3xx5FtrORwDCU=
github.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs=
github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zcalusic/sysinfo v1.1.3 h1:u/AVENkuoikKuIZ4sUEJ6iibpmQP6YpGD8SSMCrqAF0=
github.com/zcalusic/sysinfo v1.1.3/go.mod h1:NX+qYnWGtJVPV0yWldff9uppNKU4h40hJIRPf/pGLv4=
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 h1:Nzukz5fNOBIHOsnP+6I79kPx3QhLv8nBy2mfFhBRq30=
github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
github.com/zmap/zcertificate v0.0.1/go.mod h1:q0dlN54Jm4NVSSuzisusQY0hqDWvu92C+TWveAxiVWk=
github.com/zmap/zcrypto v0.0.0-20201128221613-3719af1573cf/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ=
github.com/zmap/zcrypto v0.0.0-20201211161100-e54a5822fb7e/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ=
github.com/zmap/zcrypto v0.0.0-20230310154051-c8b263fd8300/go.mod h1:mOd4yUMgn2fe2nV9KXsa9AyQBFZGzygVPovsZR+Rl5w=
github.com/zmap/zcrypto v0.0.0-20240803002437-3a861682ac77 h1:DCz0McWRVJNICkHdu2XpETqeLvPtZXs315OZyUs1BDk=
github.com/zmap/zcrypto v0.0.0-20240803002437-3a861682ac77/go.mod h1:aSvf+uTU222mUYq/KQj3oiEU7ajhCZe8RRSLHIoM4EM=
github.com/zmap/zflags v1.4.0-beta.1.0.20200204220219-9d95409821b6/go.mod h1:HXDUD+uue8yeLHr0eXx1lvY6CvMiHbTKw5nGmA9OUoo=
github.com/zmap/zgrab2 v0.1.8 h1:PFnXrIBcGjYFec1JNbxMKQuSXXzS+SbqE89luuF4ORY=
github.com/zmap/zgrab2 v0.1.8/go.mod h1:5d8HSmUwvllx4q1qG50v/KXphkg45ZzWdaQtgTFnegE=
github.com/zmap/zlint/v3 v3.0.0/go.mod h1:paGwFySdHIBEMJ61YjoqT4h7Ge+fdYG4sUQhnTb1lJ8=
gitlab.com/gitlab-org/api/client-go v0.130.1 h1:1xF5C5Zq3sFeNg3PzS2z63oqrxifne3n/OnbI7nptRc=
gitlab.com/gitlab-org/api/client-go v0.130.1/go.mod h1:ZhSxLAWadqP6J9lMh40IAZOlOxBLPRh7yFOXR/bMJWM=
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU=
go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE=
go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
goftp.io/server/v2 v2.0.1 h1:H+9UbCX2N206ePDSVNCjBftOKOgil6kQ5RAQNx5hJwE=
goftp.io/server/v2 v2.0.1/go.mod h1:7+H/EIq7tXdfo1Muu5p+l3oQ6rYkDZ8lY7IM5d5kVdQ=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 h1:Pw6WnI9W/LIdRxqK7T6XGugGbHIRl5Q7q3BssH6xk4s=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/corvus-ch/zbase32.v1 v1.0.0 h1:K4u1NprbDNvKPczKfHLbwdOWHTZ0zfv2ow71H1nRnFU=
gopkg.in/corvus-ch/zbase32.v1 v1.0.0/go.mod h1:T3oKkPOm4AV/bNXCNFUxRmlE9RUyBz/DSo0nK9U+c0Y=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
mellium.im/sasl v0.3.2 h1:PT6Xp7ccn9XaXAnJ03FcEjmAn7kK1x7aoXV6F+Vmrl0=
mellium.im/sasl v0.3.2/go.mod h1:NKXDi1zkr+BlMHLQjY3ofYuU4KSPFxknb8mfEu6SveY=
moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8=
moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE=
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
================================================
FILE: helm/Chart.yaml
================================================
apiVersion: v2
name: nuclei
description: A Helm chart for Nuclei
type: application
version: 0.1.0
appVersion: "2.5.7"
================================================
FILE: helm/templates/NOTES.txt
================================================
1. Get the application URL by running these commands:
{{- if .Values.interactsh.ingress.enabled }}
{{- range $host := .Values.interactsh.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.interactsh.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.interactsh.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "nuclei.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.interactsh.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "nuclei.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "nuclei.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.interactsh.service.port }}
{{- else if contains "ClusterIP" .Values.interactsh.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "nuclei.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}
================================================
FILE: helm/templates/_helpers.tpl
================================================
{{/*
Expand the name of the chart.
*/}}
{{- define "nuclei.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "nuclei.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "nuclei.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "nuclei.labels" -}}
helm.sh/chart: {{ include "nuclei.chart" . }}
{{ include "nuclei.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "nuclei.selectorLabels" -}}
app.kubernetes.io/name: {{ include "nuclei.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "nuclei.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "nuclei.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
================================================
FILE: helm/templates/hpa.yaml
================================================
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "nuclei.fullname" . }}
labels:
{{- include "nuclei.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "nuclei.fullname" . }}-interactsh
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}
================================================
FILE: helm/templates/interactsh-deployment.yaml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "nuclei.fullname" . }}-interactsh
labels:
{{- include "nuclei.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "nuclei.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "nuclei.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "nuclei.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}-interactsh
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.interactsh.image.repository }}:{{ .Values.interactsh.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.interactsh.image.pullPolicy }}
command: ["interactsh-server", "-skip-acme", "-d", "{{ .Values.interactsh.service.name }}"]
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
================================================
FILE: helm/templates/interactsh-ingress.yaml
================================================
{{- if .Values.interactsh.ingress.enabled -}}
{{- $fullName := include "nuclei.fullname" . -}}
{{- $svcPort := .Values.interactsh.service.port -}}
{{- $svcName := .Values.interactsh.service.name -}}
{{- if and .Values.interactsh.ingress.className (not (semverCompare ">=1.20-0" .Capabilities.KubeVersion.GitVersion)) }}
{{- if not (hasKey .Values.interactsh.ingress.annotations "kubernetes.io/ingress.class") }}
{{- $_ := set .Values.interactsh.ingress.annotations "kubernetes.io/ingress.class" .Values.interactsh.ingress.className}}
{{- end }}
{{- end }}
{{- if semverCompare ">=1.20-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "nuclei.labels" . | nindent 4 }}
{{- with .Values.interactsh.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if and .Values.interactsh.ingress.className (semverCompare ">=1.20-0" .Capabilities.KubeVersion.GitVersion) }}
ingressClassName: {{ .Values.interactsh.ingress.className }}
{{- end }}
{{- if .Values.interactsh.ingress.tls }}
tls:
{{- range .Values.interactsh.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.interactsh.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{- if and .pathType (semverCompare ">=1.20-0" $.Capabilities.KubeVersion.GitVersion) }}
pathType: {{ .pathType }}
{{- end }}
backend:
{{- if semverCompare ">=1.20-0" $.Capabilities.KubeVersion.GitVersion }}
service:
name: {{ $svcName }}
port:
number: {{ $svcPort }}
{{- else }}
serviceName: {{ $svcName }}
servicePort: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
================================================
FILE: helm/templates/interactsh-service.yaml
================================================
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.interactsh.service.name }}
labels:
{{- include "nuclei.labels" . | nindent 4 }}
spec:
type: {{ .Values.interactsh.service.type }}
ports:
- port: {{ .Values.interactsh.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "nuclei.selectorLabels" . | nindent 4 }}
================================================
FILE: helm/templates/nuclei-configmap.yaml
================================================
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nuclei-conf
data:
nuclei.conf: |-
{{ .Values.nuclei.config | indent 4 }}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nuclei-target-list
data:
target-list.txt: |-
{{ .Values.nuclei.target_list | indent 4 }}
================================================
FILE: helm/templates/nuclei-cron.yaml
================================================
{{- if .Values.nuclei.enabled -}}
apiVersion: batch/v1
kind: CronJob
metadata:
name: {{ .Chart.Name }}-nuclei-cron
spec:
schedule: "{{ .Values.nuclei.cron }}"
jobTemplate:
spec:
template:
spec:
containers:
- name: {{ .Chart.Name }}-nuclei-cron
image: "{{ .Values.nuclei.image.repository }}:{{ .Values.nuclei.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.nuclei.image.pullPolicy }}
command: [ "nuclei", "-config", "/config/nuclei.conf" ]
volumeMounts:
- name: nuclei-conf
mountPath: /config/nuclei.conf
subPath: nuclei.conf
- name: nuclei-target-list
mountPath: /config/target-list.txt
subPath: target-list.txt
restartPolicy: OnFailure
volumes:
- name: nuclei-conf
configMap:
name: nuclei-conf
- name: nuclei-target-list
configMap:
name: nuclei-target-list
{{- end }}
================================================
FILE: helm/templates/serviceaccount.yaml
================================================
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "nuclei.serviceAccountName" . }}
labels:
{{- include "nuclei.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
================================================
FILE: helm/values.yaml
================================================
# Default values for nuclei.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1
nuclei:
enabled: true
image:
repository: docker.io/projectdiscovery/nuclei
pullPolicy: IfNotPresent
tag: "v2.7.5"
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
cron: "0 0 * * Sun"
config: |
interactsh-server: http://nuclei-interactsh # should match the name of interactsh.service.name
list: /config/target-list.txt
# Headers to include with all HTTP request
#header:
# - 'X-BugBounty-Hacker: h1/geekboy'
# Directory based template execution
#templates:
# - cves/
# - vulnerabilities/
# - misconfiguration/
# Tags based template execution
#tags: exposures,cve
# Template Filters
#tags: exposures,cve
#author: geeknik,pikpikcu,dhiyaneshdk
#severity: critical,high,medium
# Template Allowlist
#include-tags: dos,fuzz # Tag based inclusion (allows overwriting nuclei-ignore list)
#include-templates: # Template based inclusion (allows overwriting nuclei-ignore list)
#- vulnerabilities/xxx
#- misconfiguration/xxxx
# Template Denylist
#exclude-tags: info # Tag based exclusion
#exclude-templates: # Template based exclusion
#- vulnerabilities/xxx
#- misconfiguration/xxxx
# Rate Limit configuration
#rate-limit: 500
#bulk-size: 50
#concurrency: 50
target_list: |
https://10.50.50.2
interactsh:
image:
repository: docker.io/projectdiscovery/interactsh-server
pullPolicy: IfNotPresent
tag: "v1.0.6"
service:
name: "nuclei-interactsh"
type: ClusterIP
port: 80
ingress:
enabled: false
className: ""
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: chart-example.local
paths:
- path: /
pathType: ImplementationSpecific
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
serviceAccount:
create: true
annotations: {}
name: ""
podAnnotations: {}
podSecurityContext: {}
securityContext: {}
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
nodeSelector: {}
tolerations: []
affinity: {}
================================================
FILE: integration_tests/debug.sh
================================================
#!/bin/bash
if [ $1 = "-h" ]; then
echo "Help for ./debug.sh"
printf "\n1. To run all integration tests of 'x' protocol:\n"
printf " \$ ./debug.sh http\n\n"
printf "2. To run all integration tests of 'x' protocol that contains 'y' in template name:\n"
printf " \$ ./debug.sh http self\n\n"
printf "3. To run all integration tests of 'x' protocol that contains 'y' in template name and pass extra args to nuclei:\n"
printf " \$ ./debug.sh http self -svd -debug-req\n\n"
printf "nuclei binary is created every time script is run but integration-test binary is not"
exit 0
fi
# Stop execution if race condition is found
export GORACE="halt_on_error=1"
echo "::group::Build nuclei"
rm nuclei 2>/dev/null
cd ../cmd/nuclei
go build -race .
mv nuclei ../../integration_tests/nuclei
echo -e "::endgroup::\n"
cd ../../integration_tests
cmdstring=""
if [ -n "$1" ]; then
cmdstring+=" -protocol $1 "
fi
if [ -n "$2" ]; then
cmdstring+=" -template $2 "
fi
# Parse any extra args that are directly passed to nuclei
if [ -n $debugArgs ]; then
export DebugExtraArgs="${@:3}"
fi
echo "$DebugExtraArgs"
echo -e "::group::Run Integration Test\n"
./integration-test $cmdstring
if [ $? -eq 0 ]
then
echo -e "::endgroup::\n"
exit 0
else
exit 1
fi
================================================
FILE: integration_tests/dsl/hide-version-warning.yaml
================================================
id: basic-example
info:
name: Test HTTP Template
author: pdteam
severity: info
reference: |
test case for default behaviour of version warning (dsl parsing error)
http:
- method: GET
path:
- "{{BaseURL}}"
matchers:
- type: dsl
dsl:
- compare_versions("GG", '< 4.8.5')
================================================
FILE: integration_tests/dsl/show-version-warning.yaml
================================================
id: basic-example
info:
name: Test HTTP Template
author: pdteam
severity: info
reference: |
test case where version warning is shown when env `SHOW_DSL_ERRORS=true` is set
http:
- method: GET
path:
- "{{BaseURL}}"
matchers:
- type: dsl
dsl:
- compare_versions("GG", '< 4.8.5')
================================================
FILE: integration_tests/flow/conditional-flow-negative.yaml
================================================
id: ghost-blog-detection
info:
name: Ghost blog detection
author: pdteam
severity: info
flow: dns() && http()
dns:
- name: "{{FQDN}}"
type: CNAME
matchers:
- type: word
words:
- "ghost.io"
internal: true
http:
- method: GET
path:
- "{{BaseURL}}"
matchers:
- type: word
words:
- "ghost.io"
================================================
FILE: integration_tests/flow/conditional-flow.yaml
================================================
id: ghost-blog-detection
info:
name: Ghost blog detection
author: pdteam
severity: info
flow: dns() && http()
dns:
- name: "{{FQDN}}"
type: CNAME
matchers:
- type: word
words:
- ".vercel-dns.com"
internal: true
http:
- method: GET
path:
- "{{BaseURL}}"
matchers:
- type: word
words:
- "html>"
================================================
FILE: integration_tests/flow/dns-ns-probe.yaml
================================================
id: dns-ns-probe
info:
name: Nuclei flow dns ns probe
author: pdteam
severity: info
description: Description of the Template
reference: https://example-reference-link
flow: |
dns("fetch-ns");
for(let ns of template["nameservers"]) {
set("nameserver",ns);
dns("probe-ns");
};
dns:
- id: "fetch-ns"
name: "{{FQDN}}"
type: NS
matchers:
- type: word
words:
- "IN\tNS"
internal: true
extractors:
- type: regex
internal: true
name: "nameservers"
group: 1
regex:
- "IN\tNS\t(.+)"
- id: "probe-ns"
name: "{{nameserver}}"
type: A
class: inet
retries: 3
recursion: true
extractors:
- type: dsl
dsl:
- "a"
================================================
FILE: integration_tests/flow/flow-hide-matcher.yaml
================================================
id: flow-hide-matcher
info:
name: Test Flow Hide Matcher
author: pdteam
severity: info
description: In Template any matcher can be marked as internal which hides it from the output.
flow: http(1) && http(2)
http:
- method: GET
path:
- "{{BaseURL}}"
matchers:
- type: word
words:
- ok
internal: true
- method: GET
path:
- "{{BaseURL}}"
matchers:
- type: word
words:
- "Failed event"
================================================
FILE: integration_tests/flow/iterate-one-value-flow.yaml
================================================
id: flow-iterate-one-value-flow
info:
name: Test Flow Iterate One Value Flow
author: pdteam
severity: info
description: |
If length of template.extracted variable is not know, i.e it could be an array of 1 or more values, then iterate function
should be used to iterate over values because nuclei by default converts array to string if it has only 1 value.
flow: |
http(1)
for(let value of iterate(template.extracted)){
set("value", value)
http(2)
}
http:
- method: GET
path:
- "{{BaseURL}}"
extractors:
- type: regex
name: extracted
internal: true
regex:
- "[ok]+"
- method: GET
path:
- "{{BaseURL}}/{{value}}"
matchers:
- type: word
words:
- "ok"
================================================
FILE: integration_tests/flow/iterate-values-flow.yaml
================================================
id: extract-emails
info:
name: Extract Email IDs from Response
author: pdteam
severity: info
flow: |
http(1)
for(let email of template["emails"]) {
set("email",email);
http(2);
}
http:
- method: GET
path:
- "{{BaseURL}}"
extractors:
- type: regex
name: emails
regex:
- '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'
internal: true
- method: GET
path:
- "{{BaseURL}}/user/{{base64(email)}}"
matchers:
- type: word
words:
- "Welcome"
extractors:
- type: dsl
name: email
dsl:
- email
================================================
FILE: integration_tests/fuzz/fuzz-body-generic-sqli.yaml
================================================
id: fuzz-body-generic
info:
name: fuzzing error sqli payloads in http req body
author: pdteam
severity: info
description: |
This template attempts to find SQL injection vulnerabilities by fuzzing http body
It automatically handles and parses json,xml,multipart form and x-www-form-urlencoded data
and performs fuzzing on the value of every key
http:
- pre-condition:
- type: dsl
dsl:
- method != "GET"
- method != "HEAD"
- contains(path, "/user") # for scope of integration test
condition: and
payloads:
injection:
- "'"
- "\""
- ";"
fuzzing:
- part: body
type: postfix
mode: single
fuzz:
- '{{injection}}'
stop-at-first-match: true
matchers:
- type: word
words:
- "unrecognized token:"
- "null"
================================================
FILE: integration_tests/fuzz/fuzz-body-json-sqli.yaml
================================================
id: json-body-error-sqli
info:
name: fuzzing error sqli payloads in json body
author: pdteam
severity: info
description: |
This template attempts to find SQL injection vulnerabilities by fuzzing http body of json type.
This is achieved by performing [ruleType](example: postfix) on value of json key
Note: this is example template, and payloads/matchers need to be modified appropriately.
http:
- pre-condition:
- type: dsl
dsl:
- method != "GET"
- method != "HEAD"
- contains(content_type, "application/json")
- contains(path, "/user") # for scope of integration test
condition: and
payloads:
injection:
- "'"
- "\""
- ";"
fuzzing:
- part: body
type: postfix
mode: single
fuzz:
- '{{injection}}'
stop-at-first-match: true
matchers:
- type: word
words:
- "unrecognized token:"
- "null"
================================================
FILE: integration_tests/fuzz/fuzz-body-multipart-form-sqli.yaml
================================================
id: body-multipart-error-sqli
info:
name: fuzzing error sqli payloads in body of multipart form data
author: pdteam
severity: info
description: |
This template attempts to find SQL injection vulnerabilities by fuzzing http body of multipart form data (file upload, etc.)
This is achieved by performing [ruleType](example: postfix) on value of body form key
Note: this is example template, and payloads/matchers need to be modified appropriately.
http:
- pre-condition:
- type: dsl
dsl:
- method != "GET"
- method != "HEAD"
- contains(content_type, "multipart/form-data")
- contains(path, "/user") # for scope of integration test
condition: and
payloads:
injection:
- "'"
- "\""
- ";"
fuzzing:
- part: body
type: postfix
mode: single
fuzz:
- '{{injection}}'
stop-at-first-match: true
matchers:
- type: word
words:
- "unrecognized token:"
- "null"
- "SELECTs to the left and right of UNION do not have the same number of result columns"
================================================
FILE: integration_tests/fuzz/fuzz-body-params-sqli.yaml
================================================
id: body-params-error-sqli
info:
name: fuzzing error sqli payloads in body with params
author: pdteam
severity: info
description: |
This template attempts to find SQL injection vulnerabilities by fuzzing http body of x-www-form-urlencoded data
This is achieved by performing [ruleType](example: postfix) on value of body key
Note: this is example template, and payloads/matchers need to be modified appropriately.
http:
- pre-condition:
- type: dsl
dsl:
- method != "GET"
- method != "HEAD"
- contains(content_type, "application/x-www-form-urlencoded")
- contains(path, "/user") # for scope of integration test
condition: and
payloads:
injection:
- "'"
- "\""
- ";"
fuzzing:
- part: body
type: postfix
mode: single
fuzz:
- '{{injection}}'
stop-at-first-match: true
matchers:
- type: word
words:
- "unrecognized token:"
- "null"
- "SELECTs to the left and right of UNION do not have the same number of result columns"
================================================
FILE: integration_tests/fuzz/fuzz-body-xml-sqli.yaml
================================================
id: xml-body-error-sqli
info:
name: fuzzing error sqli payloads in xml body
author: pdteam
severity: info
description: |
This template attempts to find SQL injection vulnerabilities by fuzzing http body of xml type.
This is achieved by performing [ruleType](example: postfix) on value of xml key
Note: this is example template, and payloads/matchers need to be modified appropriately.
http:
- pre-condition:
- type: dsl
dsl:
- method != "GET"
- method != "HEAD"
- contains(content_type, "application/xml")
- contains(path, "/user") # for scope of integration test
condition: and
payloads:
injection:
- "'"
- "\""
- ";"
fuzzing:
- part: body
type: postfix
mode: single
fuzz:
- '{{injection}}'
stop-at-first-match: true
matchers:
- type: word
words:
- "unrecognized token:"
- "null"
================================================
FILE: integration_tests/fuzz/fuzz-body.yaml
================================================
id: fuzz-body
info:
name: fuzzing error sqli payloads in http req body
author: pdteam
severity: info
description: |
This template attempts to find SQL injection vulnerabilities by fuzzing http body
It automatically handles and parses json,xml,multipart form and x-www-form-urlencoded data
and performs fuzzing on the value of every key
http:
- pre-condition:
- type: dsl
dsl:
- method != "GET"
- method != "HEAD"
condition: and
payloads:
injection:
- "'"
- "\""
- ";"
fuzzing:
- part: body
type: postfix
mode: single
fuzz:
- '{{injection}}'
stop-at-first-match: true
matchers:
- type: word
words:
- "unrecognized token:"
- "null"
================================================
FILE: integration_tests/fuzz/fuzz-cookie-error-sqli.yaml
================================================
id: cookie-fuzzing-error-sqli
info:
name: fuzzing error sqli payloads in cookie
author: pdteam
severity: info
description: |
This template attempts to find SQL injection vulnerabilities by fuzzing http cookies with SQL injection payloads.
Note: this is example template, and payloads/matchers need to be modified appropriately.
http:
- pre-condition:
- type: dsl
dsl:
- 'method == "GET"'
- len(cookie) > 0
condition: and
payloads:
sqli:
- "'"
- ''
- '`'
- '``'
- ','
- '"'
- ""
- /
- //
- \
- \\
- ;
- -- or #
- '" OR 1 = 1 -- -'
- ' OR '' = '
- '='
- 'LIKE'
- "'=0--+"
- OR 1=1
- "' OR 'x'='x"
- "' AND id IS NULL; --"
- "'''''''''''''UNION SELECT '2"
- '%00'
fuzzing:
- part: cookie
type: postfix
mode: single
fuzz:
- '{{sqli}}'
stop-at-first-match: true
matchers:
- type: word
words:
- "unrecognized token:"
- "syntax error"
- "null"
- "SELECTs to the left and right of UNION do not have the same number of result columns"
================================================
FILE: integration_tests/fuzz/fuzz-headless.yaml
================================================
id: headless-query-fuzzing
info:
name: Example Query Fuzzing
author: pdteam
severity: info
headless:
- steps:
- action: navigate
args:
url: "{{BaseURL}}"
- action: waitload
payloads:
redirect:
- "blog.com"
- "portal.com"
fuzzing:
- part: query
mode: single
type: replace
fuzz:
- "https://{{redirect}}"
matchers:
- type: word
part: body
words:
- "{{redirect}}"
================================================
FILE: integration_tests/fuzz/fuzz-host-header-injection.yaml
================================================
id: host-header-injection
info:
name: Host Header Injection
author: pdteam
severity: info
description: Host header injection
variables:
domain: "oast.fun"
http:
- pre-condition:
- type: dsl
dsl:
- 'method == "GET"'
- 'contains(path,"/host-header-lab")' # for integration testing only
condition: and
fuzzing:
- part: header
type: replace
mode: single
fuzz:
X-Forwarded-For: "{{domain}}"
X-Forwarded-Host: "{{domain}}"
Forwarded: "{{domain}}"
X-Real-IP: "{{domain}}"
X-Original-URL: "{{domain}}"
X-Rewrite-URL: "{{domain}}"
Host: "{{domain}}"
# " Host": "{{domain}}" # space before host (not supported yet due to lack of unsafe mode)
matchers:
- type: status
status:
- 200
- type: word
part: body
words:
- "Interactsh"
matchers-condition: and
================================================
FILE: integration_tests/fuzz/fuzz-mode.yaml
================================================
id: fuzz-query
info:
name: Basic Fuzz URL Query
author: pdteam
severity: info
http:
- method: GET
path:
- "{{BaseURL}}"
fuzzing:
- part: query
type: postfix
mode: multiple
keys: ["id","name"]
fuzz: ["fuzz-word"]
matchers-condition: and
matchers:
- type: word
part: body
words:
- "fuzz-word"
- type: word
part: header
words:
- "text/html"
================================================
FILE: integration_tests/fuzz/fuzz-multi-mode.yaml
================================================
id: fuzz-multi-mode-test
info:
name: multi-mode fuzzing test
author: pdteam
severity: info
http:
- payloads:
inject:
- nuclei-v1
- nuclei-v2
- nuclei-v3
fuzzing:
- part: header
type: replace
mode: multiple
fuzz:
X-Client-Id: "{{inject}}"
X-Secret-Id: "{{inject}}"
matchers-condition: or
matchers:
- type: word
words:
- "nuclei-v3"
================================================
FILE: integration_tests/fuzz/fuzz-path-sqli.yaml
================================================
id: path-based-sqli
info:
name: Path Based SQLi
author: pdteam
severity: info
description: |
This template attempts to find SQL injection vulnerabilities on path based sqli and replacing numerical values with fuzzing payloads.
ex: /admin/user/55/profile , /user/15/action/update, /posts/15, /blog/100/data, /page/51/ etc these types of paths are filtered and
replaced with sqli path payloads.
Note: this is example template, and payloads/matchers need to be modified appropriately.
http:
- pre-condition:
- type: dsl
dsl:
- 'method == "GET"'
condition: and
payloads:
pathsqli:
- '%20OR%20True'
fuzzing:
- part: path
type: postfix
mode: single
fuzz:
- '{{pathsqli}}'
matchers:
- type: status
status:
- 200
- type: word
words:
- "admin"
matchers-condition: and
================================================
FILE: integration_tests/fuzz/fuzz-query-num-replace.yaml
================================================
id: fuzz-query-num
info:
name: Fuzz Query Param For IDOR
author: pdteam
severity: info
description: Query Value Fuzzing using Fuzzing Rules
http:
- pre-condition:
- type: dsl
dsl:
- 'len(query) > 0'
# below filter is related to integration testing
- type: word
part: path
words:
- /blog/post
pre-condition-operator: and
payloads:
nums:
- 200
- 201
fuzzing:
- part: query
type: replace
mode: multiple
values:
- "^[0-9]+$" # only if value is number
fuzz:
- '{{nums}}'
matchers:
- type: status
status:
- 200
================================================
FILE: integration_tests/fuzz/fuzz-query.yaml
================================================
id: fuzz-query
info:
name: Basic Fuzz URL Query
author: pdteam
severity: info
http:
- method: GET
path:
- "{{BaseURL}}"
fuzzing:
- part: query
type: postfix
mode: single
keys: ["id"]
fuzz: ["6842'\"><"]
matchers-condition: and
matchers:
- type: word
part: body
words:
- "6842'\"><"
- type: word
part: header
words:
- "text/html"
================================================
FILE: integration_tests/fuzz/fuzz-type.yaml
================================================
id: fuzz-type
info:
name: Basic Fuzz URL Query
author: pdteam
severity: info
http:
- method: GET
path:
- "{{BaseURL}}"
fuzzing:
- part: query
type: postfix
mode: single
keys: ["id"]
fuzz: ["fuzz-word"]
matchers-condition: and
matchers:
- type: word
part: body
words:
- "fuzz-word"
- type: word
part: header
words:
- "text/html"
================================================
FILE: integration_tests/fuzz/testData/ginandjuice.proxify.yaml
================================================
timestamp: 2024-02-20T19:24:13+05:30
url: http://127.0.0.1:8082/blog/post?postId=3&source=proxify
request:
header:
Accept-Encoding: gzip
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
host: 127.0.0.1:8082
method: GET
path: /blog/post
scheme: https
raw: |+
GET /blog/post?postId=3&source=proxify HTTP/1.1
Host: 127.0.0.1:8082
Accept-Encoding: gzip
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
response:
header:
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Tue, 20 Feb 2024 13:54:13 GMT
Set-Cookie: AWSALB=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/, AWSALBCORS=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure, session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; Secure; HttpOnly; SameSite=None
X-Backend: ea6c1d8c-a8e5-4bef-b8db-879bbb13cf62
X-Frame-Options: SAMEORIGIN
raw: |+
HTTP/1.1 200 OK
Connection: close
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Tue, 20 Feb 2024 13:54:13 GMT
Set-Cookie: AWSALB=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/
Set-Cookie: AWSALBCORS=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure
Set-Cookie: session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; Secure; HttpOnly; SameSite=None
X-Backend: ea6c1d8c-a8e5-4bef-b8db-879bbb13cf62
X-Frame-Options: SAMEORIGIN
---
timestamp: 2024-02-20T19:24:13+05:30
url: http://127.0.0.1:8082/reset-password
request:
header:
Accept-Encoding: gzip
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
host: 127.0.0.1:8082
method: GET
path: /blog/post
scheme: https
raw: |+
POST /reset-password HTTP/1.1
Host: 127.0.0.1:8082
X-Forwarded-For: 127.0.0.1:8082
Accept-Encoding: gzip
Connection: close
Content-Type: application/json
Content-Length: 23
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
{"password":"12345678"}
response:
header:
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Tue, 20 Feb 2024 13:54:13 GMT
Set-Cookie: AWSALB=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/, AWSALBCORS=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure, session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; Secure; HttpOnly; SameSite=None
X-Backend: ea6c1d8c-a8e5-4bef-b8db-879bbb13cf62
X-Frame-Options: SAMEORIGIN
raw: |+
HTTP/1.1 200 OK
Connection: close
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Tue, 20 Feb 2024 13:54:13 GMT
Set-Cookie: AWSALB=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/
Set-Cookie: AWSALBCORS=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure
Set-Cookie: session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; Secure; HttpOnly; SameSite=None
X-Backend: ea6c1d8c-a8e5-4bef-b8db-879bbb13cf62
X-Frame-Options: SAMEORIGIN
---
timestamp: 2024-02-20T19:24:13+06:30
url: http://127.0.0.1:8082/user/55/profile
request:
header:
Accept-Encoding: gzip
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
host: 127.0.0.1:8082
method: GET
path: /blog/post
scheme: https
raw: |+
GET /user/55/profile HTTP/1.1
Host: 127.0.0.1:8082
Accept-Encoding: gzip
Connection: close
Content-Type: application/json
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
response:
header:
Content-Type: application/json; charset=UTF-8
Date: Tue, 27 Feb 2024 18:46:44 GMT
raw: |+
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Date: Tue, 27 Feb 2024 18:46:44 GMT
Content-Length: 47
{"ID":75,"Name":"user","Age":30,"Role":"user"}
---
timestamp: 2024-02-20T23:25:13+06:30
url: http://127.0.0.1:8082/user
request:
header:
Accept-Encoding: gzip
Connection: close
Content-Type: application/json
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
host: 127.0.0.1:8082
method: POST
path: /user
scheme: http
raw: |+
POST /user HTTP/1.1
Host: localhost:8082
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: */*
Content-Length: 32
Connection: close
Content-Type: application/json
{"id": 7 , "name": "pdteam"}
response:
header:
Content-Type: text/plain; charset=UTF-8
Date: Tue, 27 Feb 2024 18:46:44 GMT
raw: |+
HTTP/1.1 200 OK
Content-Type: text/plain; charset=UTF-8
Date: Wed, 28 Feb 2024 13:58:52 GMT
Content-Length: 25
User updated successfully
---
timestamp: 2024-02-20T23:26:13+06:30
url: http://127.0.0.1:8082/user
request:
header:
Accept-Encoding: gzip
Connection: close
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
host: 127.0.0.1:8082
method: POST
path: /user
scheme: http
raw: |+
POST /user HTTP/1.1
Host: localhost:8082
User-Agent: curl/8.1.2
Accept: */*
Content-Length: 20
Connection: close
Content-Type: application/x-www-form-urlencoded
id=7&name=pdteam
response:
header:
Content-Type: text/plain; charset=UTF-8
Date: Tue, 27 Feb 2024 18:46:44 GMT
raw: |+
HTTP/1.1 200 OK
Content-Type: text/plain; charset=UTF-8
Date: Wed, 28 Feb 2024 13:58:52 GMT
Content-Length: 25
User updated successfully
---
timestamp: 2024-02-20T23:26:13+06:30
url: http://127.0.0.1:8082/user
request:
header:
Accept-Encoding: gzip
Connection: close
Content-Type: multipart/form-data
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
host: 127.0.0.1:8082
method: POST
path: /user
scheme: http
raw: |+
POST /user HTTP/1.1
Host: localhost:8082
User-Agent: curl/8.1.2
Accept: */*
Content-Length: 226
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="id"
7
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="name"
pdteam
------WebKitFormBoundary7MA4YWxkTrZu0gW--
response:
header:
Content-Type: text/plain; charset=UTF-8
Date: Tue, 27 Feb 2024 18:46:44 GMT
raw: |+
HTTP/1.1 200 OK
Content-Type: text/plain; charset=UTF-8
Date: Wed, 28 Feb 2024 13:58:52 GMT
Content-Length: 25
User updated successfully
---
---
timestamp: 2024-02-20T19:25:13+06:30
url: http://127.0.0.1:8082/blog/posts
request:
header:
Accept-Encoding: gzip
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
host: 127.0.0.1:8082
Cookie: session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; lang=en
method: GET
path: /blog/posts
scheme: http
raw: |+
GET /blog/posts HTTP/1.1
Host: 127.0.0.1:8082
Accept-Encoding: gzip
Cookie: session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; lang=en
Connection: close
Content-Type: application/json
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
response:
header:
Content-Type: application/json; charset=UTF-8
Date: Tue, 27 Feb 2024 18:46:44 GMT
raw: |+
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Date: Wed, 28 Feb 2024 13:58:52 GMT
Content-Length: 218
[{"ID":1,"Title":"The Joy of Programming","Content":"Programming is like painting a canvas with logic.","Lang":"en"},{"ID":2,"Title":"A Journey Through Code","Content":"Every line of code tells a story.","Lang":"en"}]
---
timestamp: 2024-02-20T23:26:13+06:30
url: http://127.0.0.1:8082/user
request:
header:
Accept-Encoding: gzip
Connection: close
Content-Type: application/xml
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
host: 127.0.0.1:8082
method: POST
path: /user
scheme: http
raw: |+
POST /user HTTP/1.1
Host: localhost:8082
User-Agent: curl/8.1.2
Accept: */*
Content-Length: 76
Connection: close
Content-Type: application/xml
7pdteam
response:
header:
Content-Type: text/plain; charset=UTF-8
Date: Tue, 27 Feb 2024 18:46:44 GMT
raw: |+
HTTP/1.1 200 OK
Content-Type: text/plain; charset=UTF-8
Date: Wed, 28 Feb 2024 13:58:52 GMT
Content-Length: 25
User updated successfully
---
timestamp: 2024-02-20T19:24:13+05:32
url: http://127.0.0.1:8082/host-header-lab
request:
header:
Accept-Encoding: gzip
Authorization: Bearer 3x4mpl3t0k3n
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
host: 127.0.0.1:8082
method: POST
path: /catalog/product
scheme: https
raw: |+
GET /host-header-lab HTTP/1.1
Host: 127.0.0.1:8082
Authorization: Bearer 3x4mpl3t0k3n
Accept-Encoding: gzip
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
response:
header:
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Tue, 20 Feb 2024 13:54:13 GMT
Set-Cookie: AWSALB=Rzm8ZSSL/yQq1mG1SdGmWAahqexWvOcjIhlNKm0wBnk4jvY3Hdy3mRc+NKoMRNJ2RW+FNbtRk7DX+itnfzjMvKNpwtLWWAafxeKybc+v351g0MsLycRNQF5fG78y; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/, AWSALBCORS=Rzm8ZSSL/yQq1mG1SdGmWAahqexWvOcjIhlNKm0wBnk4jvY3Hdy3mRc+NKoMRNJ2RW+FNbtRk7DX+itnfzjMvKNpwtLWWAafxeKybc+v351g0MsLycRNQF5fG78y; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure, session=fFcCUjmQguQy820Y8xrnRypp3KBWSPk6; Secure; HttpOnly; SameSite=None
X-Backend: 2235790d-f089-4324-8ac0-f64cc96f2460
X-Frame-Options: SAMEORIGIN
body: |
Fruit Overlays - Product - Gin & Juice Shop
This is a deliberately vulnerable web application designed for testing web vulnerability scanners.
Put your scanner to the test!
When it comes to hospitality presentation is key, and nothing looks better than a well-dressed drink. We know gin fans like plenty of fruit in their glasses, some a bit more than they really need, but hey ho, each to their own. But what about fruit not inside your glass, but classily arranged over your glass? In comes Fruitus overlayus, the best way to jazz up any party. The possible colour combinations are endless, just picture that! All you need is a nice selection of small fruits, or maybe even use our Fruit Curliwurlier to add a dash of even more drama, and we will do the rest. This one is a real winner at our Christmas and New year’s outings, give it a go and turn some heads.
CONTENTS: 12 cocktail sticks.
HOW TO USE: Let your creative juices flow (Pun intended), and spend some time working on your colour coordination, try not to think too much about it, just do it! Pick up one of the Fruitus overlayus sticks and carefully slide the fruit along until there is a small space on either end of the stick. Balance the stick across the rim of the glass. Ta-Da! Your first fruit overlay. Keep going until you have as many overlays as you need. You can always purchase more at any time with a discount on bulk buys.
When it comes to hospitality presentation is key, and nothing looks better than a well-dressed drink. We know gin fans like plenty of fruit in their glasses, some a bit more than they really need, but hey ho, each to their own. But what about fruit not inside your glass, but classily arranged over your glass? In comes Fruitus overlayus, the best way to jazz up any party. The possible colour combinations are endless, just picture that! All you need is a nice selection of small fruits, or maybe even use our Fruit Curliwurlier to add a dash of even more drama, and we will do the rest. This one is a real winner at our Christmas and New year’s outings, give it a go and turn some heads.
CONTENTS: 12 cocktail sticks.
HOW TO USE: Let your creative juices flow (Pun intended), and spend some time working on your colour coordination, try not to think too much about it, just do it! Pick up one of the Fruitus overlayus sticks and carefully slide the fruit along until there is a small space on either end of the stick. Balance the stick across the rim of the glass. Ta-Da! Your first fruit overlay. Keep going until you have as many overlays as you need. You can always purchase more at any time with a discount on bulk buys.
raw: |+
HTTP/1.1 200 OK
Connection: close
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Tue, 20 Feb 2024 13:54:13 GMT
Set-Cookie: AWSALB=Rzm8ZSSL/yQq1mG1SdGmWAahqexWvOcjIhlNKm0wBnk4jvY3Hdy3mRc+NKoMRNJ2RW+FNbtRk7DX+itnfzjMvKNpwtLWWAafxeKybc+v351g0MsLycRNQF5fG78y; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/
Set-Cookie: AWSALBCORS=Rzm8ZSSL/yQq1mG1SdGmWAahqexWvOcjIhlNKm0wBnk4jvY3Hdy3mRc+NKoMRNJ2RW+FNbtRk7DX+itnfzjMvKNpwtLWWAafxeKybc+v351g0MsLycRNQF5fG78y; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure
Set-Cookie: session=fFcCUjmQguQy820Y8xrnRypp3KBWSPk6; Secure; HttpOnly; SameSite=None
X-Backend: 2235790d-f089-4324-8ac0-f64cc96f2460
X-Frame-Options: SAMEORIGIN
================================================
FILE: pkg/input/formats/testdata/openapi.yaml
================================================
openapi: 3.1.0
info:
title: VAmPI
description: OpenAPI v3 specs for VAmPI
version: '0.1'
servers:
- url: http://hackthebox:5000
components: {}
paths:
/createdb:
get:
tags:
- db-init
summary: Creates and populates the database with dummy data
description: Creates and populates the database with dummy data
operationId: api_views.main.populate_db
responses:
'200':
description: Creates and populates the database with dummy data
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: 'Database populated.'
/:
get:
tags:
- home
summary: VAmPI home
description: >-
VAmPI is a vulnerable on purpose API. It was created in order to
evaluate the efficiency of third party tools in identifying
vulnerabilities in APIs but it can also be used in learning/teaching
purposes.
operationId: api_views.main.basic
responses:
'200':
description: Home - Help
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: 'VAmPI the Vulnerable API'
help:
type: string
example: 'VAmPI is a vulnerable on purpose API. It was created in order to evaluate the efficiency of third party tools in identifying vulnerabilities in APIs but it can also be used in learning/teaching purposes.'
vulnerable:
type: number
example: 1
/users/v1:
get:
tags:
- users
summary: Retrieves all users
description: Displays all users with basic information
operationId: api_views.users.get_all_users
responses:
'200':
description: See basic info about all users
content:
application/json:
schema:
type: array
items:
type: object
properties:
email:
type: string
example: 'mail1@mail.com'
username:
type: string
example: 'name1'
/users/v1/_debug:
get:
tags:
- users
summary: Retrieves all details for all users
description: Displays all details for all users
operationId: api_views.users.debug
responses:
'200':
description: See all details of the users
content:
application/json:
schema:
type: array
items:
type: object
properties:
admin:
type: boolean
example: false
email:
type: string
example: 'mail1@mail.com'
password:
type: string
example: 'pass1'
username:
type: string
example: 'name1'
/users/v1/register:
post:
tags:
- users
summary: Register new user
description: Register new user
operationId: api_views.users.register_user
requestBody:
description: Username of the user
content:
application/json:
schema:
type: object
properties:
username:
type: string
example: 'John.Doe'
password:
type: string
example: 'password123'
email:
type: string
example: 'user@tempmail.com'
required: true
responses:
'200':
description: Successfully created user
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: 'Successfully registered. Login to receive an auth token.'
status:
type: string
enum: ['success', 'fail']
example: 'success'
'400':
description: Invalid request
content: {}
/users/v1/login:
post:
tags:
- users
summary: Login to VAmPI
description: Login to VAmPI
operationId: api_views.users.login_user
requestBody:
description: Username of the user
content:
application/json:
schema:
type: object
properties:
username:
type: string
example: 'John.Doe'
password:
type: string
example: 'password123'
required: true
responses:
'200':
description: Successfully logged in user
content:
application/json:
schema:
type: object
properties:
auth_token:
type: string
example: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NzAxNjA2MTcsImlhdCI6MTY3MDE2MDU1Nywic3ViIjoiSm9obi5Eb2UifQ.n17N4AxTbL4_z65-NR46meoytauPDjImUxrLiUMSTQw'
message:
type: string
example: 'Successfully logged in.'
status:
type: string
enum: ['success', 'fail']
example: 'success'
'400':
description: Invalid request
content:
application/json:
schema:
type: object
properties:
status:
type: string
enum: ['fail']
example: 'fail'
message:
type: string
example: 'Password is not correct for the given username.'
/users/v1/{username}:
get:
tags:
- users
summary: Retrieves user by username
description: Displays user by username
operationId: api_views.users.get_by_username
parameters:
- name: username
in: path
description: retrieve username data
required: true
schema:
type: string
example: 'John.Doe'
responses:
'200':
description: Successfully display user info
content:
application/json:
schema:
type: array
items:
type: object
properties:
username:
type: string
example: 'John.Doe'
email:
type: string
example: 'user@tempmail.com'
'404':
description: User not found
content:
application/json:
schema:
type: object
properties:
status:
type: string
enum: ['fail']
example: 'fail'
message:
type: string
example: 'User not found'
delete:
tags:
- users
summary: Deletes user by username (Only Admins)
description: Deletes user by username (Only Admins)
operationId: api_views.users.delete_user
parameters:
- name: username
in: path
description: Delete username
required: true
schema:
type: string
example: 'name1'
responses:
'200':
description: Successfully deleted user
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: 'User deleted.'
status:
type: string
enum: ['success', 'fail']
example: 'success'
'401':
description: User not authorized
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: 'fail'
enum: ['fail']
message:
type: string
example: 'Only Admins may delete users!'
'404':
description: User not found
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: 'fail'
enum: ['fail']
message:
type: string
example: 'User not found!'
/users/v1/{username}/email:
put:
tags:
- users
summary: Update users email
description: Update a single users email
operationId: api_views.users.update_email
parameters:
- name: username
in: path
description: username to update email
required: true
schema:
type: string
example: 'name1'
requestBody:
description: field to update
content:
application/json:
schema:
type: object
properties:
email:
type: string
example: 'mail3@mail.com'
required: true
responses:
'204':
description: Successfully updated user email
content: {}
'400':
description: Invalid request
content:
application/json:
schema:
type: object
properties:
status:
type: string
enum: ['fail']
example: 'fail'
message:
type: string
example: 'Please Provide a valid email address.'
'401':
description: User not authorized
content:
application/json:
schema:
type: object
properties:
status:
type: string
enum: ['fail']
example: 'fail'
message:
type: string
example: 'Invalid Token'
/users/v1/{username}/password:
put:
tags:
- users
summary: Update users password
description: Update users password
operationId: api_views.users.update_password
parameters:
- name: username
in: path
description: username to update password
required: true
schema:
type: string
example: 'name1'
requestBody:
description: field to update
content:
application/json:
schema:
type: object
properties:
password:
type: string
example: 'pass4'
required: true
responses:
'204':
description: Successfully updated users password
content: {}
'400':
description: Invalid request
content:
application/json:
schema:
type: object
properties:
status:
type: string
enum: ['fail']
example: 'fail'
message:
type: string
example: 'Malformed Data'
'401':
description: User not authorized
content:
application/json:
schema:
type: object
properties:
status:
type: string
enum: ['fail']
example: 'fail'
message:
type: string
example: 'Invalid Token'
/books/v1:
get:
tags:
- books
summary: Retrieves all books
description: Retrieves all books
operationId: api_views.books.get_all_books
responses:
'200':
description: See all books
content:
application/json:
schema:
type: object
properties:
Books:
type: array
items:
type: object
properties:
book_title:
type: string
user:
type: string
example:
Books:
- book_title: 'bookTitle77'
user: 'name1'
- book_title: 'bookTitle85'
user: 'name2'
- book_title: 'bookTitle47'
user: 'admin'
post:
tags:
- books
summary: Add new book
description: Add new book
operationId: api_views.books.add_new_book
requestBody:
description: >-
Add new book with title and secret content only available to the user
who added it.
content:
application/json:
schema:
type: object
properties:
book_title:
type: string
example: 'book99'
secret:
type: string
example: 'pass1secret'
required: true
responses:
'200':
description: Successfully added a book
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: 'Book has been added.'
status:
type: string
enum: ['success', 'fail']
example: 'success'
'400':
description: Invalid request
content:
application/json:
schema:
type: object
properties:
status:
type: string
enum: ['fail']
example: 'fail'
message:
type: string
example: 'Book Already exists!'
'401':
description: User not authorized
content:
application/json:
schema:
type: object
properties:
status:
type: string
enum: ['fail']
example: 'fail'
message:
type: string
example: 'Invalid Token'
/books/v1/{book_title}:
get:
tags:
- books
summary: Retrieves book by title along with secret
description: >-
Retrieves book by title along with secret. Only the owner may retrieve
it
operationId: api_views.books.get_by_title
parameters:
- name: book_title
in: path
description: retrieve book data
required: true
schema:
type: string
example: 'bookTitle77'
responses:
'200':
description: Successfully retrieve book info
content:
application/json:
schema:
type: array
items:
type: object
properties:
book_title:
type: string
example: 'bookTitle77'
owner:
type: string
example: 'name1'
secret:
type: string
example: 'secret for bookTitle77'
'401':
description: User not authorized
content:
application/json:
schema:
type: object
properties:
status:
type: string
enum: ['fail']
example: 'fail'
message:
type: string
example: 'Invalid Token'
'404':
description: Book not found
content:
application/json:
schema:
type: object
properties:
status:
type: string
enum: ['fail']
example: 'fail'
message:
type: string
example: 'Book not found!'
================================================
FILE: pkg/input/formats/testdata/postman.json
================================================
{
"info": {
"_postman_id": "20a3fd41-6a86-4e49-8860-f796559d0223",
"name": "advancedsearch",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "List Projects, Assets and Hosts",
"protocolProfileBehavior": {
"disableBodyPruning": true
},
"request": {
"method": "GET",
"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "http://127.0.0.1:8000/api/v1/search/",
"protocol": "http",
"host": ["127", "0", "0", "1"],
"port": "8000",
"path": ["api", "v1", "search", ""]
}
},
"response": []
},
{
"name": "List Assets and Hosts",
"protocolProfileBehavior": {
"disableBodyPruning": true
},
"request": {
"method": "GET",
"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "http://127.0.0.1:8000/api/v1/search/?projectId=1,2",
"protocol": "http",
"host": ["127", "0", "0", "1"],
"port": "8000",
"path": ["api", "v1", "search", ""],
"query": [
{
"key": "projectId",
"value": "1,2"
}
]
}
},
"response": []
},
{
"name": "List Hosts",
"protocolProfileBehavior": {
"disableBodyPruning": true
},
"request": {
"method": "GET",
"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "http://127.0.0.1:8000/api/v1/search/?projectId=1,2&assetId=1,2",
"protocol": "http",
"host": ["127", "0", "0", "1"],
"port": "8000",
"path": ["api", "v1", "search", ""],
"query": [
{
"key": "projectId",
"value": "1,2"
},
{
"key": "assetId",
"value": "1,2"
}
]
}
},
"response": []
},
{
"name": "Search Request",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"value": "application/json",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{\n\t\"query\": \"query\",\n\t\"projectId\": [4,3,4],\n\t\"assetId\": [2,3,4],\n\t\"hostId\": [1,2,3],\n \"limit\": 10,\n \"offset\": 10\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "http://127.0.0.1:8000/api/v1/search/",
"protocol": "http",
"host": ["127", "0", "0", "1"],
"port": "8000",
"path": ["api", "v1", "search", ""]
}
},
"response": []
}
],
"protocolProfileBehavior": {}
}
================================================
FILE: pkg/input/formats/testdata/swagger.yaml
================================================
swagger: "2.0"
info:
title: Sample API
description: API description in Markdown.
version: 1.0.0
host: localhost
basePath: /v1
schemes:
- https
paths:
/users:
get:
summary: Returns a list of users.
description: Optional extended description in Markdown.
produces:
- application/json
responses:
200:
description: OK
/users/{userId}:
get:
summary: Returns a user by ID.
parameters:
- in: path
name: userId
required: true
type: integer
default: 1
description: Parameter description in Markdown.
- in: query
name: test
type: string
enum: [asc, desc]
description: Type of query
responses:
200:
description: OK
================================================
FILE: pkg/input/formats/testdata/ytt/ginandjuice.ytt.yaml
================================================
#@ load("@ytt:data", "data")
#@ load("@ytt:json", "json")
#@ def get_value(key, default=""):
#@ if hasattr(data.values, key):
#@ return str(getattr(data.values, key))
#@ else:
#@ return default
#@ end
#@ end
timestamp: 2024-02-20T19:24:13+05:32
url: https://ginandjuice.shop/users/3
request:
#@yaml/text-templated-strings
raw: |+
POST /users/3 HTTP/1.1
Host: ginandjuice.shop
Authorization: Bearer (@= get_value("token", "3x4mpl3t0k3n") @)
Accept-Encoding: gzip
Content-Type: application/x-www-form-urlencoded
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
foo=(@= json.encode(data.values.foo) @)&bar=(@= get_value("bar") @)&debug=(@= get_value("debug", "false") @)
================================================
FILE: pkg/input/formats/testdata/ytt/ytt-profile.yaml
================================================
list: pkg/input/formats/testdata/ytt/ginandjuice.ytt.yaml
input-mode: yaml
templates:
- integration_tests/fuzz/fuzz-body.yaml
var:
- debug=true
- bar=bar
vars-text-templating: true
var-file-paths:
- pkg/input/formats/testdata/ytt/ytt-vars.yaml
dast: true
================================================
FILE: pkg/input/formats/testdata/ytt/ytt-vars.yaml
================================================
token: foobar
foo:
bar: baz
================================================
FILE: pkg/input/formats/yaml/multidoc.go
================================================
package yaml
import (
"bytes"
"io"
"github.com/pkg/errors"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats"
"github.com/projectdiscovery/nuclei/v3/pkg/input/types"
YamlUtil "gopkg.in/yaml.v3"
)
// YamlMultiDocFormat is a Yaml format parser for nuclei
// input HTTP requests with multiple documents separated by ---
type YamlMultiDocFormat struct {
opts formats.InputFormatOptions
}
// New creates a new JSON format parser
func New() *YamlMultiDocFormat {
return &YamlMultiDocFormat{}
}
var _ formats.Format = &YamlMultiDocFormat{}
// proxifyRequest is a request for proxify
type proxifyRequest struct {
URL string `json:"url"`
Request struct {
Header map[string]string `json:"header"`
Body string `json:"body"`
Raw string `json:"raw"`
} `json:"request"`
}
// Name returns the name of the format
func (j *YamlMultiDocFormat) Name() string {
return "yaml"
}
func (j *YamlMultiDocFormat) SetOptions(options formats.InputFormatOptions) {
j.opts = options
}
// Parse parses the input and calls the provided callback
// function for each RawRequest it discovers.
func (j *YamlMultiDocFormat) Parse(input io.Reader, resultsCb formats.ParseReqRespCallback, filePath string) error {
finalInput := input
// Apply text templating if enabled
if j.opts.VarsTextTemplating {
data, err := io.ReadAll(input)
if err != nil {
return errors.Wrap(err, "could not read input")
}
tpl := []string{string(data)}
dvs := mapToKeyValueSlice(j.opts.Variables)
finalData, err := ytt(tpl, dvs, j.opts.VarsFilePaths)
if err != nil {
return errors.Wrap(err, "could not apply ytt templating")
}
finalInput = bytes.NewReader(finalData)
}
decoder := YamlUtil.NewDecoder(finalInput)
for {
var request proxifyRequest
if err := decoder.Decode(&request); err != nil {
if err == io.EOF {
break
}
return errors.Wrap(err, "could not decode yaml file")
}
raw := request.Request.Raw
if raw == "" {
continue
}
rawRequest, err := types.ParseRawRequestWithURL(raw, request.URL)
if err != nil {
gologger.Warning().Msgf("multidoc-yaml: Could not parse raw request %s: %s", request.URL, err)
continue
}
resultsCb(rawRequest)
}
return nil
}
================================================
FILE: pkg/input/formats/yaml/multidoc_test.go
================================================
package yaml
import (
"os"
"strings"
"testing"
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats"
"github.com/projectdiscovery/nuclei/v3/pkg/input/types"
"github.com/stretchr/testify/require"
)
func TestYamlFormatterParse(t *testing.T) {
format := New()
proxifyInputFile := "../testdata/ginandjuice.proxify.yaml"
expectedUrls := []string{
"https://ginandjuice.shop/blog/post?postId=3&source=proxify",
"https://ginandjuice.shop/users/3",
}
file, err := os.Open(proxifyInputFile)
require.Nilf(t, err, "error opening proxify input file: %v", err)
defer func() {
_ = file.Close()
}()
var urls []string
err = format.Parse(file, func(request *types.RequestResponse) bool {
urls = append(urls, request.URL.String())
return false
}, proxifyInputFile)
require.Nilf(t, err, "error parsing yaml file: %v", err)
require.Len(t, urls, len(expectedUrls), "invalid number of urls")
require.ElementsMatch(t, urls, expectedUrls, "invalid urls")
}
func TestYamlFormatterParseWithVariables(t *testing.T) {
format := New()
proxifyYttFile := "../testdata/ytt/ginandjuice.ytt.yaml"
expectedUrls := []string{
"https://ginandjuice.shop/users/3",
}
format.SetOptions(formats.InputFormatOptions{
VarsTextTemplating: true,
Variables: map[string]interface{}{
"foo": "catalog",
"bar": "product",
},
})
file, err := os.Open(proxifyYttFile)
require.Nilf(t, err, "error opening proxify ytt input file: %v", err)
defer func() {
_ = file.Close()
}()
var urls []string
err = format.Parse(file, func(request *types.RequestResponse) bool {
urls = append(urls, request.URL.String())
expectedRaw := `POST /users/3 HTTP/1.1
Host: ginandjuice.shop
Authorization: Bearer 3x4mpl3t0k3n
Accept-Encoding: gzip
Content-Type: application/x-www-form-urlencoded
Connection: close
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
foo="catalog"&bar=product&debug=false`
normalised := strings.ReplaceAll(request.Request.Raw, "\r\n", "\n")
require.Equal(t, expectedRaw, strings.TrimSuffix(normalised, "\n"), "request raw does not match expected value")
return false
}, proxifyYttFile)
require.Nilf(t, err, "error parsing yaml file: %v", err)
require.Len(t, urls, len(expectedUrls), "invalid number of urls")
require.ElementsMatch(t, urls, expectedUrls, "invalid urls")
}
================================================
FILE: pkg/input/formats/yaml/ytt.go
================================================
package yaml
import (
"fmt"
"strings"
yttcmd "carvel.dev/ytt/pkg/cmd/template"
yttui "carvel.dev/ytt/pkg/cmd/ui"
yttfiles "carvel.dev/ytt/pkg/files"
"gopkg.in/yaml.v2"
)
func ytt(tpl, dvs []string, varFiles []string) ([]byte, error) {
// create and invoke ytt "template" command
templatingOptions := yttcmd.NewOptions()
input, err := templatesAsInput(tpl...)
if err != nil {
return nil, err
}
if len(varFiles) > 0 {
// Load vaarFiles into the templating options.
templatingOptions.DataValuesFlags.FromFiles = varFiles
}
// equivalent to `--data-value-yaml`
templatingOptions.DataValuesFlags.KVsFromYAML = dvs
// for in-memory use, pipe output to "/dev/null"
noopUI := yttui.NewCustomWriterTTY(false, noopWriter{}, noopWriter{})
// Evaluate the template given the configured data values...
output := templatingOptions.RunWithFiles(input, noopUI)
if output.Err != nil {
return nil, output.Err
}
return output.DocSet.AsBytes()
}
// templatesAsInput conveniently wraps one or more strings, each in a files.File, into a template.Input.
func templatesAsInput(tpl ...string) (yttcmd.Input, error) {
var files []*yttfiles.File
for i, t := range tpl {
// to make this less brittle, you'll probably want to use well-defined names for `path`, here, for each input.
// this matters when you're processing errors which report based on these paths.
file, err := yttfiles.NewFileFromSource(yttfiles.NewBytesSource(fmt.Sprintf("tpl%d.yml", i), []byte(t)))
if err != nil {
return yttcmd.Input{}, err
}
files = append(files, file)
}
return yttcmd.Input{Files: files}, nil
}
func mapToKeyValueSlice(m map[string]interface{}) []string {
var result []string
for k, v := range m {
y, _ := yaml.Marshal(v)
result = append(result, fmt.Sprintf("%s=%s", k, strings.TrimSpace(string(y))))
}
return result
}
type noopWriter struct{}
func (w noopWriter) Write(data []byte) (int, error) { return len(data), nil }
================================================
FILE: pkg/input/provider/chunked.go
================================================
package provider
import (
"github.com/projectdiscovery/nuclei/v3/pkg/input/types"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
)
// TODO: Implement ChunkedInputProvider
// 1. Lazy loading of input targets
// 2. Load and execute in chunks that fit in memory
// 3. Eliminate use of HybridMap since it performs worst due to marshal/unmarshal overhead
// ChunkedInputProvider is an input providing chunked targets instead of loading all at once
type ChunkedInputProvider interface {
// Count returns total targets for input provider
Count() int64
// Iterate over all inputs in order
Iterate(callback func(value *contextargs.MetaInput) bool)
// Set adds item to input provider
Set(value string)
// SetWithProbe adds item to input provider with http probing
SetWithProbe(value string, probe types.InputLivenessProbe) error
// SetWithExclusions adds item to input provider if it doesn't match any of the exclusions
SetWithExclusions(value string) error
// InputType returns the type of input provider
InputType() string
// Switches to the next chunk/batch of input
NextChunk() bool
}
================================================
FILE: pkg/input/provider/http/multiformat.go
================================================
package http
import (
"bytes"
"io"
"os"
"strings"
"github.com/pkg/errors"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats"
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/burp"
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/json"
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/openapi"
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/swagger"
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/yaml"
"github.com/projectdiscovery/nuclei/v3/pkg/input/types"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
)
// HttpMultiFormatOptions contains options for the http input provider
type HttpMultiFormatOptions struct {
// Options for the http input provider
Options formats.InputFormatOptions
// InputFile is the file containing the input
InputFile string
// InputMode is the mode of input
InputMode string
// optional input reader
InputContents string
}
// HttpInputProvider implements an input provider for nuclei that loads
// inputs from multiple formats like burp, openapi, postman,proxify, etc.
type HttpInputProvider struct {
format formats.Format
inputData []byte
inputFile string
count int64
}
// NewHttpInputProvider creates a new input provider for nuclei from a file
// or an input string
//
// The first preference is given to input file if provided
// otherwise it will use the input string
func NewHttpInputProvider(opts *HttpMultiFormatOptions) (*HttpInputProvider, error) {
var format formats.Format
for _, provider := range providersList {
if provider.Name() == opts.InputMode {
format = provider
}
}
if format == nil {
return nil, errors.Errorf("invalid input mode %s", opts.InputMode)
}
format.SetOptions(opts.Options)
// Do a first pass over the input to identify any errors
// and get the count of the input file as well
count := int64(0)
var inputFile *os.File
var inputReader io.Reader
if opts.InputFile != "" {
file, err := os.Open(opts.InputFile)
if err != nil {
return nil, errors.Wrap(err, "could not open input file")
}
inputFile = file
inputReader = file
} else {
inputReader = strings.NewReader(opts.InputContents)
}
defer func() {
if inputFile != nil {
_ = inputFile.Close()
}
}()
data, err := io.ReadAll(inputReader)
if err != nil {
return nil, errors.Wrap(err, "could not read input file")
}
if len(data) == 0 {
return nil, errors.New("input file is empty")
}
parseErr := format.Parse(bytes.NewReader(data), func(request *types.RequestResponse) bool {
count++
return false
}, opts.InputFile)
if parseErr != nil {
return nil, errors.Wrap(parseErr, "could not parse input file")
}
return &HttpInputProvider{format: format, inputData: data, inputFile: opts.InputFile, count: count}, nil
}
// Count returns the number of items for input provider
func (i *HttpInputProvider) Count() int64 {
return i.count
}
// Iterate over all inputs in order
func (i *HttpInputProvider) Iterate(callback func(value *contextargs.MetaInput) bool) {
err := i.format.Parse(bytes.NewReader(i.inputData), func(request *types.RequestResponse) bool {
metaInput := contextargs.NewMetaInput()
metaInput.ReqResp = request
metaInput.Input = request.URL.String()
return callback(metaInput)
}, i.inputFile)
if err != nil {
gologger.Warning().Msgf("Could not parse input file while iterating: %s\n", err)
}
}
// Set adds item to input provider
// No-op for this provider
func (i *HttpInputProvider) Set(_ string, value string) {}
// SetWithProbe adds item to input provider with http probing
// No-op for this provider
func (i *HttpInputProvider) SetWithProbe(_ string, value string, probe types.InputLivenessProbe) error {
return nil
}
// SetWithExclusions adds item to input provider if it doesn't match any of the exclusions
// No-op for this provider
func (i *HttpInputProvider) SetWithExclusions(_ string, value string) error {
return nil
}
// InputType returns the type of input provider
func (i *HttpInputProvider) InputType() string {
return "MultiFormatInputProvider"
}
// Close closes the input provider and cleans up any resources
// No-op for this provider
func (i *HttpInputProvider) Close() {}
// Supported Providers
var providersList = []formats.Format{
burp.New(),
json.New(),
yaml.New(),
openapi.New(),
swagger.New(),
}
// SupportedFormats returns the list of supported formats in comma-separated
// manner
func SupportedFormats() string {
var formats []string
for _, provider := range providersList {
formats = append(formats, provider.Name())
}
return strings.Join(formats, ", ")
}
================================================
FILE: pkg/input/provider/interface.go
================================================
package provider
import (
"errors"
"fmt"
"strings"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats"
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/openapi"
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/swagger"
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider/http"
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider/list"
"github.com/projectdiscovery/nuclei/v3/pkg/input/types"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
configTypes "github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/retryablehttp-go"
"github.com/projectdiscovery/utils/errkit"
stringsutil "github.com/projectdiscovery/utils/strings"
)
var (
ErrNotImplemented = errkit.New("provider does not implement method")
ErrInactiveInput = fmt.Errorf("input is inactive")
)
const (
MultiFormatInputProvider = "MultiFormatInputProvider"
ListInputProvider = "ListInputProvider"
SimpleListInputProvider = "SimpleInputProvider"
)
// IsErrNotImplemented checks if an error is a not implemented error
func IsErrNotImplemented(err error) bool {
if err == nil {
return false
}
if stringsutil.ContainsAll(err.Error(), "provider", "does not implement") {
return true
}
return false
}
// Validate all Implementations
var (
// SimpleInputProvider is more like a No-Op and returns given list of urls as input
_ InputProvider = &SimpleInputProvider{}
// HttpInputProvider provides support for formats that contain complete request/response
// like burp, openapi, postman,proxify, etc.
_ InputProvider = &http.HttpInputProvider{}
// ListInputProvider provides support for simple list of urls or files etc
_ InputProvider = &list.ListInputProvider{}
)
// InputProvider is unified input provider interface that provides
// processed inputs to nuclei by parsing and providing different
// formats such as list,openapi,postman,proxify,burp etc.
type InputProvider interface {
// Count returns total targets for input provider
Count() int64
// Iterate over all inputs in order
Iterate(callback func(value *contextargs.MetaInput) bool)
// Set adds item to input provider
Set(executionId string, value string)
// SetWithProbe adds item to input provider with http probing
SetWithProbe(executionId string, value string, probe types.InputLivenessProbe) error
// SetWithExclusions adds item to input provider if it doesn't match any of the exclusions
SetWithExclusions(executionId string, value string) error
// InputType returns the type of input provider
InputType() string
// Close the input provider and cleanup any resources
Close()
}
// InputOptions contains options for input provider
type InputOptions struct {
// Options for global config
Options *configTypes.Options
// TempDir is the temporary directory for storing files
TempDir string
// NotFoundCallback is the callback to call when input is not found
// only supported in list input provider
NotFoundCallback func(template string) bool
}
// NewInputProvider creates a new input provider based on the options
// and returns it
func NewInputProvider(opts InputOptions) (InputProvider, error) {
// optionally load generated vars values if available
val, err := formats.ReadOpenAPIVarDumpFile()
if err != nil && !errors.Is(err, formats.ErrNoVarsDumpFile) {
// log error and continue
gologger.Error().Msgf("Could not read vars dump file: %s\n", err)
}
extraVars := make(map[string]interface{})
if val != nil {
for _, v := range val.Var {
v = strings.TrimSpace(v)
// split into key value
parts := strings.SplitN(v, "=", 2)
if len(parts) == 2 {
extraVars[parts[0]] = parts[1]
}
}
}
// check if input provider is supported
if strings.EqualFold(opts.Options.InputFileMode, "list") {
// create a new list input provider
return list.New(&list.Options{
Options: opts.Options,
NotFoundCallback: opts.NotFoundCallback,
})
} else if len(opts.Options.Targets) > 0 &&
(strings.EqualFold(opts.Options.InputFileMode, "openapi") || strings.EqualFold(opts.Options.InputFileMode, "swagger")) {
if len(opts.Options.Targets) > 1 {
return nil, fmt.Errorf("only one target URL is supported in %s input mode", opts.Options.InputFileMode)
}
target := opts.Options.Targets[0]
if strings.HasPrefix(target, "http://") || strings.HasPrefix(target, "https://") {
var downloader formats.SpecDownloader
var tempFile string
var err error
// Get HttpClient from protocolstate if available
var httpClient *retryablehttp.Client
if opts.Options.ExecutionId != "" {
dialers := protocolstate.GetDialersWithId(opts.Options.ExecutionId)
if dialers != nil {
httpClient = dialers.DefaultHTTPClient
}
}
switch strings.ToLower(opts.Options.InputFileMode) {
case "openapi":
downloader = openapi.NewDownloader()
tempFile, err = downloader.Download(target, opts.TempDir, httpClient)
case "swagger":
downloader = swagger.NewDownloader()
tempFile, err = downloader.Download(target, opts.TempDir, httpClient)
default:
return nil, fmt.Errorf("unsupported input mode: %s", opts.Options.InputFileMode)
}
if err != nil {
return nil, fmt.Errorf("failed to download %s spec from url %s: %w", opts.Options.InputFileMode, target, err)
}
opts.Options.TargetsFilePath = tempFile
}
}
return http.NewHttpInputProvider(&http.HttpMultiFormatOptions{
InputFile: opts.Options.TargetsFilePath,
InputMode: opts.Options.InputFileMode,
Options: formats.InputFormatOptions{
Variables: generators.MergeMaps(extraVars, opts.Options.Vars.AsMap()),
SkipFormatValidation: opts.Options.SkipFormatValidation,
RequiredOnly: opts.Options.FormatUseRequiredOnly,
VarsTextTemplating: opts.Options.VarsTextTemplating,
VarsFilePaths: opts.Options.VarsFilePaths,
},
})
}
// SupportedInputFormats returns all supported input formats of nuclei
func SupportedInputFormats() string {
return "list, " + http.SupportedFormats()
}
================================================
FILE: pkg/input/provider/list/hmap.go
================================================
// package list implements a hybrid hmap/filekv backed input provider
// for nuclei that can either stream or store results using different kv stores.
package list
import (
"bufio"
"context"
"fmt"
"io"
"net"
"os"
"regexp"
"strings"
"sync"
"time"
"github.com/pkg/errors"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/hmap/filekv"
"github.com/projectdiscovery/hmap/store/hybrid"
"github.com/projectdiscovery/mapcidr/asn"
providerTypes "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/uncover"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/expand"
uncoverlib "github.com/projectdiscovery/uncover"
fileutil "github.com/projectdiscovery/utils/file"
iputil "github.com/projectdiscovery/utils/ip"
readerutil "github.com/projectdiscovery/utils/reader"
sliceutil "github.com/projectdiscovery/utils/slice"
urlutil "github.com/projectdiscovery/utils/url"
)
const DefaultMaxDedupeItemsCount = 10000
// ListInputProvider is a hmap/filekv backed nuclei ListInputProvider provider
// it supports list type of input ex: urls,file,stdin,uncover,etc. (i.e just url not complete request/response)
type ListInputProvider struct {
ipOptions *ipOptions
inputCount int64
excludedCount int64
dupeCount int64
skippedCount int64
hostMap *hybrid.HybridMap
excludedHosts map[string]struct{}
hostMapStream *filekv.FileDB
hostMapStreamOnce sync.Once
sync.Once
}
// Options is a wrapper around types.Options structure
type Options struct {
// Options contains options for hmap provider
Options *types.Options
// NotFoundCallback is called for each not found target
// This overrides error handling for not found target
NotFoundCallback func(template string) bool
}
// New creates a new hmap backed nuclei Input Provider
// and initializes it based on the passed options Model.
func New(opts *Options) (*ListInputProvider, error) {
options := opts.Options
hm, err := hybrid.New(hybrid.DefaultDiskOptions)
if err != nil {
return nil, errors.Wrap(err, "could not create temporary input file")
}
input := &ListInputProvider{
hostMap: hm,
ipOptions: &ipOptions{
ScanAllIPs: options.ScanAllIPs,
IPV4: sliceutil.Contains(options.IPVersion, "4"),
IPV6: sliceutil.Contains(options.IPVersion, "6"),
},
excludedHosts: make(map[string]struct{}),
}
if options.Stream {
fkvOptions := filekv.DefaultOptions
fkvOptions.MaxItems = DefaultMaxDedupeItemsCount
if tmpFileName, err := fileutil.GetTempFileName(); err != nil {
return nil, errors.Wrap(err, "could not create temporary input file")
} else {
fkvOptions.Path = tmpFileName
}
fkv, err := filekv.Open(fkvOptions)
if err != nil {
return nil, errors.Wrap(err, "could not create temporary unsorted input file")
}
input.hostMapStream = fkv
}
if initErr := input.initializeInputSources(opts); initErr != nil {
return nil, initErr
}
if input.excludedCount > 0 {
gologger.Info().Msgf("Number of hosts excluded from input: %d", input.excludedCount)
}
if input.dupeCount > 0 {
gologger.Info().Msgf("Supplied input was automatically deduplicated (%d removed).", input.dupeCount)
}
if input.skippedCount > 0 {
gologger.Info().Msgf("Number of hosts skipped from input due to exclusion: %d", input.skippedCount)
}
return input, nil
}
// Count returns the input count
func (i *ListInputProvider) Count() int64 {
return i.inputCount
}
// Iterate over all inputs in order
func (i *ListInputProvider) Iterate(callback func(value *contextargs.MetaInput) bool) {
if i.hostMapStream != nil {
i.hostMapStreamOnce.Do(func() {
if err := i.hostMapStream.Process(); err != nil {
gologger.Warning().Msgf("error in stream mode processing: %s\n", err)
}
})
}
callbackFunc := func(k, _ []byte) error {
metaInput := contextargs.NewMetaInput()
if err := metaInput.Unmarshal(string(k)); err != nil {
return err
}
if !callback(metaInput) {
return io.EOF
}
return nil
}
if i.hostMapStream != nil {
_ = i.hostMapStream.Scan(callbackFunc)
} else {
i.hostMap.Scan(callbackFunc)
}
}
// Set normalizes and stores passed input values
func (i *ListInputProvider) Set(executionId string, value string) {
URL := strings.TrimSpace(value)
if URL == "" {
return
}
// parse hostname if url is given
urlx, err := urlutil.Parse(URL)
if err != nil || (urlx != nil && urlx.Host == "") {
gologger.Debug().Label("url").MsgFunc(func() string {
if err != nil {
return fmt.Sprintf("failed to parse url %v got %v skipping ip selection", URL, err)
}
return fmt.Sprintf("got empty hostname for %v skipping ip selection", URL)
})
metaInput := contextargs.NewMetaInput()
metaInput.Input = URL
i.setItem(metaInput)
return
}
// Check if input is ip or hostname
if iputil.IsIP(urlx.Hostname()) {
metaInput := contextargs.NewMetaInput()
metaInput.Input = URL
i.setItem(metaInput)
return
}
if i.ipOptions.ScanAllIPs {
// scan all ips
dialers := protocolstate.GetDialersWithId(executionId)
if dialers == nil {
panic("dialers with executionId " + executionId + " not found")
}
dnsData, err := dialers.Fastdialer.GetDNSData(urlx.Hostname())
if err == nil {
if (len(dnsData.A) + len(dnsData.AAAA)) > 0 {
var ips []string
if i.ipOptions.IPV4 {
ips = append(ips, dnsData.A...)
}
if i.ipOptions.IPV6 {
ips = append(ips, dnsData.AAAA...)
}
for _, ip := range ips {
if ip == "" {
continue
}
metaInput := contextargs.NewMetaInput()
metaInput.Input = URL
metaInput.CustomIP = ip
i.setItem(metaInput)
}
return
} else {
gologger.Debug().Msgf("scanAllIps: no ip's found reverting to default")
}
} else {
// failed to scanallips falling back to defaults
gologger.Debug().Msgf("scanAllIps: dns resolution failed: %v", err)
}
}
ips := []string{}
// only scan the target but ipv6 if it has one
if i.ipOptions.IPV6 {
dialers := protocolstate.GetDialersWithId(executionId)
if dialers == nil {
panic("dialers with executionId " + executionId + " not found")
}
dnsData, err := dialers.Fastdialer.GetDNSData(urlx.Hostname())
if err == nil && len(dnsData.AAAA) > 0 {
// pick/ prefer 1st
ips = append(ips, dnsData.AAAA[0])
} else {
gologger.Warning().Msgf("target does not have ipv6 address falling back to ipv4 %v\n", err)
}
}
if i.ipOptions.IPV4 {
// if IPV4 is enabled do not specify ip let dialer handle it
ips = append(ips, "")
}
for _, ip := range ips {
metaInput := contextargs.NewMetaInput()
if ip != "" {
metaInput.Input = URL
metaInput.CustomIP = ip
i.setItem(metaInput)
} else {
metaInput.Input = URL
i.setItem(metaInput)
}
}
}
// SetWithProbe only sets the input if it is live
func (i *ListInputProvider) SetWithProbe(executionId string, value string, probe providerTypes.InputLivenessProbe) error {
probedValue, err := probe.ProbeURL(value)
if err != nil {
return err
}
i.Set(executionId, probedValue)
return nil
}
// SetWithExclusions normalizes and stores passed input values if not excluded
func (i *ListInputProvider) SetWithExclusions(executionId string, value string) error {
URL := strings.TrimSpace(value)
if URL == "" {
return nil
}
if i.isExcluded(URL) {
i.skippedCount++
return nil
}
i.Set(executionId, URL)
return nil
}
// ListInputProvider is a hmap/filekv backed nuclei ListInputProvider provider
func (i *ListInputProvider) InputType() string {
return "ListInputProvider"
}
// Close closes the input provider
func (i *ListInputProvider) Close() {
_ = i.hostMap.Close()
if i.hostMapStream != nil {
i.hostMapStream.Close()
}
}
// initializeInputSources initializes the input sources for hmap input
func (i *ListInputProvider) initializeInputSources(opts *Options) error {
options := opts.Options
// Handle targets flags
for _, target := range options.Targets {
switch {
case iputil.IsCIDR(target):
ips := expand.CIDR(target)
i.addTargets(options.ExecutionId, ips)
case asn.IsASN(target):
ips := expand.ASN(target)
i.addTargets(options.ExecutionId, ips)
default:
i.Set(options.ExecutionId, target)
}
}
// Handle stdin
if options.Stdin {
i.scanInputFromReader(
options.ExecutionId,
readerutil.TimeoutReader{Reader: os.Stdin, Timeout: time.Duration(options.InputReadTimeout)})
}
// Handle target file
if options.TargetsFilePath != "" {
input, inputErr := os.Open(options.TargetsFilePath)
if inputErr != nil {
// Handle cloud based input here.
if opts.NotFoundCallback == nil || !opts.NotFoundCallback(options.TargetsFilePath) {
return errors.Wrap(inputErr, "could not open targets file")
}
}
if input != nil {
i.scanInputFromReader(options.ExecutionId, input)
_ = input.Close()
}
}
if options.Uncover && options.UncoverQuery != nil {
gologger.Info().Msgf("Running uncover query against: %s", strings.Join(options.UncoverEngine, ","))
uncoverOpts := &uncoverlib.Options{
Agents: options.UncoverEngine,
Queries: options.UncoverQuery,
Limit: options.UncoverLimit,
MaxRetry: options.Retries,
Timeout: options.Timeout,
RateLimit: uint(options.UncoverRateLimit),
RateLimitUnit: time.Minute, // default unit is minute
}
ch, err := uncover.GetTargetsFromUncover(context.TODO(), options.UncoverField, uncoverOpts)
if err != nil {
return err
}
for c := range ch {
i.Set(options.ExecutionId, c)
}
}
if len(options.ExcludeTargets) > 0 {
for _, target := range options.ExcludeTargets {
switch {
case iputil.IsCIDR(target):
i.removeTargets([]string{target})
case asn.IsASN(target):
cidrs, _ := asn.GetCIDRsForASNNum(target)
cidrStrs := make([]string, 0, len(cidrs))
for _, cidr := range cidrs {
cidrStrs = append(cidrStrs, cidr.String())
}
i.removeTargets(cidrStrs)
default:
i.Del(options.ExecutionId, target)
}
}
}
return nil
}
// scanInputFromReader scans a line of input from reader and passes it for storage
func (i *ListInputProvider) scanInputFromReader(executionId string, reader io.Reader) {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
item := scanner.Text()
switch {
case iputil.IsCIDR(item):
ips := expand.CIDR(item)
i.addTargets(executionId, ips)
case asn.IsASN(item):
ips := expand.ASN(item)
i.addTargets(executionId, ips)
default:
i.Set(executionId, item)
}
}
}
// isExcluded checks if a URL is in the exclusion list
func (i *ListInputProvider) isExcluded(URL string) bool {
metaInput := contextargs.NewMetaInput()
metaInput.Input = URL
key, err := metaInput.MarshalString()
if err != nil {
gologger.Warning().Msgf("%s\n", err)
return false
}
_, exists := i.excludedHosts[key]
return exists
}
func (i *ListInputProvider) Del(executionId string, value string) {
URL := strings.TrimSpace(value)
if URL == "" {
return
}
// parse hostname if url is given
urlx, err := urlutil.Parse(URL)
if err != nil || (urlx != nil && urlx.Host == "") {
gologger.Debug().Label("url").MsgFunc(func() string {
if err != nil {
return fmt.Sprintf("failed to parse url %v got %v skipping ip selection", URL, err)
}
return fmt.Sprintf("got empty hostname for %v skipping ip selection", URL)
})
metaInput := contextargs.NewMetaInput()
metaInput.Input = URL
i.delItem(metaInput)
return
}
// Check if input is ip or hostname
if iputil.IsIP(urlx.Hostname()) {
metaInput := contextargs.NewMetaInput()
metaInput.Input = URL
i.delItem(metaInput)
return
}
if i.ipOptions.ScanAllIPs {
// scan all ips
dialers := protocolstate.GetDialersWithId(executionId)
if dialers == nil {
panic("dialers with executionId " + executionId + " not found")
}
dnsData, err := dialers.Fastdialer.GetDNSData(urlx.Hostname())
if err == nil {
if (len(dnsData.A) + len(dnsData.AAAA)) > 0 {
var ips []string
if i.ipOptions.IPV4 {
ips = append(ips, dnsData.A...)
}
if i.ipOptions.IPV6 {
ips = append(ips, dnsData.AAAA...)
}
for _, ip := range ips {
if ip == "" {
continue
}
metaInput := contextargs.NewMetaInput()
metaInput.Input = value
metaInput.CustomIP = ip
i.delItem(metaInput)
}
return
} else {
gologger.Debug().Msgf("scanAllIps: no ip's found reverting to default")
}
} else {
// failed to scanallips falling back to defaults
gologger.Debug().Msgf("scanAllIps: dns resolution failed: %v", err)
}
}
ips := []string{}
// only scan the target but ipv6 if it has one
if i.ipOptions.IPV6 {
dialers := protocolstate.GetDialersWithId(executionId)
if dialers == nil {
panic("dialers with executionId " + executionId + " not found")
}
dnsData, err := dialers.Fastdialer.GetDNSData(urlx.Hostname())
if err == nil && len(dnsData.AAAA) > 0 {
// pick/ prefer 1st
ips = append(ips, dnsData.AAAA[0])
} else {
gologger.Warning().Msgf("target does not have ipv6 address falling back to ipv4 %v\n", err)
}
}
if i.ipOptions.IPV4 {
// if IPV4 is enabled do not specify ip let dialer handle it
ips = append(ips, "")
}
for _, ip := range ips {
metaInput := contextargs.NewMetaInput()
if ip != "" {
metaInput.Input = URL
metaInput.CustomIP = ip
i.delItem(metaInput)
} else {
metaInput.Input = URL
i.delItem(metaInput)
}
}
}
// setItem in the kv store
func (i *ListInputProvider) setItem(metaInput *contextargs.MetaInput) {
key, err := metaInput.MarshalString()
if err != nil {
gologger.Warning().Msgf("%s\n", err)
return
}
if _, ok := i.hostMap.Get(key); ok {
i.dupeCount++
return
}
i.inputCount++ // tracks target count
_ = i.hostMap.Set(key, nil)
if i.hostMapStream != nil {
i.setHostMapStream(key)
}
}
const removeTargetsChunkSize = 5000
// delItem removes all hostMap entries matching any of the given targets in a single scan.
func (i *ListInputProvider) delItem(targets ...*contextargs.MetaInput) {
type parsedTarget struct {
host string
regex *regexp.Regexp
}
var parsed []parsedTarget
for _, mi := range targets {
targetUrl, err := urlutil.ParseURL(mi.Input, true)
if err != nil {
continue
}
re, _ := regexp.Compile(mi.Input)
parsed = append(parsed, parsedTarget{host: targetUrl.Host, regex: re})
}
if len(parsed) == 0 {
return
}
var keysToDelete []string
i.hostMap.Scan(func(k, _ []byte) error {
var tmpMetaInput contextargs.MetaInput
if err := tmpMetaInput.Unmarshal(string(k)); err != nil {
return nil
}
tmpKey, err := tmpMetaInput.MarshalString()
if err != nil {
return nil
}
tmpUrl, err := urlutil.ParseURL(tmpMetaInput.Input, true)
if err != nil {
return nil
}
for _, pt := range parsed {
if tmpUrl.Host == pt.host || (pt.regex != nil && pt.regex.MatchString(tmpUrl.Host)) {
keysToDelete = append(keysToDelete, tmpKey)
break
}
}
return nil
})
for _, key := range keysToDelete {
_ = i.hostMap.Del(key)
i.excludedHosts[key] = struct{}{}
i.excludedCount++
i.inputCount--
}
}
// setHostMapStream sets item in stream mode
func (i *ListInputProvider) setHostMapStream(data string) {
if _, err := i.hostMapStream.Merge([][]byte{[]byte(data)}); err != nil {
gologger.Warning().Msgf("%s\n", err)
return
}
}
func (i *ListInputProvider) addTargets(executionId string, targets []string) {
for _, target := range targets {
i.Set(executionId, target)
}
}
func (i *ListInputProvider) removeTargets(targets []string) {
var cidrs []*net.IPNet
var otherTargets []string
for _, t := range targets {
if _, ipnet, err := net.ParseCIDR(t); err == nil {
cidrs = append(cidrs, ipnet)
} else {
otherTargets = append(otherTargets, t)
}
}
// CIDR targets: single scan with containment check
if len(cidrs) > 0 {
var keysToDelete []string
i.hostMap.Scan(func(k, _ []byte) error {
var mi contextargs.MetaInput
if err := mi.Unmarshal(string(k)); err != nil {
return nil
}
key, err := mi.MarshalString()
if err != nil {
return nil
}
parsed, err := urlutil.ParseURL(mi.Input, true)
if err != nil {
return nil
}
if matchesCIDR(parsed.Hostname(), cidrs) || (mi.CustomIP != "" && matchesCIDR(mi.CustomIP, cidrs)) {
keysToDelete = append(keysToDelete, key)
}
return nil
})
for _, key := range keysToDelete {
_ = i.hostMap.Del(key)
i.excludedHosts[key] = struct{}{}
i.excludedCount++
i.inputCount--
}
}
// other targets: chunked delItem with existing hostname+regex matching
for start := 0; start < len(otherTargets); start += removeTargetsChunkSize {
end := start + removeTargetsChunkSize
if end > len(otherTargets) {
end = len(otherTargets)
}
chunk := otherTargets[start:end]
metaInputs := make([]*contextargs.MetaInput, 0, len(chunk))
for _, target := range chunk {
mi := contextargs.NewMetaInput()
mi.Input = target
metaInputs = append(metaInputs, mi)
}
i.delItem(metaInputs...)
}
}
func matchesCIDR(host string, cidrs []*net.IPNet) bool {
ip := net.ParseIP(host)
if ip == nil {
return false
}
for _, ipnet := range cidrs {
if ipnet.Contains(ip) {
return true
}
}
return false
}
================================================
FILE: pkg/input/provider/list/hmap_test.go
================================================
package list
import (
"net"
"os"
"strconv"
"strings"
"testing"
"github.com/miekg/dns"
"github.com/projectdiscovery/hmap/store/hybrid"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/expand"
"github.com/projectdiscovery/utils/auth/pdcp"
"github.com/stretchr/testify/require"
)
func Test_expandCIDR(t *testing.T) {
tests := []struct {
cidr string
expected []string
}{
{
cidr: "173.0.84.0/30",
expected: []string{"173.0.84.0", "173.0.84.1", "173.0.84.2", "173.0.84.3"},
}, {
cidr: "104.154.124.0/29",
expected: []string{"104.154.124.0", "104.154.124.1", "104.154.124.2", "104.154.124.3", "104.154.124.4", "104.154.124.5", "104.154.124.6", "104.154.124.7"},
},
}
for _, tt := range tests {
hm, err := hybrid.New(hybrid.DefaultDiskOptions)
require.Nil(t, err, "could not create temporary input file")
input := &ListInputProvider{hostMap: hm}
ips := expand.CIDR(tt.cidr)
input.addTargets("", ips)
// scan
got := []string{}
input.hostMap.Scan(func(k, _ []byte) error {
metainput := contextargs.NewMetaInput()
if err := metainput.Unmarshal(string(k)); err != nil {
return err
}
got = append(got, metainput.Input)
return nil
})
require.ElementsMatch(t, tt.expected, got, "could not get correct cidrs")
input.Close()
}
}
type mockDnsHandler struct{}
func (m *mockDnsHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
msg := dns.Msg{}
msg.SetReply(r)
switch r.Question[0].Qtype {
case dns.TypeA:
msg.Authoritative = true
domain := msg.Question[0].Name
msg.Answer = append(msg.Answer, &dns.A{
Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 60},
A: net.ParseIP("128.199.158.128"),
})
case dns.TypeAAAA:
msg.Authoritative = true
domain := msg.Question[0].Name
msg.Answer = append(msg.Answer, &dns.AAAA{
Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 60},
AAAA: net.ParseIP("2400:6180:0:d0::91:1001"),
})
}
_ = w.WriteMsg(&msg)
}
func Test_scanallips_normalizeStoreInputValue(t *testing.T) {
srv := &dns.Server{Addr: ":" + strconv.Itoa(61234), Net: "udp"}
srv.Handler = &mockDnsHandler{}
go func() {
err := srv.ListenAndServe()
require.Nil(t, err)
}()
defaultOpts := types.DefaultOptions()
defaultOpts.InternalResolversList = []string{"127.0.0.1:61234"}
_ = protocolstate.Init(defaultOpts)
type testcase struct {
hostname string
ipv4 bool
ipv6 bool
expected []string
}
tests := []testcase{
{
hostname: "scanme.sh",
ipv4: true,
expected: []string{"128.199.158.128"},
}, {
hostname: "scanme.sh",
ipv6: true,
expected: []string{"2400:6180:0:d0::91:1001"},
},
}
// add extra edge cases
urls := []string{
"https://scanme.sh/",
"http://scanme.sh",
"https://scanme.sh:443/",
"https://scanme.sh:443/somepath",
"http://scanme.sh:80/?with=param",
"scanme.sh/home",
"scanme.sh",
}
resolvedIps := []string{"128.199.158.128", "2400:6180:0:d0::91:1001"}
for _, v := range urls {
tests = append(tests, testcase{
hostname: v,
ipv4: true,
ipv6: true,
expected: resolvedIps,
})
}
for _, tt := range tests {
hm, err := hybrid.New(hybrid.DefaultDiskOptions)
require.Nil(t, err, "could not create temporary input file")
input := &ListInputProvider{
hostMap: hm,
ipOptions: &ipOptions{
ScanAllIPs: true,
IPV4: tt.ipv4,
IPV6: tt.ipv6,
},
}
input.Set(defaultOpts.ExecutionId, tt.hostname)
// scan
got := []string{}
input.hostMap.Scan(func(k, v []byte) error {
metainput := contextargs.NewMetaInput()
if err := metainput.Unmarshal(string(k)); err != nil {
return err
}
got = append(got, metainput.CustomIP)
return nil
})
require.ElementsMatchf(t, tt.expected, got, "could not get correct ips for hostname %v", tt.hostname)
input.Close()
}
}
func Test_expandASNInputValue(t *testing.T) {
// skip this test if pdcp keys are not present
h := pdcp.PDCPCredHandler{}
creds, err := h.GetCreds()
if err != nil || creds == nil || creds.APIKey == "" {
t.Logf("Skipping asnmap test as pdcp keys are not present")
t.SkipNow()
}
tests := []struct {
asn string
expectedOutputFile string
}{
{
asn: "AS14421",
expectedOutputFile: "tests/AS14421.txt",
},
{
asn: "AS134029",
expectedOutputFile: "tests/AS134029.txt",
},
}
for _, tt := range tests {
hm, err := hybrid.New(hybrid.DefaultDiskOptions)
require.Nil(t, err, "could not create temporary input file")
input := &ListInputProvider{hostMap: hm}
// get the IP addresses for ASN number
ips := expand.ASN(tt.asn)
input.addTargets("", ips)
// scan the hmap
got := []string{}
input.hostMap.Scan(func(k, v []byte) error {
metainput := contextargs.NewMetaInput()
if err := metainput.Unmarshal(string(k)); err != nil {
return err
}
got = append(got, metainput.Input)
return nil
})
if len(got) == 0 {
// asnmap server is down
t.SkipNow()
}
// read the expected IPs from the file
fileContent, err := os.ReadFile(tt.expectedOutputFile)
require.Nil(t, err, "could not read the expectedOutputFile file")
items := strings.Split(strings.ReplaceAll(string(fileContent), "\r\n", "\n"), "\n")
require.ElementsMatch(t, items, got, "could not get correct ips")
}
}
================================================
FILE: pkg/input/provider/list/tests/AS134029.txt
================================================
103.57.226.0
103.57.226.1
103.57.226.2
103.57.226.3
103.57.226.4
103.57.226.5
103.57.226.6
103.57.226.7
103.57.226.8
103.57.226.9
103.57.226.10
103.57.226.11
103.57.226.12
103.57.226.13
103.57.226.14
103.57.226.15
103.57.226.16
103.57.226.17
103.57.226.18
103.57.226.19
103.57.226.20
103.57.226.21
103.57.226.22
103.57.226.23
103.57.226.24
103.57.226.25
103.57.226.26
103.57.226.27
103.57.226.28
103.57.226.29
103.57.226.30
103.57.226.31
103.57.226.32
103.57.226.33
103.57.226.34
103.57.226.35
103.57.226.36
103.57.226.37
103.57.226.38
103.57.226.39
103.57.226.40
103.57.226.41
103.57.226.42
103.57.226.43
103.57.226.44
103.57.226.45
103.57.226.46
103.57.226.47
103.57.226.48
103.57.226.49
103.57.226.50
103.57.226.51
103.57.226.52
103.57.226.53
103.57.226.54
103.57.226.55
103.57.226.56
103.57.226.57
103.57.226.58
103.57.226.59
103.57.226.60
103.57.226.61
103.57.226.62
103.57.226.63
103.57.226.64
103.57.226.65
103.57.226.66
103.57.226.67
103.57.226.68
103.57.226.69
103.57.226.70
103.57.226.71
103.57.226.72
103.57.226.73
103.57.226.74
103.57.226.75
103.57.226.76
103.57.226.77
103.57.226.78
103.57.226.79
103.57.226.80
103.57.226.81
103.57.226.82
103.57.226.83
103.57.226.84
103.57.226.85
103.57.226.86
103.57.226.87
103.57.226.88
103.57.226.89
103.57.226.90
103.57.226.91
103.57.226.92
103.57.226.93
103.57.226.94
103.57.226.95
103.57.226.96
103.57.226.97
103.57.226.98
103.57.226.99
103.57.226.100
103.57.226.101
103.57.226.102
103.57.226.103
103.57.226.104
103.57.226.105
103.57.226.106
103.57.226.107
103.57.226.108
103.57.226.109
103.57.226.110
103.57.226.111
103.57.226.112
103.57.226.113
103.57.226.114
103.57.226.115
103.57.226.116
103.57.226.117
103.57.226.118
103.57.226.119
103.57.226.120
103.57.226.121
103.57.226.122
103.57.226.123
103.57.226.124
103.57.226.125
103.57.226.126
103.57.226.127
103.57.226.128
103.57.226.129
103.57.226.130
103.57.226.131
103.57.226.132
103.57.226.133
103.57.226.134
103.57.226.135
103.57.226.136
103.57.226.137
103.57.226.138
103.57.226.139
103.57.226.140
103.57.226.141
103.57.226.142
103.57.226.143
103.57.226.144
103.57.226.145
103.57.226.146
103.57.226.147
103.57.226.148
103.57.226.149
103.57.226.150
103.57.226.151
103.57.226.152
103.57.226.153
103.57.226.154
103.57.226.155
103.57.226.156
103.57.226.157
103.57.226.158
103.57.226.159
103.57.226.160
103.57.226.161
103.57.226.162
103.57.226.163
103.57.226.164
103.57.226.165
103.57.226.166
103.57.226.167
103.57.226.168
103.57.226.169
103.57.226.170
103.57.226.171
103.57.226.172
103.57.226.173
103.57.226.174
103.57.226.175
103.57.226.176
103.57.226.177
103.57.226.178
103.57.226.179
103.57.226.180
103.57.226.181
103.57.226.182
103.57.226.183
103.57.226.184
103.57.226.185
103.57.226.186
103.57.226.187
103.57.226.188
103.57.226.189
103.57.226.190
103.57.226.191
103.57.226.192
103.57.226.193
103.57.226.194
103.57.226.195
103.57.226.196
103.57.226.197
103.57.226.198
103.57.226.199
103.57.226.200
103.57.226.201
103.57.226.202
103.57.226.203
103.57.226.204
103.57.226.205
103.57.226.206
103.57.226.207
103.57.226.208
103.57.226.209
103.57.226.210
103.57.226.211
103.57.226.212
103.57.226.213
103.57.226.214
103.57.226.215
103.57.226.216
103.57.226.217
103.57.226.218
103.57.226.219
103.57.226.220
103.57.226.221
103.57.226.222
103.57.226.223
103.57.226.224
103.57.226.225
103.57.226.226
103.57.226.227
103.57.226.228
103.57.226.229
103.57.226.230
103.57.226.231
103.57.226.232
103.57.226.233
103.57.226.234
103.57.226.235
103.57.226.236
103.57.226.237
103.57.226.238
103.57.226.239
103.57.226.240
103.57.226.241
103.57.226.242
103.57.226.243
103.57.226.244
103.57.226.245
103.57.226.246
103.57.226.247
103.57.226.248
103.57.226.249
103.57.226.250
103.57.226.251
103.57.226.252
103.57.226.253
103.57.226.254
103.57.226.255
103.58.114.0
103.58.114.1
103.58.114.2
103.58.114.3
103.58.114.4
103.58.114.5
103.58.114.6
103.58.114.7
103.58.114.8
103.58.114.9
103.58.114.10
103.58.114.11
103.58.114.12
103.58.114.13
103.58.114.14
103.58.114.15
103.58.114.16
103.58.114.17
103.58.114.18
103.58.114.19
103.58.114.20
103.58.114.21
103.58.114.22
103.58.114.23
103.58.114.24
103.58.114.25
103.58.114.26
103.58.114.27
103.58.114.28
103.58.114.29
103.58.114.30
103.58.114.31
103.58.114.32
103.58.114.33
103.58.114.34
103.58.114.35
103.58.114.36
103.58.114.37
103.58.114.38
103.58.114.39
103.58.114.40
103.58.114.41
103.58.114.42
103.58.114.43
103.58.114.44
103.58.114.45
103.58.114.46
103.58.114.47
103.58.114.48
103.58.114.49
103.58.114.50
103.58.114.51
103.58.114.52
103.58.114.53
103.58.114.54
103.58.114.55
103.58.114.56
103.58.114.57
103.58.114.58
103.58.114.59
103.58.114.60
103.58.114.61
103.58.114.62
103.58.114.63
103.58.114.64
103.58.114.65
103.58.114.66
103.58.114.67
103.58.114.68
103.58.114.69
103.58.114.70
103.58.114.71
103.58.114.72
103.58.114.73
103.58.114.74
103.58.114.75
103.58.114.76
103.58.114.77
103.58.114.78
103.58.114.79
103.58.114.80
103.58.114.81
103.58.114.82
103.58.114.83
103.58.114.84
103.58.114.85
103.58.114.86
103.58.114.87
103.58.114.88
103.58.114.89
103.58.114.90
103.58.114.91
103.58.114.92
103.58.114.93
103.58.114.94
103.58.114.95
103.58.114.96
103.58.114.97
103.58.114.98
103.58.114.99
103.58.114.100
103.58.114.101
103.58.114.102
103.58.114.103
103.58.114.104
103.58.114.105
103.58.114.106
103.58.114.107
103.58.114.108
103.58.114.109
103.58.114.110
103.58.114.111
103.58.114.112
103.58.114.113
103.58.114.114
103.58.114.115
103.58.114.116
103.58.114.117
103.58.114.118
103.58.114.119
103.58.114.120
103.58.114.121
103.58.114.122
103.58.114.123
103.58.114.124
103.58.114.125
103.58.114.126
103.58.114.127
103.58.114.128
103.58.114.129
103.58.114.130
103.58.114.131
103.58.114.132
103.58.114.133
103.58.114.134
103.58.114.135
103.58.114.136
103.58.114.137
103.58.114.138
103.58.114.139
103.58.114.140
103.58.114.141
103.58.114.142
103.58.114.143
103.58.114.144
103.58.114.145
103.58.114.146
103.58.114.147
103.58.114.148
103.58.114.149
103.58.114.150
103.58.114.151
103.58.114.152
103.58.114.153
103.58.114.154
103.58.114.155
103.58.114.156
103.58.114.157
103.58.114.158
103.58.114.159
103.58.114.160
103.58.114.161
103.58.114.162
103.58.114.163
103.58.114.164
103.58.114.165
103.58.114.166
103.58.114.167
103.58.114.168
103.58.114.169
103.58.114.170
103.58.114.171
103.58.114.172
103.58.114.173
103.58.114.174
103.58.114.175
103.58.114.176
103.58.114.177
103.58.114.178
103.58.114.179
103.58.114.180
103.58.114.181
103.58.114.182
103.58.114.183
103.58.114.184
103.58.114.185
103.58.114.186
103.58.114.187
103.58.114.188
103.58.114.189
103.58.114.190
103.58.114.191
103.58.114.192
103.58.114.193
103.58.114.194
103.58.114.195
103.58.114.196
103.58.114.197
103.58.114.198
103.58.114.199
103.58.114.200
103.58.114.201
103.58.114.202
103.58.114.203
103.58.114.204
103.58.114.205
103.58.114.206
103.58.114.207
103.58.114.208
103.58.114.209
103.58.114.210
103.58.114.211
103.58.114.212
103.58.114.213
103.58.114.214
103.58.114.215
103.58.114.216
103.58.114.217
103.58.114.218
103.58.114.219
103.58.114.220
103.58.114.221
103.58.114.222
103.58.114.223
103.58.114.224
103.58.114.225
103.58.114.226
103.58.114.227
103.58.114.228
103.58.114.229
103.58.114.230
103.58.114.231
103.58.114.232
103.58.114.233
103.58.114.234
103.58.114.235
103.58.114.236
103.58.114.237
103.58.114.238
103.58.114.239
103.58.114.240
103.58.114.241
103.58.114.242
103.58.114.243
103.58.114.244
103.58.114.245
103.58.114.246
103.58.114.247
103.58.114.248
103.58.114.249
103.58.114.250
103.58.114.251
103.58.114.252
103.58.114.253
103.58.114.254
103.58.114.255
================================================
FILE: pkg/input/provider/list/tests/AS14421.txt
================================================
216.101.17.0
216.101.17.1
216.101.17.2
216.101.17.3
216.101.17.4
216.101.17.5
216.101.17.6
216.101.17.7
216.101.17.8
216.101.17.9
216.101.17.10
216.101.17.11
216.101.17.12
216.101.17.13
216.101.17.14
216.101.17.15
216.101.17.16
216.101.17.17
216.101.17.18
216.101.17.19
216.101.17.20
216.101.17.21
216.101.17.22
216.101.17.23
216.101.17.24
216.101.17.25
216.101.17.26
216.101.17.27
216.101.17.28
216.101.17.29
216.101.17.30
216.101.17.31
216.101.17.32
216.101.17.33
216.101.17.34
216.101.17.35
216.101.17.36
216.101.17.37
216.101.17.38
216.101.17.39
216.101.17.40
216.101.17.41
216.101.17.42
216.101.17.43
216.101.17.44
216.101.17.45
216.101.17.46
216.101.17.47
216.101.17.48
216.101.17.49
216.101.17.50
216.101.17.51
216.101.17.52
216.101.17.53
216.101.17.54
216.101.17.55
216.101.17.56
216.101.17.57
216.101.17.58
216.101.17.59
216.101.17.60
216.101.17.61
216.101.17.62
216.101.17.63
216.101.17.64
216.101.17.65
216.101.17.66
216.101.17.67
216.101.17.68
216.101.17.69
216.101.17.70
216.101.17.71
216.101.17.72
216.101.17.73
216.101.17.74
216.101.17.75
216.101.17.76
216.101.17.77
216.101.17.78
216.101.17.79
216.101.17.80
216.101.17.81
216.101.17.82
216.101.17.83
216.101.17.84
216.101.17.85
216.101.17.86
216.101.17.87
216.101.17.88
216.101.17.89
216.101.17.90
216.101.17.91
216.101.17.92
216.101.17.93
216.101.17.94
216.101.17.95
216.101.17.96
216.101.17.97
216.101.17.98
216.101.17.99
216.101.17.100
216.101.17.101
216.101.17.102
216.101.17.103
216.101.17.104
216.101.17.105
216.101.17.106
216.101.17.107
216.101.17.108
216.101.17.109
216.101.17.110
216.101.17.111
216.101.17.112
216.101.17.113
216.101.17.114
216.101.17.115
216.101.17.116
216.101.17.117
216.101.17.118
216.101.17.119
216.101.17.120
216.101.17.121
216.101.17.122
216.101.17.123
216.101.17.124
216.101.17.125
216.101.17.126
216.101.17.127
216.101.17.128
216.101.17.129
216.101.17.130
216.101.17.131
216.101.17.132
216.101.17.133
216.101.17.134
216.101.17.135
216.101.17.136
216.101.17.137
216.101.17.138
216.101.17.139
216.101.17.140
216.101.17.141
216.101.17.142
216.101.17.143
216.101.17.144
216.101.17.145
216.101.17.146
216.101.17.147
216.101.17.148
216.101.17.149
216.101.17.150
216.101.17.151
216.101.17.152
216.101.17.153
216.101.17.154
216.101.17.155
216.101.17.156
216.101.17.157
216.101.17.158
216.101.17.159
216.101.17.160
216.101.17.161
216.101.17.162
216.101.17.163
216.101.17.164
216.101.17.165
216.101.17.166
216.101.17.167
216.101.17.168
216.101.17.169
216.101.17.170
216.101.17.171
216.101.17.172
216.101.17.173
216.101.17.174
216.101.17.175
216.101.17.176
216.101.17.177
216.101.17.178
216.101.17.179
216.101.17.180
216.101.17.181
216.101.17.182
216.101.17.183
216.101.17.184
216.101.17.185
216.101.17.186
216.101.17.187
216.101.17.188
216.101.17.189
216.101.17.190
216.101.17.191
216.101.17.192
216.101.17.193
216.101.17.194
216.101.17.195
216.101.17.196
216.101.17.197
216.101.17.198
216.101.17.199
216.101.17.200
216.101.17.201
216.101.17.202
216.101.17.203
216.101.17.204
216.101.17.205
216.101.17.206
216.101.17.207
216.101.17.208
216.101.17.209
216.101.17.210
216.101.17.211
216.101.17.212
216.101.17.213
216.101.17.214
216.101.17.215
216.101.17.216
216.101.17.217
216.101.17.218
216.101.17.219
216.101.17.220
216.101.17.221
216.101.17.222
216.101.17.223
216.101.17.224
216.101.17.225
216.101.17.226
216.101.17.227
216.101.17.228
216.101.17.229
216.101.17.230
216.101.17.231
216.101.17.232
216.101.17.233
216.101.17.234
216.101.17.235
216.101.17.236
216.101.17.237
216.101.17.238
216.101.17.239
216.101.17.240
216.101.17.241
216.101.17.242
216.101.17.243
216.101.17.244
216.101.17.245
216.101.17.246
216.101.17.247
216.101.17.248
216.101.17.249
216.101.17.250
216.101.17.251
216.101.17.252
216.101.17.253
216.101.17.254
216.101.17.255
================================================
FILE: pkg/input/provider/list/utils.go
================================================
package list
type ipOptions struct {
ScanAllIPs bool
IPV4 bool
IPV6 bool
}
================================================
FILE: pkg/input/provider/simple.go
================================================
package provider
import (
"github.com/projectdiscovery/nuclei/v3/pkg/input/types"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
)
// SimpleInputProvider is a simple input provider for nuclei
// that acts like a No-Op and returns given list of urls as input
type SimpleInputProvider struct {
Inputs []*contextargs.MetaInput
}
// NewSimpleInputProvider creates a new simple input provider
func NewSimpleInputProvider() *SimpleInputProvider {
return &SimpleInputProvider{
Inputs: make([]*contextargs.MetaInput, 0),
}
}
// NewSimpleInputProviderWithUrls creates a new simple input provider with the given urls
func NewSimpleInputProviderWithUrls(executionId string, urls ...string) *SimpleInputProvider {
provider := NewSimpleInputProvider()
for _, url := range urls {
provider.Set(executionId, url)
}
return provider
}
// Count returns the total number of targets for the input provider
func (s *SimpleInputProvider) Count() int64 {
return int64(len(s.Inputs))
}
// Iterate over all inputs in order
func (s *SimpleInputProvider) Iterate(callback func(value *contextargs.MetaInput) bool) {
for _, input := range s.Inputs {
if !callback(input) {
break
}
}
}
// Set adds an item to the input provider
func (s *SimpleInputProvider) Set(_ string, value string) {
metaInput := contextargs.NewMetaInput()
metaInput.Input = value
s.Inputs = append(s.Inputs, metaInput)
}
// SetWithProbe adds an item to the input provider with HTTP probing
func (s *SimpleInputProvider) SetWithProbe(_ string, value string, probe types.InputLivenessProbe) error {
probedValue, err := probe.ProbeURL(value)
if err != nil {
return err
}
metaInput := contextargs.NewMetaInput()
metaInput.Input = probedValue
s.Inputs = append(s.Inputs, metaInput)
return nil
}
// SetWithExclusions adds an item to the input provider if it doesn't match any of the exclusions
func (s *SimpleInputProvider) SetWithExclusions(_ string, value string) error {
metaInput := contextargs.NewMetaInput()
metaInput.Input = value
s.Inputs = append(s.Inputs, metaInput)
return nil
}
// InputType returns the type of input provider
func (s *SimpleInputProvider) InputType() string {
return "SimpleInputProvider"
}
// Close the input provider and cleanup any resources
func (s *SimpleInputProvider) Close() {
// no-op
}
================================================
FILE: pkg/input/transform.go
================================================
package input
import (
"net"
"path/filepath"
"strings"
"github.com/projectdiscovery/hmap/store/hybrid"
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
fileutil "github.com/projectdiscovery/utils/file"
"github.com/projectdiscovery/utils/ports"
stringsutil "github.com/projectdiscovery/utils/strings"
urlutil "github.com/projectdiscovery/utils/url"
)
// Helper is a structure for helping with input transformation
type Helper struct {
InputsHTTP *hybrid.HybridMap
}
// NewHelper returns a new input helper instance
func NewHelper() *Helper {
helper := &Helper{}
return helper
}
// Close closes the resources associated with input helper
func (h *Helper) Close() error {
var err error
if h.InputsHTTP != nil {
err = h.InputsHTTP.Close()
}
return err
}
// Transform transforms an input based on protocol type and returns
// appropriate input based on it.
func (h *Helper) Transform(input string, protocol templateTypes.ProtocolType) string {
switch protocol {
case templateTypes.DNSProtocol, templateTypes.WHOISProtocol:
return h.convertInputToType(input, typeHostOnly, "")
case templateTypes.FileProtocol, templateTypes.OfflineHTTPProtocol:
return h.convertInputToType(input, typeFilepath, "")
case templateTypes.HTTPProtocol, templateTypes.HeadlessProtocol:
return h.convertInputToType(input, typeURL, "")
case templateTypes.NetworkProtocol:
return h.convertInputToType(input, typeHostWithOptionalPort, "")
case templateTypes.WebsocketProtocol:
return h.convertInputToType(input, typeWebsocket, "")
case templateTypes.SSLProtocol:
return h.convertInputToType(input, typeHostWithPort, "443")
}
return input
}
type inputType int
const (
typeHostOnly inputType = iota + 1
typeHostWithPort
typeHostWithOptionalPort
typeURL
typeFilepath
typeWebsocket
)
// convertInputToType converts an input based on an inputType.
// Various formats are supported for inputs and their transformation
func (h *Helper) convertInputToType(input string, inputType inputType, defaultPort string) string {
isURL := strings.Contains(input, "://")
uri, _ := urlutil.Parse(input)
var host, port string
if isURL && uri != nil {
host, port, _ = net.SplitHostPort(uri.Host)
} else {
host, port, _ = net.SplitHostPort(input)
}
hasHost := host != ""
hasPort := ports.IsValid(port)
hasDefaultPort := ports.IsValid(defaultPort)
switch inputType {
case typeFilepath:
// if it has ports most likely it's not a file
if hasPort {
return ""
}
if filepath.IsAbs(input) {
return input
}
if absPath, _ := filepath.Abs(input); absPath != "" && fileutil.FileOrFolderExists(absPath) {
return input
}
if _, err := filepath.Match(input, ""); err != filepath.ErrBadPattern && !isURL {
return input
}
// if none of these satisfy the condition return empty
return ""
case typeHostOnly:
if hasHost {
return host
}
if isURL && uri != nil {
return uri.Hostname()
}
return input
case typeURL:
if uri != nil && stringsutil.EqualFoldAny(uri.Scheme, "http", "https") {
return input
}
if h.InputsHTTP != nil {
if probed, ok := h.InputsHTTP.Get(input); ok {
return string(probed)
}
}
// try to parse it as absolute url and return
if absUrl, err := urlutil.ParseAbsoluteURL(input, false); err == nil {
return absUrl.String()
}
case typeHostWithPort, typeHostWithOptionalPort:
if hasHost && hasPort {
return net.JoinHostPort(host, port)
}
if uri != nil && !hasPort && uri.Scheme == "https" {
return net.JoinHostPort(uri.Host, "443")
}
if hasDefaultPort {
return net.JoinHostPort(input, defaultPort)
}
if inputType == typeHostWithOptionalPort {
return input
}
case typeWebsocket:
if uri != nil && stringsutil.EqualFoldAny(uri.Scheme, "ws", "wss") {
return input
}
// empty if prefix is not given
return ""
}
// do not return empty
return input
}
================================================
FILE: pkg/input/transform_test.go
================================================
package input
import (
"testing"
"github.com/projectdiscovery/hmap/store/hybrid"
"github.com/stretchr/testify/require"
)
func TestConvertInputToType(t *testing.T) {
helper := &Helper{}
hm, err := hybrid.New(hybrid.DefaultDiskOptions)
require.NoError(t, err, "could not create hybrid map")
helper.InputsHTTP = hm
defer func() {
_ = hm.Close()
}()
_ = hm.Set("google.com", []byte("https://google.com"))
tests := []struct {
input string
inputType inputType
result string
defaultPort string
}{
// host
{"google.com", typeHostOnly, "google.com", ""},
{"google.com:443", typeHostOnly, "google.com", ""},
{"https://google.com", typeHostOnly, "google.com", ""},
{"https://google.com:443", typeHostOnly, "google.com", ""},
// url
{"test.com", typeURL, "test.com", ""},
{"google.com", typeURL, "https://google.com", ""},
{"https://google.com", typeURL, "https://google.com", ""},
// file
{"google.com:443", typeFilepath, "", ""},
{"https://google.com:443", typeFilepath, "", ""},
{"/example/path", typeFilepath, "/example/path", ""},
{"input_test.go", typeFilepath, "input_test.go", ""},
{"../input", typeFilepath, "../input", ""},
{"input_test.*", typeFilepath, "input_test.*", ""},
// host-port
{"google.com", typeHostWithPort, "google.com", ""},
{"google.com:443", typeHostWithPort, "google.com:443", ""},
{"https://google.com", typeHostWithPort, "google.com:443", ""},
{"https://google.com:443", typeHostWithPort, "google.com:443", ""},
// host-port with default port
{"google.com", typeHostWithPort, "google.com:443", "443"},
// host with optional port
{"google.com", typeHostWithOptionalPort, "google.com", ""},
{"google.com:443", typeHostWithOptionalPort, "google.com:443", ""},
{"https://google.com", typeHostWithOptionalPort, "google.com:443", ""},
{"https://google.com:443", typeHostWithOptionalPort, "google.com:443", ""},
// host with optional port and default port
{"google.com", typeHostWithOptionalPort, "google.com:443", "443"},
// websocket
{"google.com", typeWebsocket, "", ""},
{"google.com:443", typeWebsocket, "", ""},
{"https://google.com:443", typeWebsocket, "", ""},
{"wss://google.com", typeWebsocket, "wss://google.com", ""},
}
for _, test := range tests {
result := helper.convertInputToType(test.input, test.inputType, test.defaultPort)
require.Equal(t, test.result, result, "could not get correct result %+v", test)
}
}
================================================
FILE: pkg/input/types/http.go
================================================
package types
import (
"bufio"
"bytes"
"crypto/sha256"
"fmt"
"io"
"net/textproto"
"strings"
"sync"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
"github.com/projectdiscovery/retryablehttp-go"
"github.com/projectdiscovery/useragent"
"github.com/projectdiscovery/utils/conversion"
mapsutil "github.com/projectdiscovery/utils/maps"
urlutil "github.com/projectdiscovery/utils/url"
)
var (
_ json.JSONCodec = &RequestResponse{}
)
// RequestResponse is a struct containing request and response
// obtained from one of the input formats.
// this struct can be considered as pd standard for request and response
type RequestResponse struct {
// Timestamp is the timestamp of the request
// Timestamp string `json:"timestamp"`
// URL is the URL of the request
URL urlutil.URL `json:"url"`
// Request is the request of the request
Request *HttpRequest `json:"request"`
// Response is the response of the request
Response *HttpResponse `json:"response"`
// unexported / internal fields
// lazy build request
req *retryablehttp.Request `json:"-"`
reqErr error `json:"-"`
once sync.Once `json:"-"`
}
// Clone clones the request response
func (rr *RequestResponse) Clone() *RequestResponse {
cloned := &RequestResponse{
URL: *rr.URL.Clone(),
}
if rr.Request != nil {
cloned.Request = rr.Request.Clone()
}
if rr.Response != nil {
cloned.Response = rr.Response.Clone()
}
return cloned
}
// BuildRequest builds a retryablehttp request from the request response
func (rr *RequestResponse) BuildRequest() (*retryablehttp.Request, error) {
rr.once.Do(func() {
urlx := rr.URL.Clone()
var body io.Reader = nil
if rr.Request.Body != "" {
body = strings.NewReader(rr.Request.Body)
}
req, err := retryablehttp.NewRequestFromURL(rr.Request.Method, urlx, body)
if err != nil {
rr.reqErr = fmt.Errorf("could not create request: %s", err)
return
}
rr.Request.Headers.Iterate(func(k, v string) bool {
req.Header.Add(k, v)
return true
})
if req.Header.Get("User-Agent") == "" {
userAgent := useragent.PickRandom()
req.Header.Set("User-Agent", userAgent.Raw)
}
rr.req = req
})
return rr.req, rr.reqErr
}
// To be implemented in the future
// func (rr *RequestResponse) BuildUnsafeRequest()
// ID returns a unique id/hash for request response
func (rr *RequestResponse) ID() string {
var buff bytes.Buffer
buff.WriteString(rr.URL.String())
if rr.Request != nil {
buff.WriteString(rr.Request.ID())
}
if rr.Response != nil {
buff.WriteString(rr.Response.ID())
}
val := sha256.Sum256(buff.Bytes())
return string(val[:])
}
// MarshalJSON marshals the request response to json
func (rr *RequestResponse) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{})
m["url"] = rr.URL.String()
reqBin, err := json.Marshal(rr.Request)
if err != nil {
return nil, err
}
m["request"] = reqBin
respBin, err := json.Marshal(rr.Response)
if err != nil {
return nil, err
}
m["response"] = respBin
return json.Marshal(m)
}
// UnmarshalJSON unmarshals the request response from json
func (rr *RequestResponse) UnmarshalJSON(data []byte) error {
var m map[string]json.Message
if err := json.Unmarshal(data, &m); err != nil {
return err
}
urlStrRaw, ok := m["url"]
if !ok {
return fmt.Errorf("missing url in request response")
}
var urlStr string
if err := json.Unmarshal(urlStrRaw, &urlStr); err != nil {
return err
}
parsed, err := urlutil.ParseAbsoluteURL(urlStr, false)
if err != nil {
return err
}
rr.URL = *parsed
reqBin, ok := m["request"]
if ok {
var req HttpRequest
if err := json.Unmarshal(reqBin, &req); err != nil {
return err
}
rr.Request = &req
}
respBin, ok := m["response"]
if ok {
var resp HttpResponse
if err := json.Unmarshal(respBin, &resp); err != nil {
return err
}
rr.Response = &resp
}
return nil
}
// HttpRequest is a struct containing the http request
type HttpRequest struct {
// method of the request
Method string `json:"method"`
// headers of the request
Headers mapsutil.OrderedMap[string, string] `json:"headers"`
// body of the request
Body string `json:"body"`
// raw request (includes everything including method, headers, body, etc)
Raw string `json:"raw"`
}
// ID returns a unique id/hash for raw request
func (hr *HttpRequest) ID() string {
val := sha256.Sum256([]byte(hr.Raw))
return string(val[:])
}
// Clone clones the request
func (hr *HttpRequest) Clone() *HttpRequest {
return &HttpRequest{
Method: hr.Method,
Headers: hr.Headers.Clone(),
Body: hr.Body,
Raw: hr.Raw,
}
}
type HttpResponse struct {
// status code of the response
StatusCode int `json:"status_code"`
// headers of the response
Headers mapsutil.OrderedMap[string, string] `json:"headers"`
// body of the response
Body string `json:"body"`
// raw response (includes everything including status code, headers, body, etc)
Raw string `json:"raw"`
}
// Id returns a unique id/hash for raw response
func (hr *HttpResponse) ID() string {
val := sha256.Sum256([]byte(hr.Raw))
return string(val[:])
}
// Clone clones the response
func (hr *HttpResponse) Clone() *HttpResponse {
return &HttpResponse{
StatusCode: hr.StatusCode,
Headers: hr.Headers.Clone(),
Body: hr.Body,
Raw: hr.Raw,
}
}
// ParseRawRequest parses a raw request from a string
// and returns the request and response object
// Note: it currently does not parse response and is meant to be added manually since its a optional field
func ParseRawRequest(raw string) (rr *RequestResponse, err error) {
protoReader := textproto.NewReader(bufio.NewReader(strings.NewReader(raw)))
methodLine, err := protoReader.ReadLine()
if err != nil {
return nil, fmt.Errorf("failed to read method line: %s", err)
}
rr = &RequestResponse{
Request: &HttpRequest{},
}
/// must contain at least 3 parts
parts := strings.Split(methodLine, " ")
if len(parts) < 3 {
return nil, fmt.Errorf("invalid method line: %s", methodLine)
}
method := parts[0]
rr.Request.Method = method
// parse relative url
urlx, err := urlutil.ParseRawRelativePath(parts[1], true)
if err != nil {
return nil, fmt.Errorf("failed to parse url: %s", err)
}
rr.URL = *urlx
// parse host line
hostLine, err := protoReader.ReadLine()
if err != nil {
return nil, fmt.Errorf("failed to read host line: %s", err)
}
sep := strings.Index(hostLine, ":")
if sep <= 0 || sep >= len(hostLine)-1 {
return nil, fmt.Errorf("invalid host line: %s", hostLine)
}
hostLine = hostLine[sep+2:]
rr.URL.Host = hostLine
// parse headers
rr.Request.Headers = mapsutil.NewOrderedMap[string, string]()
for {
line, err := protoReader.ReadLine()
if err != nil {
return nil, fmt.Errorf("failed to read header line: %s", err)
}
if line == "" {
// end of headers next is body
break
}
parts := strings.SplitN(line, ":", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("invalid header line: %s", line)
}
rr.Request.Headers.Set(parts[0], parts[1][1:])
}
// parse body
rr.Request.Body = ""
var buff bytes.Buffer
_, err = buff.ReadFrom(protoReader.R)
if err != nil && err != io.EOF {
return nil, fmt.Errorf("failed to read body: %s", err)
}
if buff.Len() > 0 {
// yaml may include trailing newlines
// remove them if present
bin := buff.Bytes()
if bin[len(bin)-1] == '\n' {
bin = bin[:len(bin)-1]
}
if bin[len(bin)-1] == '\r' || bin[len(bin)-1] == '\n' {
bin = bin[:len(bin)-1]
}
rr.Request.Body = conversion.String(bin)
}
// set raw request
rr.Request.Raw = raw
return rr, nil
}
// ParseRawRequestWithURL parses a raw request from a string with given url
func ParseRawRequestWithURL(raw, url string) (rr *RequestResponse, err error) {
rr, err = ParseRawRequest(raw)
if err != nil {
return nil, err
}
urlx, err := urlutil.ParseAbsoluteURL(url, false)
if err != nil {
return nil, err
}
rr.URL = *urlx
return rr, nil
}
================================================
FILE: pkg/input/types/http_test.go
================================================
package types
import (
"io"
"net/http"
"net/http/httputil"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
// Possibly add more tests here.
func TestParseHttpRequest(t *testing.T) {
tests := []struct {
name string
method string
url string
headerKey string
headerValue string
body string
contentLength string
}{
{"GET Request", "GET", "example.com/", "X-Test", "test", "", "0"},
{"POST Request with body", "POST", "example.com/resource", "Content-Type", "application/json", `{"key":"value"}`, "15"},
{"PUT Request with body", "PUT", "example.com/update", "Content-Type", "text/plain", "update data", "11"},
{"DELETE Request", "DELETE", "example.com/delete", "X-User", "user1", "", "0"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var bodyReader io.Reader
if tc.body != "" {
bodyReader = strings.NewReader(tc.body)
}
req, err := http.NewRequest(tc.method, "http://"+tc.url, bodyReader)
if err != nil {
t.Fatal(err)
}
req.Header.Add(tc.headerKey, tc.headerValue)
if tc.contentLength != "" {
req.Header.Add("Content-Length", tc.contentLength)
}
binx, err := httputil.DumpRequestOut(req, true)
if err != nil {
t.Fatal(err)
}
rr, err := ParseRawRequest(string(binx))
if err != nil {
t.Fatal(err)
}
if rr.Request.Method != tc.method {
t.Fatalf("invalid method: got %v want %v", rr.Request.Method, tc.method)
}
require.Equal(t, tc.url, rr.URL.String())
val, _ := rr.Request.Headers.Get(tc.headerKey)
require.Equal(t, tc.headerValue, val)
if tc.body != "" {
require.Equal(t, tc.body, rr.Request.Body)
contentLengthVal, _ := rr.Request.Headers.Get("Content-Length")
require.Equal(t, tc.contentLength, contentLengthVal)
}
t.Log(*rr.Request)
})
}
}
func TestUnmarshalJSON(t *testing.T) {
tests := []struct {
name string
rawJSONStr string
expectedURLStr string
}{
{"basic url", `{"url": "example.com"}`, "example.com"},
{"basic url with scheme", `{"url": "http://example.com"}`, "http://example.com"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var rr RequestResponse
err := rr.UnmarshalJSON([]byte(tc.rawJSONStr))
if err != nil {
t.Fatal(err)
}
if tc.expectedURLStr != "" {
require.Equal(t, rr.URL.String(), tc.expectedURLStr)
}
})
}
}
================================================
FILE: pkg/input/types/probe.go
================================================
package types
// InputLivenessProbe is an interface for probing the liveness of an input
type InputLivenessProbe interface {
// ProbeURL probes the scheme for a URL. first HTTPS is tried
ProbeURL(input string) (string, error)
// Close closes the liveness probe
Close() error
}
================================================
FILE: pkg/installer/doc.go
================================================
package installer
// package install provides helper functions for all download and installation of following tasks:
// 1. downloading and install nuclei templates
// 2. download and update nuclei binary (i.e self update)
// 3. version check for nuclei binary & templates
================================================
FILE: pkg/installer/template.go
================================================
package installer
import (
"bytes"
"context"
"crypto/md5"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/charmbracelet/glamour"
"github.com/olekukonko/tablewriter"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/external/customtemplates"
"github.com/projectdiscovery/utils/errkit"
fileutil "github.com/projectdiscovery/utils/file"
mapsutil "github.com/projectdiscovery/utils/maps"
stringsutil "github.com/projectdiscovery/utils/strings"
updateutils "github.com/projectdiscovery/utils/update"
)
const (
checkSumFilePerm = 0644
)
var (
HideProgressBar = true
HideUpdateChangesTable = false
HideReleaseNotes = true
)
// TemplateUpdateResults contains the results of template update
type templateUpdateResults struct {
additions []string
deletions []string
modifications []string
totalCount int
}
// String returns markdown table of template update results
func (t *templateUpdateResults) String() string {
var buff bytes.Buffer
data := [][]string{
{
strconv.Itoa(t.totalCount),
strconv.Itoa(len(t.additions)),
strconv.Itoa(len(t.modifications)),
strconv.Itoa(len(t.deletions)),
},
}
table := tablewriter.NewWriter(&buff)
table.Header([]string{"Total", "Added", "Modified", "Removed"})
for _, v := range data {
_ = table.Append(v)
}
_ = table.Render()
defer func() {
_ = table.Close()
}()
return buff.String()
}
// TemplateManager is a manager for templates.
// It downloads / updates / installs templates.
type TemplateManager struct {
CustomTemplates *customtemplates.CustomTemplatesManager // optional if given tries to download custom templates
DisablePublicTemplates bool // if true,
// public templates are not downloaded from the GitHub nuclei-templates repository
}
// FreshInstallIfNotExists installs templates if they are not already installed
// if templates directory already exists, it does nothing
func (t *TemplateManager) FreshInstallIfNotExists() error {
if fileutil.FolderExists(config.DefaultConfig.TemplatesDirectory) {
return nil
}
gologger.Info().Msgf("nuclei-templates are not installed, installing...")
if err := t.installTemplatesAt(config.DefaultConfig.TemplatesDirectory); err != nil {
return errkit.Wrapf(err, "failed to install templates at %s", config.DefaultConfig.TemplatesDirectory)
}
if t.CustomTemplates != nil {
t.CustomTemplates.Download(context.TODO())
}
return nil
}
// UpdateIfOutdated updates templates if they are outdated
func (t *TemplateManager) UpdateIfOutdated() error {
// if the templates folder does not exist, it's a fresh installation and do not update
if !fileutil.FolderExists(config.DefaultConfig.TemplatesDirectory) {
return t.FreshInstallIfNotExists()
}
needsUpdate := config.DefaultConfig.NeedsTemplateUpdate()
// NOTE(dwisiswant0): if PDTM API data is not available
// (LatestNucleiTemplatesVersion is empty) but we have a current template
// version, so we MUST verify against GitHub directly.
if !needsUpdate && config.DefaultConfig.LatestNucleiTemplatesVersion == "" && config.DefaultConfig.TemplateVersion != "" {
ghrd, err := updateutils.NewghReleaseDownloader(config.OfficialNucleiTemplatesRepoName)
if err == nil {
latestVersion := ghrd.Latest.GetTagName()
if config.IsOutdatedVersion(config.DefaultConfig.TemplateVersion, latestVersion) {
needsUpdate = true
gologger.Debug().Msgf("PDTM API unavailable, verified update needed via GitHub API: %s -> %s", config.DefaultConfig.TemplateVersion, latestVersion)
}
}
}
if needsUpdate {
return t.updateTemplatesAt(config.DefaultConfig.TemplatesDirectory)
}
return nil
}
// installTemplatesAt installs templates at given directory
func (t *TemplateManager) installTemplatesAt(dir string) error {
if !fileutil.FolderExists(dir) {
if err := fileutil.CreateFolder(dir); err != nil {
return errkit.Wrapf(err, "failed to create directory at %s", dir)
}
}
if t.DisablePublicTemplates {
gologger.Info().Msgf("Skipping installation of public nuclei-templates")
return nil
}
ghrd, err := updateutils.NewghReleaseDownloader(config.OfficialNucleiTemplatesRepoName)
if err != nil {
return errkit.Wrapf(err, "failed to install templates at %s", dir)
}
// write templates to disk
_, err = t.writeTemplatesToDisk(ghrd, dir)
if err != nil {
return errkit.Wrapf(err, "failed to write templates to disk at %s", dir)
}
gologger.Info().Msgf("Successfully installed nuclei-templates at %s", dir)
return nil
}
// updateTemplatesAt updates templates at given directory
func (t *TemplateManager) updateTemplatesAt(dir string) error {
if t.DisablePublicTemplates {
gologger.Info().Msgf("Skipping update of public nuclei-templates")
return nil
}
// firstly, read checksums from .checksum file these are used to generate stats
oldchecksums, err := t.getChecksumFromDir(dir)
if err != nil {
// if something went wrong, overwrite all files
oldchecksums = make(map[string]string)
}
ghrd, err := updateutils.NewghReleaseDownloader(config.OfficialNucleiTemplatesRepoName)
if err != nil {
return errkit.Wrapf(err, "failed to install templates at %s", dir)
}
latestVersion := ghrd.Latest.GetTagName()
currentVersion := config.DefaultConfig.TemplateVersion
if config.IsOutdatedVersion(currentVersion, latestVersion) {
gologger.Info().Msgf("Your current nuclei-templates %s are outdated. Latest is %s\n", currentVersion, latestVersion)
} else {
gologger.Debug().Msgf("Updating nuclei-templates from %s to %s (forced update)\n", currentVersion, latestVersion)
}
// write templates to disk
writtenPaths, err := t.writeTemplatesToDisk(ghrd, dir)
if err != nil {
return err
}
// cleanup orphaned templates that exist locally but weren't in the new release
if err := t.cleanupOrphanedTemplates(dir, writtenPaths); err != nil {
// log warning but don't fail the update
gologger.Warning().Msgf("failed to cleanup orphaned templates: %s", err)
} else {
// Regenerate metadata (index and checksum) after successful cleanup to ensure
// metadata accurately reflects the cleaned template tree. This prevents stale
// index entries and checksum entries for deleted templates.
if err := t.regenerateTemplateMetadata(dir); err != nil {
// Log warning but don't fail the update - metadata will be out of sync
// but templates are cleaned up correctly
gologger.Warning().Msgf("failed to regenerate template metadata after cleanup: %s", err)
}
}
// get checksums from new templates
newchecksums, err := t.getChecksumFromDir(dir)
if err != nil {
// unlikely this case will happen
return errkit.Wrapf(err, "failed to get checksums from %s after update", dir)
}
// summarize all changes
results := t.summarizeChanges(oldchecksums, newchecksums)
// remove deleted templates
for _, deletion := range results.deletions {
if err := os.Remove(deletion); err != nil && !os.IsNotExist(err) {
gologger.Warning().Msgf("failed to remove deleted template %s: %s", deletion, err)
}
}
// print summary
if results.totalCount > 0 {
gologger.Info().Msgf("Successfully updated nuclei-templates (%v) to %s. GoodLuck!", ghrd.Latest.GetTagName(), dir)
if !HideUpdateChangesTable {
// print summary table
gologger.Print().Msgf("\nNuclei Templates %s Changelog\n", ghrd.Latest.GetTagName())
gologger.Print().Msg(results.String())
}
} else {
gologger.Info().Msgf("Successfully updated nuclei-templates (%v) to %s. GoodLuck!", ghrd.Latest.GetTagName(), dir)
}
return nil
}
// summarizeChanges summarizes changes between old and new checksums
func (t *TemplateManager) summarizeChanges(old, new map[string]string) *templateUpdateResults {
results := &templateUpdateResults{}
for k, v := range new {
if oldv, ok := old[k]; ok {
if oldv != v {
results.modifications = append(results.modifications, k)
}
} else {
results.additions = append(results.additions, k)
}
}
for k := range old {
if _, ok := new[k]; !ok {
results.deletions = append(results.deletions, k)
}
}
results.totalCount = len(results.additions) + len(results.deletions) + len(results.modifications)
return results
}
// getAbsoluteFilePath returns an absolute path where a file should be written based on given uri(i.e., files in zip)
// if a returned path is empty, it means that file should not be written and skipped
func (t *TemplateManager) getAbsoluteFilePath(templateDir, uri string, f fs.FileInfo) string {
// overwrite .nuclei-ignore every time nuclei-templates are downloaded
if f.Name() == config.NucleiIgnoreFileName {
return config.DefaultConfig.GetIgnoreFilePath()
}
// skip all meta files
if !strings.EqualFold(f.Name(), config.NewTemplateAdditionsFileName) {
if strings.TrimSpace(f.Name()) == "" || strings.HasPrefix(f.Name(), ".") || strings.EqualFold(f.Name(), "README.md") {
return ""
}
}
// get root or leftmost directory name from path
// this is in format `projectdiscovery-nuclei-templates-commithash`
index := strings.Index(uri, "/")
if index == -1 {
// zip files does not have directory at all , in this case log error but continue
gologger.Warning().Msgf("failed to get directory name from uri: %s", uri)
return filepath.Join(templateDir, uri)
}
// separator is also included in rootDir
rootDirectory := uri[:index+1]
relPath := strings.TrimPrefix(uri, rootDirectory)
// if it is a github meta directory skip it
if stringsutil.HasPrefixAny(relPath, ".github", ".git") {
return ""
}
newPath := filepath.Clean(filepath.Join(templateDir, relPath))
if !strings.HasPrefix(newPath, templateDir) {
// we don't allow LFI
return ""
}
if newPath == templateDir || newPath == templateDir+string(os.PathSeparator) {
// skip writing the folder itself since it already exists
return ""
}
if relPath != "" && f.IsDir() {
// if uri is a directory, create it
if err := fileutil.CreateFolder(newPath); err != nil {
gologger.Warning().Msgf("uri %v: got %s while installing templates", uri, err)
}
return ""
}
return newPath
}
// writeTemplatesToDisk writes all templates to disk and returns a map of written file paths
// The returned map contains absolute paths of all template files that were successfully written
func (t *TemplateManager) writeTemplatesToDisk(ghrd *updateutils.GHReleaseDownloader, dir string) (*mapsutil.SyncLockMap[string, struct{}], error) {
localTemplatesIndex, err := config.GetNucleiTemplatesIndex()
if err != nil {
gologger.Warning().Msgf("failed to get local nuclei-templates index: %s", err)
if localTemplatesIndex == nil {
localTemplatesIndex = map[string]string{} // no-op
}
}
// Track all paths that are successfully written during this update
writtenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
callbackFunc := func(uri string, f fs.FileInfo, r io.Reader) error {
writePath := t.getAbsoluteFilePath(dir, uri, f)
if writePath == "" {
// skip writing file
return nil
}
bin, err := io.ReadAll(r)
if err != nil {
// if error occurs, iteration also stops
return errkit.Wrapf(err, "failed to read file %s", uri)
}
// TODO: It might be better to just download index file from nuclei templates repo
// instead of creating it from scratch
id, _ := config.GetTemplateIDFromReader(bytes.NewReader(bin), uri)
if id != "" {
// based on template id, check if we are updating a path of official nuclei template
if oldPath, ok := localTemplatesIndex[id]; ok {
if oldPath != writePath {
// write new template at a new path and delete old template
if err := os.WriteFile(writePath, bin, f.Mode()); err != nil {
return errkit.Wrapf(err, "failed to write file %s", uri)
}
// Track the new path as written
_ = writtenPaths.Set(writePath, struct{}{})
// after successful write, remove old template
if err := os.Remove(oldPath); err != nil {
gologger.Warning().Msgf("failed to remove old template %s: %s", oldPath, err)
}
return nil
}
}
}
// no change in template Path of official templates
if err := os.WriteFile(writePath, bin, f.Mode()); err != nil {
return errkit.Wrapf(err, "failed to write file %s", uri)
}
// Track successfully written paths
_ = writtenPaths.Set(writePath, struct{}{})
return nil
}
err = ghrd.DownloadSourceWithCallback(!HideProgressBar, callbackFunc)
if err != nil {
return nil, errkit.Wrap(err, "failed to download templates")
}
if err := config.DefaultConfig.WriteTemplatesConfig(); err != nil {
return nil, errkit.Wrap(err, "failed to write templates config")
}
// update ignore hash after writing new templates
if err := config.DefaultConfig.UpdateNucleiIgnoreHash(); err != nil {
return nil, errkit.Wrap(err, "failed to update nuclei ignore hash")
}
// update templates version in config file
if err := config.DefaultConfig.SetTemplatesVersion(ghrd.Latest.GetTagName()); err != nil {
return nil, errkit.Wrap(err, "failed to update templates version")
}
PurgeEmptyDirectories(dir)
// generate index of all templates
_ = os.Remove(config.DefaultConfig.GetTemplateIndexFilePath())
index, err := config.GetNucleiTemplatesIndex()
if err != nil {
return nil, errkit.Wrap(err, "failed to get nuclei templates index")
}
if err = config.DefaultConfig.WriteTemplatesIndex(index); err != nil {
return nil, errkit.Wrap(err, "failed to write nuclei templates index")
}
if !HideReleaseNotes {
output := ghrd.Latest.GetBody()
// adjust colors for both dark / light terminal themes
r, err := glamour.NewTermRenderer(glamour.WithAutoStyle())
if err != nil {
gologger.Error().Msgf("markdown rendering not supported: %v", err)
}
if rendered, err := r.Render(output); err == nil {
output = rendered
} else {
gologger.Error().Msg(err.Error())
}
gologger.Print().Msgf("\n%v\n\n", output)
}
// after installation, create and write checksums to .checksum file
if err := t.writeChecksumFileInDir(dir); err != nil {
return nil, err
}
return writtenPaths, nil
}
// cleanupOrphanedTemplates removes template files that exist locally but were not part of the new release
// It scans the templates directory for template files and deletes those that are not in the writtenPaths set
// This function handles empty directories gracefully - if the directory is empty, no orphaned files will be found
func (t *TemplateManager) cleanupOrphanedTemplates(dir string, writtenPaths *mapsutil.SyncLockMap[string, struct{}]) error {
absDir, err := filepath.Abs(dir)
if err != nil {
return errkit.Wrapf(err, "failed to get absolute path of templates directory")
}
// Use Clean to normalize the path consistently (handles Windows paths better)
absDir = filepath.Clean(absDir)
// If directory doesn't exist, there's nothing to clean up
if !fileutil.FolderExists(absDir) {
return nil
}
// Normalize all written paths to absolute paths for comparison
normalizedWrittenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
for path := range writtenPaths.GetAll() {
absPath, err := filepath.Abs(path)
if err == nil {
// Use Clean to normalize the path consistently (handles Windows paths better)
absPath = filepath.Clean(absPath)
_ = normalizedWrittenPaths.Set(absPath, struct{}{})
}
}
// Get custom template directories to exclude
customDirs := config.DefaultConfig.GetAllCustomTemplateDirs()
customDirAbs := make([]string, 0, len(customDirs))
for _, customDir := range customDirs {
if absCustomDir, err := filepath.Abs(customDir); err == nil {
// Use Clean to normalize the path consistently (handles Windows paths better)
absCustomDir = filepath.Clean(absCustomDir)
customDirAbs = append(customDirAbs, absCustomDir)
}
}
var orphanedFiles []string
// Walk the templates directory to find all template files
err = filepath.WalkDir(absDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
// Log but continue walking
gologger.Debug().Msgf("error accessing path %s during orphan cleanup: %s", path, err)
return nil
}
// Skip directories
if d.IsDir() {
return nil
}
absPath, err := filepath.Abs(path)
if err != nil {
return nil
}
// Use Clean to normalize the path consistently (handles Windows paths better)
absPath = filepath.Clean(absPath)
// Skip custom template directories
for _, customDir := range customDirAbs {
if strings.HasPrefix(absPath, customDir) {
return nil
}
}
// Only process template files
if !config.IsTemplate(absPath) {
return nil
}
// Skip if this file was written in the new release
if normalizedWrittenPaths.Has(absPath) {
return nil
}
// This is an orphaned template file
orphanedFiles = append(orphanedFiles, absPath)
return nil
})
if err != nil {
return errkit.Wrapf(err, "failed to walk templates directory for orphan cleanup")
}
// Delete orphaned files
for _, orphanPath := range orphanedFiles {
if err := os.Remove(orphanPath); err != nil {
if !os.IsNotExist(err) {
gologger.Warning().Msgf("failed to remove orphaned template %s: %s", orphanPath, err)
}
} else {
gologger.Debug().Msgf("removed orphaned template: %s", orphanPath)
}
}
if len(orphanedFiles) > 0 {
gologger.Info().Msgf("cleaned up %d orphaned template file(s)", len(orphanedFiles))
}
return nil
}
// regenerateTemplateMetadata regenerates template index and checksum files after cleanup operations.
// This ensures the metadata accurately reflects the current state of template files on disk.
func (t *TemplateManager) regenerateTemplateMetadata(dir string) error {
// Purge empty directories that may have been left after cleanup
PurgeEmptyDirectories(dir)
// Ensure templates directory exists (it may have been purged if empty)
if !fileutil.FolderExists(dir) {
if err := os.MkdirAll(dir, 0755); err != nil {
return errkit.Wrapf(err, "failed to recreate templates directory %s after purge", dir)
}
}
// Remove old index file and regenerate it from current templates on disk
indexFilePath := config.DefaultConfig.GetTemplateIndexFilePath()
if err := os.Remove(indexFilePath); err != nil && !os.IsNotExist(err) {
return errkit.Wrapf(err, "failed to remove old index file %s", indexFilePath)
}
// Force regeneration by ensuring the file doesn't exist (handles Windows file handle issues)
// GetNucleiTemplatesIndex will scan the directory if the file doesn't exist
index, err := config.GetNucleiTemplatesIndex()
if err != nil {
return errkit.Wrap(err, "failed to regenerate nuclei templates index after cleanup")
}
// Filter out any entries that don't actually exist on disk (Windows file deletion timing issues)
filteredIndex := make(map[string]string)
for id, path := range index {
if fileutil.FileExists(path) {
filteredIndex[id] = path
}
}
if err = config.DefaultConfig.WriteTemplatesIndex(filteredIndex); err != nil {
return errkit.Wrap(err, "failed to write nuclei templates index after cleanup")
}
// Regenerate checksum file to reflect current templates on disk
if err := t.writeChecksumFileInDir(dir); err != nil {
return errkit.Wrap(err, "failed to regenerate checksum file after cleanup")
}
return nil
}
// getChecksumFromDir returns a map containing checksums (md5 hash) of all yaml files (with .yaml extension)
// if .checksum file does not exist, checksums are calculated and returned
func (t *TemplateManager) getChecksumFromDir(dir string) (map[string]string, error) {
checksumFilePath := config.DefaultConfig.GetChecksumFilePath()
if fileutil.FileExists(checksumFilePath) {
checksums, err := os.ReadFile(checksumFilePath)
if err == nil {
allChecksums := make(map[string]string)
for _, v := range strings.Split(string(checksums), ";") {
v = strings.TrimSpace(v)
tmparr := strings.Split(v, ",")
if len(tmparr) != 2 {
continue
}
allChecksums[tmparr[0]] = tmparr[1]
}
return allChecksums, nil
}
}
return t.calculateChecksumMap(dir)
}
// writeChecksumFileInDir creates checksums of all yaml files in given directory
// and writes them to a file named .checksum
func (t *TemplateManager) writeChecksumFileInDir(dir string) error {
checksumMap, err := t.calculateChecksumMap(dir)
if err != nil {
return err
}
var buff bytes.Buffer
for k, v := range checksumMap {
buff.WriteString(k)
buff.WriteString(",")
buff.WriteString(v)
buff.WriteString(";")
}
return os.WriteFile(config.DefaultConfig.GetChecksumFilePath(), buff.Bytes(), checkSumFilePerm)
}
// getChecksumMap returns a map containing checksums (md5 hash) of all yaml files (with .yaml extension)
func (t *TemplateManager) calculateChecksumMap(dir string) (map[string]string, error) {
// getchecksumMap walks given directory `dir` and returns a map containing
// checksums (md5 hash) of all yaml files (with .yaml extension) and the
// format is map[filePath]checksum
checksumMap := map[string]string{}
getChecksum := func(filepath string) (string, error) {
// return md5 hash of the file
bin, err := os.ReadFile(filepath)
if err != nil {
return "", err
}
return fmt.Sprintf("%x", md5.Sum(bin)), nil
}
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
// skip checksums of custom templates i.e github and s3
if stringsutil.HasPrefixAny(path, config.DefaultConfig.GetAllCustomTemplateDirs()...) {
return nil
}
// current implementations calculates checksums of all files (including .yaml,.txt,.md,.json etc)
if !d.IsDir() {
checksum, err := getChecksum(path)
if err != nil {
return err
}
checksumMap[path] = checksum
}
return nil
})
return checksumMap, errkit.Wrap(err, "failed to calculate checksums of templates")
}
================================================
FILE: pkg/installer/template_test.go
================================================
package installer
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
fileutil "github.com/projectdiscovery/utils/file"
mapsutil "github.com/projectdiscovery/utils/maps"
"github.com/stretchr/testify/require"
)
func TestTemplateInstallation(t *testing.T) {
// test that the templates are installed correctly
// along with necessary changes that are made
HideProgressBar = true
tm := &TemplateManager{}
dir, err := os.MkdirTemp("", "nuclei-templates-*")
require.Nil(t, err)
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
require.Nil(t, err)
defer func() {
_ = os.RemoveAll(dir)
_ = os.RemoveAll(cfgdir)
}()
// set the config directory to a temporary directory
config.DefaultConfig.SetConfigDir(cfgdir)
// set the templates directory to a temporary directory
templatesTempDir := filepath.Join(dir, "templates")
config.DefaultConfig.SetTemplatesDir(templatesTempDir)
err = tm.FreshInstallIfNotExists()
if err != nil {
if strings.Contains(err.Error(), "rate limit") {
t.Skip("Skipping test due to github rate limit")
}
require.Nil(t, err)
}
// we should switch to more fine granular tests for template
// integrity, but for now, we just check that the templates are installed
counter := 0
err = filepath.Walk(templatesTempDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
counter++
}
return nil
})
require.Nil(t, err)
// we should have at least 1000 templates
require.Greater(t, counter, 1000)
// every time we install templates, it should override the ignore file with latest one
require.FileExists(t, config.DefaultConfig.GetIgnoreFilePath())
t.Logf("Installed %d templates", counter)
}
func TestIsOutdatedVersion(t *testing.T) {
testCases := []struct {
current string
latest string
expected bool
desc string
}{
// Test the empty latest version case (main bug fix)
{"v10.2.7", "", false, "Empty latest version should not trigger update"},
// Test same versions
{"v10.2.7", "v10.2.7", false, "Same versions should not trigger update"},
// Test outdated version
{"v10.2.6", "v10.2.7", true, "Older version should trigger update"},
// Test newer current version (edge case)
{"v10.2.8", "v10.2.7", false, "Newer current version should not trigger update"},
// Test dev versions
{"v10.2.7-dev", "v10.2.7", false, "Dev version matching release should not trigger update"},
{"v10.2.6-dev", "v10.2.7", true, "Outdated dev version should trigger update"},
// Test invalid semver fallback
{"invalid-version", "v10.2.7", true, "Invalid current version should trigger update (fallback)"},
{"v10.2.7", "invalid-version", true, "Invalid latest version should trigger update (fallback)"},
{"same-invalid", "same-invalid", false, "Same invalid versions should not trigger update (fallback)"},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
result := config.IsOutdatedVersion(tc.current, tc.latest)
require.Equal(t, tc.expected, result,
"IsOutdatedVersion(%q, %q) = %t, expected %t",
tc.current, tc.latest, result, tc.expected)
})
}
}
func TestCleanupOrphanedTemplates(t *testing.T) {
HideProgressBar = true
tm := &TemplateManager{}
t.Run("removes orphaned templates", func(t *testing.T) {
// Create temporary directories
tmpDir, err := os.MkdirTemp("", "nuclei-cleanup-test-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(tmpDir)
}()
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(cfgdir)
}()
config.DefaultConfig.SetConfigDir(cfgdir)
config.DefaultConfig.SetTemplatesDir(tmpDir)
// Create subdirectories for templates
templatesDir1 := filepath.Join(tmpDir, "cves", "2023")
templatesDir2 := filepath.Join(tmpDir, "exposures", "configs")
require.NoError(t, os.MkdirAll(templatesDir1, 0755))
require.NoError(t, os.MkdirAll(templatesDir2, 0755))
// Create template files
template1 := filepath.Join(templatesDir1, "CVE-2023-1234.yaml")
template2 := filepath.Join(templatesDir1, "CVE-2023-5678.yaml")
template3 := filepath.Join(templatesDir2, "git-config-exposure.yaml")
orphanedTemplate1 := filepath.Join(templatesDir1, "old-template.yaml")
orphanedTemplate2 := filepath.Join(templatesDir2, "removed-template.yaml")
// Write valid template files
templateContent := `id: test-template
info:
name: Test Template
author: test
severity: info`
require.NoError(t, os.WriteFile(template1, []byte(templateContent), 0644))
require.NoError(t, os.WriteFile(template2, []byte(templateContent), 0644))
require.NoError(t, os.WriteFile(template3, []byte(templateContent), 0644))
require.NoError(t, os.WriteFile(orphanedTemplate1, []byte(templateContent), 0644))
require.NoError(t, os.WriteFile(orphanedTemplate2, []byte(templateContent), 0644))
// Simulate written paths from new release (only template1, template2, template3)
writtenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
absTemplate1, _ := filepath.Abs(template1)
absTemplate2, _ := filepath.Abs(template2)
absTemplate3, _ := filepath.Abs(template3)
// Normalize paths consistently (same as cleanupOrphanedTemplates does)
absTemplate1 = filepath.Clean(absTemplate1)
absTemplate2 = filepath.Clean(absTemplate2)
absTemplate3 = filepath.Clean(absTemplate3)
_ = writtenPaths.Set(absTemplate1, struct{}{})
_ = writtenPaths.Set(absTemplate2, struct{}{})
_ = writtenPaths.Set(absTemplate3, struct{}{})
// Run cleanup
err = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)
require.NoError(t, err)
// Verify orphaned templates were removed
require.NoFileExists(t, orphanedTemplate1, "orphaned template should be removed")
require.NoFileExists(t, orphanedTemplate2, "orphaned template should be removed")
// Verify non-orphaned templates still exist
require.FileExists(t, template1, "template from new release should exist")
require.FileExists(t, template2, "template from new release should exist")
require.FileExists(t, template3, "template from new release should exist")
})
t.Run("preserves custom templates", func(t *testing.T) {
// Create temporary directories
tmpDir, err := os.MkdirTemp("", "nuclei-cleanup-custom-test-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(tmpDir)
}()
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(cfgdir)
}()
config.DefaultConfig.SetConfigDir(cfgdir)
config.DefaultConfig.SetTemplatesDir(tmpDir)
// Create custom template directory
customGitHubDir := filepath.Join(tmpDir, "github", "owner", "repo")
require.NoError(t, os.MkdirAll(customGitHubDir, 0755))
// Create custom template file
customTemplate := filepath.Join(customGitHubDir, "custom-template.yaml")
templateContent := `id: custom-template
info:
name: Custom Template
author: test
severity: info`
require.NoError(t, os.WriteFile(customTemplate, []byte(templateContent), 0644))
// Empty written paths (simulating no custom templates in new release)
writtenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
// Run cleanup
err = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)
require.NoError(t, err)
// Verify custom template was NOT removed
require.FileExists(t, customTemplate, "custom template should be preserved")
})
t.Run("skips non-template files", func(t *testing.T) {
// Create temporary directories
tmpDir, err := os.MkdirTemp("", "nuclei-cleanup-nontemplate-test-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(tmpDir)
}()
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(cfgdir)
}()
config.DefaultConfig.SetConfigDir(cfgdir)
config.DefaultConfig.SetTemplatesDir(tmpDir)
// Create non-template files
readmeFile := filepath.Join(tmpDir, "README.md")
configFile := filepath.Join(tmpDir, "cves.json")
checksumFile := filepath.Join(tmpDir, ".checksum")
require.NoError(t, os.WriteFile(readmeFile, []byte("# Templates"), 0644))
require.NoError(t, os.WriteFile(configFile, []byte("{}"), 0644))
require.NoError(t, os.WriteFile(checksumFile, []byte(""), 0644))
// Empty written paths
writtenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
// Run cleanup
err = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)
require.NoError(t, err)
// Verify non-template files were NOT removed
require.FileExists(t, readmeFile, "README.md should be preserved")
require.FileExists(t, configFile, "config file should be preserved")
require.FileExists(t, checksumFile, "checksum file should be preserved")
})
t.Run("handles empty written paths", func(t *testing.T) {
// Create temporary directories
tmpDir, err := os.MkdirTemp("", "nuclei-cleanup-empty-test-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(tmpDir)
}()
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(cfgdir)
}()
config.DefaultConfig.SetConfigDir(cfgdir)
config.DefaultConfig.SetTemplatesDir(tmpDir)
// Create template files
template1 := filepath.Join(tmpDir, "template1.yaml")
templateContent := `id: test-template
info:
name: Test Template
author: test
severity: info`
require.NoError(t, os.WriteFile(template1, []byte(templateContent), 0644))
// Empty written paths
writtenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
// Run cleanup
err = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)
require.NoError(t, err)
// Verify template was removed (since it's not in written paths)
require.NoFileExists(t, template1, "template should be removed when not in written paths")
})
t.Run("handles relative and absolute paths correctly", func(t *testing.T) {
// Create temporary directories
tmpDir, err := os.MkdirTemp("", "nuclei-cleanup-path-test-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(tmpDir)
}()
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(cfgdir)
}()
config.DefaultConfig.SetConfigDir(cfgdir)
config.DefaultConfig.SetTemplatesDir(tmpDir)
// Create template file
template1 := filepath.Join(tmpDir, "template1.yaml")
templateContent := `id: test-template
info:
name: Test Template
author: test
severity: info`
require.NoError(t, os.WriteFile(template1, []byte(templateContent), 0644))
// Use relative path in written paths
writtenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
_ = writtenPaths.Set(template1, struct{}{}) // relative path
// Run cleanup - should normalize paths correctly
err = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)
require.NoError(t, err)
// Verify template was NOT removed (it was in written paths)
require.FileExists(t, template1, "template should be preserved when in written paths")
})
t.Run("handles empty templates directory", func(t *testing.T) {
// Create temporary directories
tmpDir, err := os.MkdirTemp("", "nuclei-cleanup-empty-dir-test-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(tmpDir)
}()
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(cfgdir)
}()
config.DefaultConfig.SetConfigDir(cfgdir)
config.DefaultConfig.SetTemplatesDir(tmpDir)
// Directory exists but is empty (user deleted all templates)
require.True(t, fileutil.FolderExists(tmpDir), "templates directory should exist")
// Written paths from new release
writtenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
// Run cleanup - should handle empty directory gracefully
err = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)
require.NoError(t, err, "cleanup should handle empty directory without error")
// Directory should still exist after cleanup
require.True(t, fileutil.FolderExists(tmpDir), "templates directory should still exist")
})
t.Run("handles non-existent directory gracefully", func(t *testing.T) {
// Use a non-existent directory path
nonExistentDir := "/tmp/nuclei-test-non-existent-dir-12345"
// Ensure it doesn't exist
_ = os.RemoveAll(nonExistentDir)
require.False(t, fileutil.FolderExists(nonExistentDir), "directory should not exist")
writtenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
// Run cleanup - should handle non-existent directory gracefully
err := tm.cleanupOrphanedTemplates(nonExistentDir, writtenPaths)
require.NoError(t, err, "cleanup should handle non-existent directory without error")
})
}
func TestRegenerateTemplateMetadata(t *testing.T) {
HideProgressBar = true
tm := &TemplateManager{}
t.Run("creates index and checksum files", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "nuclei-metadata-test-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(tmpDir)
}()
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(cfgdir)
}()
config.DefaultConfig.SetConfigDir(cfgdir)
config.DefaultConfig.SetTemplatesDir(tmpDir)
// Create template files with unique IDs
template1 := filepath.Join(tmpDir, "template1.yaml")
template2 := filepath.Join(tmpDir, "cves", "template2.yaml")
require.NoError(t, os.MkdirAll(filepath.Dir(template2), 0755))
template1Content := `id: template-one
info:
name: Template One
author: test
severity: info`
template2Content := `id: template-two
info:
name: Template Two
author: test
severity: high`
require.NoError(t, os.WriteFile(template1, []byte(template1Content), 0644))
require.NoError(t, os.WriteFile(template2, []byte(template2Content), 0644))
// Regenerate metadata
err = tm.regenerateTemplateMetadata(tmpDir)
require.NoError(t, err)
// Verify index file was created
indexPath := config.DefaultConfig.GetTemplateIndexFilePath()
require.FileExists(t, indexPath, "template index file should be created")
// Verify checksum file was created
checksumPath := config.DefaultConfig.GetChecksumFilePath()
require.FileExists(t, checksumPath, "checksum file should be created")
// Verify index contains both templates
index, err := config.GetNucleiTemplatesIndex()
require.NoError(t, err)
require.Contains(t, index, "template-one", "index should contain template-one")
require.Contains(t, index, "template-two", "index should contain template-two")
// Verify checksum file contains both templates
checksums, err := tm.getChecksumFromDir(tmpDir)
require.NoError(t, err)
require.Contains(t, checksums, template1, "checksum should contain template1")
require.Contains(t, checksums, template2, "checksum should contain template2")
})
t.Run("excludes deleted templates from index after cleanup", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "nuclei-metadata-cleanup-test-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(tmpDir)
}()
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(cfgdir)
}()
config.DefaultConfig.SetConfigDir(cfgdir)
config.DefaultConfig.SetTemplatesDir(tmpDir)
// Create template files
template1 := filepath.Join(tmpDir, "kept-template.yaml")
template2 := filepath.Join(tmpDir, "deleted-template.yaml")
orphanedTemplate := filepath.Join(tmpDir, "orphaned-template.yaml")
template1Content := `id: test-template-1
info:
name: Test Template 1
author: test
severity: info`
template2Content := `id: test-template-2
info:
name: Test Template 2
author: test
severity: info`
orphanedContent := `id: test-template-orphaned
info:
name: Test Template Orphaned
author: test
severity: info`
require.NoError(t, os.WriteFile(template1, []byte(template1Content), 0644))
require.NoError(t, os.WriteFile(template2, []byte(template2Content), 0644))
require.NoError(t, os.WriteFile(orphanedTemplate, []byte(orphanedContent), 0644))
// Create initial index with all templates (simulating state before cleanup)
initialIndex := map[string]string{
"test-template-1": template1,
"test-template-2": template2,
"test-template-orphaned": orphanedTemplate,
}
err = config.DefaultConfig.WriteTemplatesIndex(initialIndex)
require.NoError(t, err)
// Verify initial index contains all templates
index, err := config.GetNucleiTemplatesIndex()
require.NoError(t, err)
require.Contains(t, index, "test-template-orphaned", "initial index should contain orphaned template")
// Simulate cleanup: remove orphaned template
writtenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
absTemplate1, _ := filepath.Abs(template1)
// Normalize path consistently (same as cleanupOrphanedTemplates does)
absTemplate1 = filepath.Clean(absTemplate1)
_ = writtenPaths.Set(absTemplate1, struct{}{})
err = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)
require.NoError(t, err)
require.NoFileExists(t, orphanedTemplate, "orphaned template should be deleted")
require.NoFileExists(t, template2, "template2 should be deleted since it's not in writtenPaths")
// Regenerate metadata after cleanup
err = tm.regenerateTemplateMetadata(tmpDir)
require.NoError(t, err)
// Verify index no longer contains deleted template
index, err = config.GetNucleiTemplatesIndex()
require.NoError(t, err)
require.NotContains(t, index, "test-template-orphaned", "index should not contain deleted orphaned template")
require.Contains(t, index, "test-template-1", "index should still contain kept template")
require.NotContains(t, index, "test-template-2", "index should not contain template that was deleted but not cleaned")
})
t.Run("excludes deleted templates from checksum after cleanup", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "nuclei-checksum-cleanup-test-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(tmpDir)
}()
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(cfgdir)
}()
config.DefaultConfig.SetConfigDir(cfgdir)
config.DefaultConfig.SetTemplatesDir(tmpDir)
// Create template files
keptTemplate := filepath.Join(tmpDir, "kept.yaml")
orphanedTemplate := filepath.Join(tmpDir, "orphaned.yaml")
templateContent := `id: test-template
info:
name: Test Template
author: test
severity: info`
require.NoError(t, os.WriteFile(keptTemplate, []byte(templateContent), 0644))
require.NoError(t, os.WriteFile(orphanedTemplate, []byte(templateContent), 0644))
// Create initial checksum with both templates
err = tm.writeChecksumFileInDir(tmpDir)
require.NoError(t, err)
// Verify initial checksum contains both templates
initialChecksums, err := tm.getChecksumFromDir(tmpDir)
require.NoError(t, err)
require.Contains(t, initialChecksums, orphanedTemplate, "initial checksum should contain orphaned template")
// Simulate cleanup: remove orphaned template
writtenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
absKept, _ := filepath.Abs(keptTemplate)
// Normalize path consistently (same as cleanupOrphanedTemplates does)
absKept = filepath.Clean(absKept)
_ = writtenPaths.Set(absKept, struct{}{})
err = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)
require.NoError(t, err)
require.NoFileExists(t, orphanedTemplate, "orphaned template should be deleted")
// Regenerate metadata after cleanup
err = tm.regenerateTemplateMetadata(tmpDir)
require.NoError(t, err)
// Verify checksum no longer contains deleted template
checksums, err := tm.getChecksumFromDir(tmpDir)
require.NoError(t, err)
require.NotContains(t, checksums, orphanedTemplate, "checksum should not contain deleted orphaned template")
require.Contains(t, checksums, keptTemplate, "checksum should still contain kept template")
})
t.Run("cleanup and metadata regeneration integration", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "nuclei-integration-test-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(tmpDir)
}()
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(cfgdir)
}()
config.DefaultConfig.SetConfigDir(cfgdir)
config.DefaultConfig.SetTemplatesDir(tmpDir)
// Create multiple templates
template1 := filepath.Join(tmpDir, "cves", "2023", "cve1.yaml")
template2 := filepath.Join(tmpDir, "cves", "2023", "cve2.yaml")
orphaned1 := filepath.Join(tmpDir, "cves", "2022", "old-cve.yaml")
orphaned2 := filepath.Join(tmpDir, "exposures", "old-exposure.yaml")
require.NoError(t, os.MkdirAll(filepath.Dir(template1), 0755))
require.NoError(t, os.MkdirAll(filepath.Dir(orphaned1), 0755))
require.NoError(t, os.MkdirAll(filepath.Dir(orphaned2), 0755))
template1Content := `id: cve1
info:
name: CVE1
author: test
severity: info`
template2Content := `id: cve2
info:
name: CVE2
author: test
severity: info`
orphaned1Content := `id: old-cve
info:
name: Old CVE
author: test
severity: info`
orphaned2Content := `id: old-exposure
info:
name: Old Exposure
author: test
severity: info`
require.NoError(t, os.WriteFile(template1, []byte(template1Content), 0644))
require.NoError(t, os.WriteFile(template2, []byte(template2Content), 0644))
require.NoError(t, os.WriteFile(orphaned1, []byte(orphaned1Content), 0644))
require.NoError(t, os.WriteFile(orphaned2, []byte(orphaned2Content), 0644))
// Simulate written paths from new release
writtenPaths := mapsutil.NewSyncLockMap[string, struct{}]()
absTemplate1, _ := filepath.Abs(template1)
absTemplate2, _ := filepath.Abs(template2)
// Normalize paths consistently (same as cleanupOrphanedTemplates does)
absTemplate1 = filepath.Clean(absTemplate1)
absTemplate2 = filepath.Clean(absTemplate2)
_ = writtenPaths.Set(absTemplate1, struct{}{})
_ = writtenPaths.Set(absTemplate2, struct{}{})
// Perform cleanup
err = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)
require.NoError(t, err)
require.NoFileExists(t, orphaned1, "orphaned template 1 should be deleted")
require.NoFileExists(t, orphaned2, "orphaned template 2 should be deleted")
// Regenerate metadata (simulating what updateTemplatesAt does)
err = tm.regenerateTemplateMetadata(tmpDir)
require.NoError(t, err)
// Verify index only contains kept templates
index, err := config.GetNucleiTemplatesIndex()
require.NoError(t, err)
require.Contains(t, index, "cve1", "index should contain kept template cve1")
require.Contains(t, index, "cve2", "index should contain kept template cve2")
require.NotContains(t, index, "old-cve", "index should not contain deleted template")
require.NotContains(t, index, "old-exposure", "index should not contain deleted template")
// Verify checksum only contains kept templates
checksums, err := tm.getChecksumFromDir(tmpDir)
require.NoError(t, err)
require.Contains(t, checksums, template1, "checksum should contain kept template1")
require.Contains(t, checksums, template2, "checksum should contain kept template2")
require.NotContains(t, checksums, orphaned1, "checksum should not contain deleted template")
require.NotContains(t, checksums, orphaned2, "checksum should not contain deleted template")
// Verify empty directories are purged
require.False(t, fileutil.FolderExists(filepath.Dir(orphaned1)), "empty directory should be purged")
require.False(t, fileutil.FolderExists(filepath.Dir(orphaned2)), "empty directory should be purged")
})
t.Run("handles empty templates directory", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "nuclei-metadata-empty-test-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(tmpDir)
}()
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(cfgdir)
}()
config.DefaultConfig.SetConfigDir(cfgdir)
config.DefaultConfig.SetTemplatesDir(tmpDir)
// Ensure templates directory exists (even if empty)
require.NoError(t, os.MkdirAll(tmpDir, 0755))
// Regenerate metadata on empty directory
err = tm.regenerateTemplateMetadata(tmpDir)
require.NoError(t, err, "should handle empty directory without error")
// Index should exist but be empty or minimal
indexPath := config.DefaultConfig.GetTemplateIndexFilePath()
if fileutil.FileExists(indexPath) {
index, err := config.GetNucleiTemplatesIndex()
require.NoError(t, err)
require.Empty(t, index, "index should be empty for empty templates directory")
}
})
t.Run("purges empty directories", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "nuclei-metadata-purge-test-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(tmpDir)
}()
cfgdir, err := os.MkdirTemp("", "nuclei-config-*")
require.NoError(t, err)
defer func() {
_ = os.RemoveAll(cfgdir)
}()
config.DefaultConfig.SetConfigDir(cfgdir)
config.DefaultConfig.SetTemplatesDir(tmpDir)
// Create empty nested directories
emptyDir1 := filepath.Join(tmpDir, "empty1", "nested", "deep")
emptyDir2 := filepath.Join(tmpDir, "empty2")
require.NoError(t, os.MkdirAll(emptyDir1, 0755))
require.NoError(t, os.MkdirAll(emptyDir2, 0755))
// Create one template in a different directory
templateFile := filepath.Join(tmpDir, "kept", "template.yaml")
require.NoError(t, os.MkdirAll(filepath.Dir(templateFile), 0755))
require.NoError(t, os.WriteFile(templateFile, []byte(`id: kept-template
info:
name: Kept
author: test
severity: info`), 0644))
// Regenerate metadata (should purge empty directories)
err = tm.regenerateTemplateMetadata(tmpDir)
require.NoError(t, err)
// Verify empty directories were purged
require.False(t, fileutil.FolderExists(emptyDir1), "empty nested directory should be purged")
require.False(t, fileutil.FolderExists(emptyDir2), "empty directory should be purged")
require.True(t, fileutil.FolderExists(filepath.Dir(templateFile)), "directory with template should not be purged")
})
}
================================================
FILE: pkg/installer/util.go
================================================
package installer
import (
"bufio"
"bytes"
"fmt"
"io"
"io/fs"
"net/http"
"os"
"path/filepath"
"sort"
"github.com/Masterminds/semver/v3"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/utils/errkit"
)
// GetNewTemplatesInVersions returns templates path of all newly added templates
// in these versions
func GetNewTemplatesInVersions(versions ...string) []string {
allTemplates := []string{}
for _, v := range versions {
if v == config.DefaultConfig.TemplateVersion {
allTemplates = append(allTemplates, config.DefaultConfig.GetNewAdditions()...)
}
_, err := semver.NewVersion(v)
if err != nil {
gologger.Error().Msgf("%v is not a valid semver version. skipping", v)
continue
}
if config.IsOutdatedVersion(v, "v8.8.4") {
// .new-additions was added in v8.8.4 any version before that is not supported
gologger.Error().Msgf(".new-additions support was added in v8.8.4 older versions are not supported")
continue
}
arr, err := getNewAdditionsFileFromGitHub(v)
if err != nil {
gologger.Error().Msgf("failed to fetch new additions for %v got: %v", v, err)
continue
}
allTemplates = append(allTemplates, arr...)
}
return allTemplates
}
func getNewAdditionsFileFromGitHub(version string) ([]string, error) {
resp, err := retryableHttpClient.Get(fmt.Sprintf("https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/%s/.new-additions", version))
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, errkit.New("version not found")
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
templatesList := []string{}
scanner := bufio.NewScanner(bytes.NewReader(data))
for scanner.Scan() {
text := scanner.Text()
if text == "" {
continue
}
if config.IsTemplate(text) {
templatesList = append(templatesList, text)
}
}
return templatesList, nil
}
func PurgeEmptyDirectories(dir string) {
alldirs := []string{}
_ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if d.IsDir() {
alldirs = append(alldirs, path)
}
return nil
})
// sort in ascending order
sort.Strings(alldirs)
// reverse the order
sort.Sort(sort.Reverse(sort.StringSlice(alldirs)))
for _, d := range alldirs {
if isEmptyDir(d) {
_ = os.RemoveAll(d)
}
}
}
func isEmptyDir(dir string) bool {
hasFiles := false
_ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if !d.IsDir() {
hasFiles = true
return io.EOF
}
return nil
})
return !hasFiles
}
================================================
FILE: pkg/installer/versioncheck.go
================================================
package installer
import (
"io"
"net/url"
"os"
"sync"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
"github.com/projectdiscovery/retryablehttp-go"
updateutils "github.com/projectdiscovery/utils/update"
)
const (
pdtmNucleiVersionEndpoint = "https://api.pdtm.sh/api/v1/tools/nuclei"
pdtmNucleiIgnoreFileEndpoint = "https://api.pdtm.sh/api/v1/tools/nuclei/ignore"
)
// defaultHttpClient is http client that is only meant to be used for version check
// if proxy env variables are set those are reflected in this client
var retryableHttpClient = retryablehttp.NewClient(retryablehttp.Options{HttpClient: updateutils.DefaultHttpClient, RetryMax: 2})
// PdtmAPIResponse is the response from pdtm API for nuclei endpoint
type PdtmAPIResponse struct {
IgnoreHash string `json:"ignore-hash"`
Tools []struct {
Name string `json:"name"`
Version string `json:"version"`
} `json:"tools"`
}
// NucleiVersionCheck checks for the latest version of nuclei and nuclei templates
// and returns an error if it fails to check on success it returns nil and changes are
// made to the default config in config.DefaultConfig
func NucleiVersionCheck() error {
return doVersionCheck(false)
}
// this will be updated by features of 1.21 release (which directly provides sync.Once(func()))
type sdkUpdateCheck struct {
sync.Once
}
var sdkUpdateCheckInstance = &sdkUpdateCheck{}
// NucleiSDKVersionCheck checks for latest version of nuclei which running in sdk mode
// this only happens once per process regardless of how many times this function is called
func NucleiSDKVersionCheck() {
sdkUpdateCheckInstance.Do(func() {
_ = doVersionCheck(true)
})
}
// getpdtmParams returns encoded query parameters sent to update check endpoint
func getpdtmParams(isSDK bool) string {
values, err := url.ParseQuery(updateutils.GetpdtmParams(config.Version))
if err != nil {
gologger.Verbose().Msgf("error parsing update check params: %v", err)
return updateutils.GetpdtmParams(config.Version)
}
if isSDK {
values.Add("sdk", "true")
}
return values.Encode()
}
// UpdateIgnoreFile updates default ignore file by downloading latest ignore file
func UpdateIgnoreFile() error {
resp, err := retryableHttpClient.Get(pdtmNucleiIgnoreFileEndpoint + "?" + getpdtmParams(false))
if err != nil {
return err
}
bin, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if err := os.WriteFile(config.DefaultConfig.GetIgnoreFilePath(), bin, 0644); err != nil {
return err
}
return config.DefaultConfig.UpdateNucleiIgnoreHash()
}
func doVersionCheck(isSDK bool) error {
// we use global retryablehttp client so its not immediately gc'd if any references are held
// and according our config we have idle connections which are shown as leaked by goleak in tests
// i.e we close all idle connections after our use and it doesn't affect any other part of the code
defer retryableHttpClient.HTTPClient.CloseIdleConnections()
resp, err := retryableHttpClient.Get(pdtmNucleiVersionEndpoint + "?" + getpdtmParams(isSDK))
if err != nil {
return err
}
defer func() {
_ = resp.Body.Close()
}()
bin, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
var pdtmResp PdtmAPIResponse
if err := json.Unmarshal(bin, &pdtmResp); err != nil {
return err
}
var nucleiversion, templateversion string
for _, tool := range pdtmResp.Tools {
switch tool.Name {
case "nuclei":
if tool.Version != "" {
nucleiversion = "v" + tool.Version
}
case "nuclei-templates":
if tool.Version != "" {
templateversion = "v" + tool.Version
}
}
}
return config.DefaultConfig.WriteVersionCheckData(pdtmResp.IgnoreHash, nucleiversion, templateversion)
}
================================================
FILE: pkg/installer/versioncheck_test.go
================================================
package installer
import (
"testing"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/utils/generic"
"github.com/stretchr/testify/require"
)
func TestVersionCheck(t *testing.T) {
err := NucleiVersionCheck()
require.Nil(t, err)
cfg := config.DefaultConfig
if generic.EqualsAny("", cfg.LatestNucleiIgnoreHash, cfg.LatestNucleiVersion, cfg.LatestNucleiTemplatesVersion) {
// all above values cannot be empty
t.Errorf("something went wrong got empty response nuclei-version=%v templates-version=%v ignore-hash=%v", cfg.LatestNucleiVersion, cfg.LatestNucleiTemplatesVersion, cfg.LatestNucleiIgnoreHash)
}
}
================================================
FILE: pkg/installer/zipslip_unix_test.go
================================================
package installer
import (
"io/fs"
"os"
"path/filepath"
"testing"
"time"
osutils "github.com/projectdiscovery/utils/os"
"github.com/stretchr/testify/require"
)
var _ fs.FileInfo = &tempFileInfo{}
type tempFileInfo struct {
name string
}
func (t *tempFileInfo) Name() string {
return t.name
}
func (t *tempFileInfo) ModTime() time.Time {
return time.Now()
}
func (t *tempFileInfo) Mode() fs.FileMode {
return fs.ModePerm
}
func (t tempFileInfo) IsDir() bool {
return false
}
func (t *tempFileInfo) Size() int64 {
return 100
}
func (t *tempFileInfo) Sys() any {
return nil
}
func TestZipSlip(t *testing.T) {
if osutils.IsWindows() {
t.Skip("Skipping Zip LFI Check on Windows")
}
configuredTemplateDirectory := filepath.Join(os.TempDir(), "templates")
defer func() {
_ = os.RemoveAll(configuredTemplateDirectory)
}()
t.Run("negative scenarios", func(t *testing.T) {
filePathsFromZip := []string{
"./../nuclei-templates/../cve/test.yaml",
"nuclei-templates/../cve/test.yaml",
"nuclei-templates/././../cve/test.yaml",
"nuclei-templates/.././../cve/test.yaml",
"nuclei-templates/.././../cve/../test.yaml",
}
tm := TemplateManager{}
for _, filePathFromZip := range filePathsFromZip {
var tmp fs.FileInfo = &tempFileInfo{name: filePathFromZip}
writePath := tm.getAbsoluteFilePath(configuredTemplateDirectory, filePathFromZip, tmp)
require.Equal(t, "", writePath, filePathFromZip)
}
})
}
================================================
FILE: pkg/js/CONTRIBUTE.md
================================================
# JS Contribution Guide
The JS layer provides a mechanism to add scriptability into the Nuclei Engine. The `pkg/js` directory contains the implementation of the JS runtime in Nuclei. This document provides a guide to adding new libraries, extending existing ones and other types of contributions.
## First step
The Very First before making any type of contribution to javascript runtime in nuclei is taking a look at [design.md](./DESIGN.md) to understand spread out design of nuclei javascript runtime.
## Documentation/Typo Contribution
Most of JavaScript API Reference documentation is auto-generated with help of code-generation and [jsdocgen](./devtools/jsdocgen/README.md) and hence any type of documentation contribution are always welcome and can be done by editing [JavaScript jsdoc](./generated/js/) files
## Improving Existing Libraries(aka node_modules)
Improving existing libraries includes adding new functions, types, fixing bugs etc. to any of the existing libraries in [libs](./libs/) directory. This is very easy to achieve and can be done by following steps below
1. Do suggested changes in targeted package in [libs](./libs/) directory
2. Refer [devtools](./devtools/README.md) to autogenerate bindings and documentation
3. Check for errors / improve documentation in generated documentation in [generated/js/*](./generated/js/*) directory
## Adding New Libraries(aka node_modules)
Libraries/node_modules represent adding new protocol or something similar and should not include helper functions or types/objects .Adding new libraries requires few more steps than improving existing libraries and can be done by following steps below
1. Refer any existing library in [libs](./libs/) directory to understand style and structure of node_modules
2. Create new package in [libs](./libs/) directory with suggested protocol / library
3. Refer [devtools](./devtools/README.md) to autogenerate bindings and documentation
4. Check for errors / improve documentation in generated documentation in [generated/js/*](./generated/js/*) directory
5. Import newly created library with '_' import in [compiler](./compiler/compiler.go)
## Adding Helper Objects/Types/Functions
Helper objects/types/functions can simply be understood as javascript utils to simplify writing javascript and reduce code duplication in javascript templates. Helper functions/objects are divided into two categories
### javascript based helpers
javascript based helpers are written in javascript and are available in javascript runtime by default without needing to import any module. These are located in [global/js](./global/js/) directory and are exported using [exports.js](./global/exports.js) file.
### go based helpers
go based helpers are written in go and can import any go library if required. Minimal/Simple helper functions can be directly added using `runtime.Set("function_name", function)` in [global/scripts.go](./global/scripts.go) file. For more complex helpers, a new package can be created in [libs](./libs/) directory and can be imported in [global/scripts.go](./global/scripts.go) file. Refer to existing implementations in [globals](./global/) directory for more details.
### Updating / Publishing Docs
JavaScript Protocol Documentation is auto-generated using [jsdoc] and is hosted at [js-proto-docs](https://projectdiscovery.github.io/js-proto-docs/). To update documentation, please follow steps mentioned at [projectdiscovery/js-proto-docs](https://github.com/projectdiscovery/js-proto-docs)
### Go Code Guidelines
1. Always use 'protocolstate.Dialer' (i.e fastdialer) to dial connections instead of net.Dial this has many benefits along with proxy support , **network policy** usage (i.e local network access can be disabled from cli)
2. When usage of 'protocolstate.Dialer' is not possible due to some reason ex: imported library does not accept dialer etc. then validate host using 'protocolstate.IsHostAllowed' before dialing connection.
```go
if !protocolstate.IsHostAllowed(host) {
// host is not valid according to network policy
return false, protocolstate.ErrHostDenied.Msgf(host)
}
```
3. Keep exported package clean. Do not keep unnecessary global exports which the consumer of the API doesn't need to know about. Keep only user-exposed API public.
4. Use timeouts and context cancellation when calling Network related stuff. Also make sure to close your connections or provide a mechanism to the user of the API to do so.
5. Always try to return single types from inside javascript with an error like `(IsRDP, error)` instead of returning multiple values `(name, version string, err error)`. The second one will get converted to an array is much harder for consumers to deal with. Instead, try to return `Structures` which will be accessible natively.
### JavaScript Code Guidelines
1. Catch exceptions using `try/catch` blocks and handle errors gracefully, showing useful information. By default, the implementation returns a Go error on an unhandled exception along with stack trace in debug mode.
2. Use `let`/`const` instead of `var` to declare variables.
3. Keep the global scope clean. The VMs are not shared so do not rely on VM state.
4. Use functions to divide the code and keep the implementation clean.
================================================
FILE: pkg/js/DESIGN.md
================================================
# javascript protocol design
javascript protocol is implemented using `goja`(pure go javascript VM) and overall logic/design of its usage is split into multiple packages/directories
## [api_reference](./api_reference/)
api_reference contains a static site generated using `jsdoc` . It contains documentation for all the exposed functions and types in javascript protocol.
## [compiler](./compiler/)
compiler contains abstracted logic for compiling and executing javascript code. It also handles loading javascript aka node modules , adding builtin / global types and functions etc.
## [devtools](./devtools/README.md)
devtools contains development related tools to automate boring tasks like generating bindings, adding jsdoc comments, generating api reference etc.
## [generated](./generated/README.md)
generated contains two types of generated code
### [- generated/go](./generated/go/)
generated/go contains actual bindings for native go packages using `goja` this involves exposing libraries,functions and types written in go to javascript.
### [- generated/js](./generated/js/)
generated/js contains a visual representation of all exposed functions and types in javascript minus the actual implementation . it is meant to be used as a reference for developers and generating api reference.
## [global](./global/)
global (or builtin) contains all builtin types and functions that are by default available in javascript runtime without needing to import any module using 'require' keyword. It's split into 2 sections
### [- global/js](./global/js/)
global/js contains javascript code and it acts more like a javascript library and contains functions / types written in javascript itself and exported using [exports.js](./global/exports.js)
### [- global/scripts.go](./global/scripts.go)
global/scripts.go contains declaration and implementation of functions written in go and are made available in javascript runtime. It also contains loading javascript based global functions this is done by executing javascript code in every vm instance.
## [gojs](./gojs/)
gojs contain minimalistic types and interfaces used to register packages written in go as node_modules in javascript runtime.
## [libs](./libs/)
libs contains all go native packages that contain **actual** implementation of all the functions and types that are exposed to javascript runtime.
================================================
FILE: pkg/js/THANKS.md
================================================
# THANKS
- https://github.com/dop251/goja - Pure Go JavaScript VM used by nuclei JS layer.
- https://github.com/gogap/gojs-tool - Inspiration for code generation used in JS Libraries addition.
- https://github.com/ropnop/kerbrute - Kerberos Module of JS layer
- https://github.com/praetorian-inc/fingerprintx - A lot of Network Protocol fingerprinting functionality is used from `fingerprintx` package.
- https://github.com/zmap/zgrab2 - Used for SMB and SSH protocol handshake Metadata gathering.
A lot of other Go based libraries are used in the javascript layer. Thanks goes to the creators and maintainers.
================================================
FILE: pkg/js/compiler/compiler.go
================================================
// Package compiler provides a compiler for the goja runtime.
package compiler
import (
"context"
"fmt"
"github.com/Mzack9999/goja"
"github.com/kitabisa/go-ci"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
contextutil "github.com/projectdiscovery/utils/context"
"github.com/projectdiscovery/utils/errkit"
stringsutil "github.com/projectdiscovery/utils/strings"
)
var (
// ErrJSExecDeadline is the error returned when allotted time for script execution exceeds
ErrJSExecDeadline = errkit.New("js engine execution deadline exceeded").SetKind(errkit.ErrKindDeadline).Build()
)
// Compiler provides a runtime to execute goja runtime
// based javascript scripts efficiently while also
// providing them access to custom modules defined in libs/.
type Compiler struct{}
// New creates a new compiler for the goja runtime.
func New() *Compiler {
return &Compiler{}
}
// ExecuteOptions provides options for executing a script.
type ExecuteOptions struct {
// ExecutionId is the id of the execution
ExecutionId string
// Callback can be used to register new runtime helper functions
// ex: export etc
Callback func(runtime *goja.Runtime) error
// Cleanup is extra cleanup function to be called after execution
Cleanup func(runtime *goja.Runtime)
// Source is original source of the script
Source *string
Context context.Context
TimeoutVariants *types.Timeouts
// Manually exported objects
exports map[string]interface{}
}
// ExecuteArgs is the arguments to pass to the script.
type ExecuteArgs struct {
Args map[string]interface{} //these are protocol variables
TemplateCtx map[string]interface{} // templateCtx contains template scoped variables
}
// Map returns a merged map of the TemplateCtx and Args fields.
func (e *ExecuteArgs) Map() map[string]interface{} {
return generators.MergeMaps(e.TemplateCtx, e.Args)
}
// NewExecuteArgs returns a new execute arguments.
func NewExecuteArgs() *ExecuteArgs {
return &ExecuteArgs{
Args: make(map[string]interface{}),
TemplateCtx: make(map[string]interface{}),
}
}
// ExecuteResult is the result of executing a script.
type ExecuteResult map[string]interface{}
// Map returns the map representation of the ExecuteResult
func (e ExecuteResult) Map() map[string]interface{} {
if e == nil {
return make(map[string]interface{})
}
return e
}
// NewExecuteResult returns a new execute result instance
func NewExecuteResult() ExecuteResult {
return make(map[string]interface{})
}
// GetSuccess returns whether the script was successful or not.
func (e ExecuteResult) GetSuccess() bool {
if e == nil {
return false
}
val, ok := e["success"].(bool)
if !ok {
return false
}
return val
}
// ExecuteWithOptions executes a script with the provided options.
func (c *Compiler) ExecuteWithOptions(program *goja.Program, args *ExecuteArgs, opts *ExecuteOptions) (ExecuteResult, error) {
if opts == nil {
opts = &ExecuteOptions{Context: context.Background()}
}
if args == nil {
args = NewExecuteArgs()
}
// handle nil maps
if args.TemplateCtx == nil {
args.TemplateCtx = make(map[string]interface{})
}
if args.Args == nil {
args.Args = make(map[string]interface{})
}
// merge all args into templatectx
args.TemplateCtx = generators.MergeMaps(args.TemplateCtx, args.Args)
// execute with context and timeout
ctx, cancel := context.WithTimeoutCause(opts.Context, opts.TimeoutVariants.JsCompilerExecutionTimeout, ErrJSExecDeadline)
defer cancel()
// execute the script
results, err := contextutil.ExecFuncWithTwoReturns(ctx, func() (val goja.Value, err error) {
// TODO(dwisiswant0): remove this once we get the RCA.
defer func() {
if ci.IsCI() {
return
}
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
return ExecuteProgram(program, args, opts)
})
if err != nil {
if val, ok := err.(*goja.Exception); ok {
if x := val.Unwrap(); x != nil {
err = x
}
}
e := NewExecuteResult()
e["error"] = err.Error()
return e, err
}
var res ExecuteResult
if opts.exports != nil {
res = ExecuteResult(opts.exports)
opts.exports = nil
} else {
res = NewExecuteResult()
}
res["response"] = results.Export()
res["success"] = results.ToBoolean()
return res, nil
}
// if the script uses export/ExportAS tokens then we can run it in IIFE mode
// but if not we can't run it
func CanRunAsIIFE(script string) bool {
return stringsutil.ContainsAny(script, exportAsToken, exportToken)
}
// SourceIIFEMode is a mode where the script is wrapped in a function and compiled.
// This is used when the script is not exported or exported as a function.
func SourceIIFEMode(script string, strict bool) (*goja.Program, error) {
val := fmt.Sprintf(`
(function() {
%s
})()
`, script)
return goja.Compile("", val, strict)
}
// SourceAutoMode is a mode where the script is wrapped in a function and compiled.
// This is used when the script is exported or exported as a function.
func SourceAutoMode(script string, strict bool) (*goja.Program, error) {
if !CanRunAsIIFE(script) {
// this will not be run in a pooled runtime
return goja.Compile("", script, strict)
}
val := fmt.Sprintf(`
(function() {
%s
})()
`, script)
return goja.Compile("", val, strict)
}
================================================
FILE: pkg/js/compiler/compiler_test.go
================================================
package compiler
import (
"context"
"strings"
"testing"
"time"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/gologger/levels"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
)
func TestNewCompilerConsoleDebug(t *testing.T) {
gotString := ""
gologger.DefaultLogger.SetMaxLevel(levels.LevelDebug)
gologger.DefaultLogger.SetWriter(&noopWriter{
Callback: func(data []byte, level levels.Level) {
gotString = string(data)
},
})
compiler := New()
p, err := SourceAutoMode("console.log('hello world');", false)
if err != nil {
t.Fatal(err)
}
_, err = compiler.ExecuteWithOptions(p, NewExecuteArgs(), &ExecuteOptions{Context: context.Background(),
TimeoutVariants: &types.Timeouts{JsCompilerExecutionTimeout: time.Duration(20) * time.Second}},
)
if err != nil {
t.Fatal(err)
}
if !strings.HasSuffix(gotString, "hello world") {
t.Fatalf("console.log not working, got=%v", gotString)
}
}
type noopWriter struct {
Callback func(data []byte, level levels.Level)
}
func (n *noopWriter) Write(data []byte, level levels.Level) {
if n.Callback != nil {
n.Callback(data, level)
}
}
================================================
FILE: pkg/js/compiler/init.go
================================================
package compiler
import (
"sync"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
)
// jsprotocolInit
var (
PoolingJsVmConcurrency = 100
NonPoolingVMConcurrency = 20
m sync.Mutex
)
// Init initializes the javascript protocol
func Init(opts *types.Options) error {
m.Lock()
defer m.Unlock()
if opts.JsConcurrency < 100 {
// 100 is reasonable default
opts.JsConcurrency = 100
}
PoolingJsVmConcurrency = opts.JsConcurrency
PoolingJsVmConcurrency -= NonPoolingVMConcurrency
return nil
}
================================================
FILE: pkg/js/compiler/non-pool.go
================================================
package compiler
import (
"sync"
"github.com/Mzack9999/goja"
syncutil "github.com/projectdiscovery/utils/sync"
)
var (
ephemeraljsc *syncutil.AdaptiveWaitGroup
lazyFixedSgInit = sync.OnceFunc(func() {
ephemeraljsc, _ = syncutil.New(syncutil.WithSize(NonPoolingVMConcurrency))
})
)
func executeWithoutPooling(p *goja.Program, args *ExecuteArgs, opts *ExecuteOptions) (result goja.Value, err error) {
lazyFixedSgInit()
ephemeraljsc.Add()
defer ephemeraljsc.Done()
runtime := createNewRuntime()
return executeWithRuntime(runtime, p, args, opts)
}
================================================
FILE: pkg/js/compiler/pool.go
================================================
package compiler
import (
"bytes"
"context"
"fmt"
"reflect"
"sync"
"github.com/Mzack9999/goja"
"github.com/Mzack9999/goja_nodejs/console"
"github.com/Mzack9999/goja_nodejs/require"
"github.com/kitabisa/go-ci"
"github.com/projectdiscovery/gologger"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libbytes"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libfs"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libikev2"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libkerberos"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libldap"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libmssql"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libmysql"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libnet"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/liboracle"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libpop3"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libpostgres"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/librdp"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libredis"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/librsync"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libsmb"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libsmtp"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libssh"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libstructs"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libtelnet"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libvnc"
"github.com/projectdiscovery/nuclei/v3/pkg/js/global"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/goconsole"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
stringsutil "github.com/projectdiscovery/utils/strings"
syncutil "github.com/projectdiscovery/utils/sync"
)
const (
exportToken = "Export"
exportAsToken = "ExportAs"
)
var (
r *require.Registry
lazyRegistryInit = sync.OnceFunc(func() {
r = new(require.Registry) // this can be shared by multiple runtimes
// autoregister console node module with default printer it uses gologger backend
require.RegisterNativeModule(console.ModuleName, console.RequireWithPrinter(goconsole.NewGoConsolePrinter()))
})
pooljsc *syncutil.AdaptiveWaitGroup
lazySgInit = sync.OnceFunc(func() {
pooljsc, _ = syncutil.New(syncutil.WithSize(PoolingJsVmConcurrency))
})
sgResizeCheck = func(ctx context.Context) {
// resize check point
if pooljsc.Size != PoolingJsVmConcurrency {
if err := pooljsc.Resize(ctx, PoolingJsVmConcurrency); err != nil {
gologger.Warning().Msgf("Could not resize workpool: %s\n", err)
}
}
}
)
var gojapool = &sync.Pool{
New: func() interface{} {
return createNewRuntime()
},
}
func executeWithRuntime(runtime *goja.Runtime, p *goja.Program, args *ExecuteArgs, opts *ExecuteOptions) (result goja.Value, err error) {
defer func() {
// reset before putting back to pool
_ = runtime.GlobalObject().Delete("template") // template ctx
// remove all args
for k := range args.Args {
_ = runtime.GlobalObject().Delete(k)
}
if opts != nil && opts.Cleanup != nil {
opts.Cleanup(runtime)
}
runtime.RemoveContextValue("executionId")
}()
// TODO(dwisiswant0): remove this once we get the RCA.
defer func() {
if ci.IsCI() {
return
}
if r := recover(); r != nil {
err = fmt.Errorf("panic: %s", r)
}
}()
// set template ctx
_ = runtime.Set("template", args.TemplateCtx)
// set args
for k, v := range args.Args {
_ = runtime.Set(k, v)
}
// register extra callbacks if any
if opts != nil && opts.Callback != nil {
if err := opts.Callback(runtime); err != nil {
return nil, err
}
}
// inject execution id and context
runtime.SetContextValue("executionId", opts.ExecutionId)
// execute the script
return runtime.RunProgram(p)
}
// ExecuteProgram executes a compiled program with the default options.
// it deligates if a particular program should run in a pooled or non-pooled runtime
func ExecuteProgram(p *goja.Program, args *ExecuteArgs, opts *ExecuteOptions) (result goja.Value, err error) {
if opts.Source == nil {
// not-recommended anymore
return executeWithoutPooling(p, args, opts)
}
if !stringsutil.ContainsAny(*opts.Source, exportAsToken, exportToken) {
// not-recommended anymore
return executeWithoutPooling(p, args, opts)
}
return executeWithPoolingProgram(p, args, opts)
}
// executes the actual js program
func executeWithPoolingProgram(p *goja.Program, args *ExecuteArgs, opts *ExecuteOptions) (result goja.Value, err error) {
// its unknown (most likely cannot be done) to limit max js runtimes at a moment without making it static
// unlike sync.Pool which reacts to GC and its purposes is to reuse objects rather than creating new ones
lazySgInit()
sgResizeCheck(opts.Context)
pooljsc.Add()
defer pooljsc.Done()
runtime := gojapool.Get().(*goja.Runtime)
defer gojapool.Put(runtime)
var buff bytes.Buffer
opts.exports = make(map[string]interface{})
defer func() {
// remove below functions from runtime
_ = runtime.GlobalObject().Delete(exportAsToken)
_ = runtime.GlobalObject().Delete(exportToken)
}()
// register export functions
_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{
Name: "Export", // we use string instead of const for documentation generation
Signatures: []string{"Export(value any)"},
Description: "Converts a given value to a string and is appended to output of script",
FuncDecl: func(call goja.FunctionCall, runtime *goja.Runtime) goja.Value {
if len(call.Arguments) == 0 {
return goja.Null()
}
for _, arg := range call.Arguments {
if out := stringify(arg, runtime); out != "" {
buff.WriteString(out)
}
}
return goja.Null()
},
})
// register exportAs function
_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{
Name: "ExportAs", // Export
Signatures: []string{"ExportAs(key string,value any)"},
Description: "Exports given value with specified key and makes it available in DSL and response",
FuncDecl: func(call goja.FunctionCall, runtime *goja.Runtime) goja.Value {
if len(call.Arguments) != 2 {
// this is how goja expects errors to be returned
// and internally it is done same way for all errors
panic(runtime.ToValue("ExportAs expects 2 arguments"))
}
key := call.Argument(0).String()
value := call.Argument(1)
opts.exports[key] = stringify(value, runtime)
return goja.Null()
},
})
val, err := executeWithRuntime(runtime, p, args, opts)
if err != nil {
return nil, err
}
if val.Export() != nil {
// append last value to output
buff.WriteString(stringify(val, runtime))
}
// and return it as result
return runtime.ToValue(buff.String()), nil
}
// Internal purposes i.e generating bindings
func InternalGetGeneratorRuntime() *goja.Runtime {
runtime := gojapool.Get().(*goja.Runtime)
return runtime
}
func getRegistry() *require.Registry {
lazyRegistryInit()
return r
}
func createNewRuntime() *goja.Runtime {
runtime := protocolstate.NewJSRuntime()
_ = getRegistry().Enable(runtime)
// by default import below modules every time
_ = runtime.Set("console", require.Require(runtime, console.ModuleName))
// Register embedded javascript helpers
if err := global.RegisterNativeScripts(runtime); err != nil {
gologger.Error().Msgf("Could not register scripts: %s\n", err)
}
return runtime
}
// stringify converts a given value to string
// if its a struct it will be marshalled to json
func stringify(gojaValue goja.Value, runtime *goja.Runtime) string {
value := gojaValue.Export()
if value == nil {
return ""
}
kind := reflect.TypeOf(value).Kind()
if kind == reflect.Struct || kind == reflect.Ptr && reflect.ValueOf(value).Elem().Kind() == reflect.Struct {
// in this case we must use JSON.stringify to convert to string
// because json.Marshal() utilizes json tags when marshalling
// but goja has custom implementation of json.Marshal() which does not
// since we have been using `to_json` in all our examples we must stick to it
// marshal structs or struct pointers to json automatically
jsonStringify, ok := goja.AssertFunction(runtime.Get("to_json"))
if ok {
result, err := jsonStringify(goja.Undefined(), gojaValue)
if err == nil {
return result.String()
}
}
// unlikely but if to_json threw some error use native json.Marshal
val := value
if kind == reflect.Ptr {
val = reflect.ValueOf(value).Elem().Interface()
}
bin, err := json.Marshal(val)
if err == nil {
return string(bin)
}
}
// for everything else stringify
return fmt.Sprintf("%+v", value)
}
================================================
FILE: pkg/js/devtools/README.md
================================================
## devtools
devtools contains tools and scripts to automate boring tasks related to javascript layer/ packages.
### bindgen
[bindgen](./bindgen/README.md) is a tool that automatically generated bindings for native go packages with 'goja'
### scrapefuncs
[scrapefuncs](./scrapefuncs/README.md) is a tool to scrapes all helper functions exposed in javascript with help of go/ast and generates a js file with jsdoc comments using LLM (OpenAI)
### Generating API Reference (aka static site using javascript files using jsdoc)
```console
jsdoc -R [Homepage.md] -r -d api_reference -t [optional: jsdoc theme to use] generated/js
```
generated static site will be available at `api_reference/` directory and can be verified using simplehttpserver
```console
simplehttpserver
```
and then open `http://localhost:8000/` in browser
### Notes
we currently use [clean-jsdoc-theme](https://www.npmjs.com/package/clean-jsdoc-theme) demo at [sample-jsproto-docs/](https://projectdiscovery.github.io/js-proto-docs/)
================================================
FILE: pkg/js/devtools/bindgen/INSTALL.md
================================================
# INSTALL
1. Requires `js-beautify` node plugin installed in `$PATH`.
2. Requires `gofmt` installed in `$PATH`.
================================================
FILE: pkg/js/devtools/bindgen/README.md
================================================
## bindgen (aka bindings generator)
bindgen is a tool that automatically generated bindings for native go packages with 'goja'
Native Go packages are available [here](../../libs/)
Generated Output is available [here](../../generated/)
bindgen generates 3 different types of outputs
- `go` => this directory contains corresponding goja bindings (actual bindings code) ex: [kerberos.go](../../generated/go/libkerberos/kerberos.go)
- `js` => this is more of a javascript **representation** of all exposed functions and types etc. in javascript ex: [kerberos.js](../../generated/js/libkerberos/kerberos.js) and does not server any functional purpose other than reference
- `markdown` => autogenerated markdown documentation for each library / package ex: [kerberos.md](../../generated/markdown/libkerberos/kerberos.md)
================================================
FILE: pkg/js/devtools/bindgen/cmd/bindgen/main.go
================================================
package main
import (
"flag"
"fmt"
"log"
"path"
"path/filepath"
"github.com/pkg/errors"
generator "github.com/projectdiscovery/nuclei/v3/pkg/js/devtools/bindgen"
fileutil "github.com/projectdiscovery/utils/file"
)
var (
dir string
generatedDir string
targetModules string
)
func main() {
flag.StringVar(&dir, "dir", "libs", "directory to process")
flag.StringVar(&generatedDir, "out", "generated", "directory to output generated files")
flag.StringVar(&targetModules, "target", "", "target modules to generate")
flag.Parse()
log.SetFlags(0)
if !fileutil.FolderExists(dir) {
log.Fatalf("directory %s does not exist", dir)
}
if err := process(); err != nil {
log.Fatal(err)
}
}
func process() error {
modules, err := generator.GetLibraryModules(dir)
if err != nil {
return errors.Wrap(err, "could not get library modules")
}
if len(modules) == 0 && fileutil.FolderExists(dir) {
// if no modules are found, then given directory is the module itself
targetModules = path.Base(dir)
modules = append(modules, targetModules)
dir = filepath.Dir(dir)
}
for _, module := range modules {
log.Printf("[module] Generating %s", module)
data, err := generator.CreateTemplateData(filepath.Join(dir, module), "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/")
if err != nil {
return fmt.Errorf("could not create template data: %v", err)
}
prefixed := "lib" + module
// if !goOnly {
// err = data.WriteJSTemplate(filepath.Join(generatedDir, "js/"+prefixed), module)
// if err != nil {
// return fmt.Errorf("could not write js template: %v", err)
// }
// }
err = data.WriteGoTemplate(path.Join(generatedDir, "go/"+prefixed), module)
if err != nil {
return fmt.Errorf("could not write go template: %v", err)
}
// disabled for now since we have static website for docs
// err = data.WriteMarkdownLibraryDocumentation(path.Join(generatedDir, "markdown/"), module)
// if err != nil {
// return fmt.Errorf("could not write markdown template: %v", err)
// }
// err = data.WriteMarkdownIndexTemplate(path.Join(generatedDir, "markdown/"))
// if err != nil {
// return fmt.Errorf("could not write markdown index template: %v", err)
// }
data.InitNativeScripts()
}
return nil
}
================================================
FILE: pkg/js/devtools/bindgen/generator.go
================================================
package generator
import (
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"log"
"os"
"strings"
_ "embed"
"github.com/pkg/errors"
"github.com/projectdiscovery/nuclei/v3/pkg/js/compiler"
)
var (
//go:embed templates/js_class.tmpl
jsClassFile string
//go:embed templates/go_class.tmpl
goClassFile string
//go:embed templates/markdown_class.tmpl
markdownClassFile string
)
// TemplateData contains the parameters for the JS code generator
type TemplateData struct {
PackageName string
PackagePath string
HasObjects bool
PackageFuncs map[string]string
PackageInterfaces map[string]string
PackageFuncsExtraNoType map[string]PackageFunctionExtra
PackageFuncsExtra map[string]PackageFuncExtra
PackageVars map[string]string
PackageVarsValues map[string]string
PackageTypes map[string]string
PackageTypesExtra map[string]PackageTypeExtra
PackageDefinedConstructor map[string]struct{}
typesPackage *types.Package
// NativeScripts contains the list of native scripts
// that should be included in the package.
NativeScripts []string
}
// PackageTypeExtra contains extra information about a type
type PackageTypeExtra struct {
Fields map[string]string
}
// PackageFuncExtra contains extra information about a function
type PackageFuncExtra struct {
Items map[string]PackageFunctionExtra
Doc string
}
// PackageFunctionExtra contains extra information about a function
type PackageFunctionExtra struct {
Args []string
Name string
Returns []string
Doc string
}
// newTemplateData creates a new template data structure
func newTemplateData(packagePrefix, pkgName string) *TemplateData {
return &TemplateData{
PackageName: pkgName,
PackagePath: packagePrefix + pkgName,
PackageFuncs: make(map[string]string),
PackageFuncsExtraNoType: make(map[string]PackageFunctionExtra),
PackageFuncsExtra: make(map[string]PackageFuncExtra),
PackageVars: make(map[string]string),
PackageVarsValues: make(map[string]string),
PackageTypes: make(map[string]string),
PackageInterfaces: make(map[string]string),
PackageTypesExtra: make(map[string]PackageTypeExtra),
PackageDefinedConstructor: make(map[string]struct{}),
}
}
// GetLibraryModules takes a directory and returns subdirectories as modules
func GetLibraryModules(directory string) ([]string, error) {
dirs, err := os.ReadDir(directory)
if err != nil {
return nil, errors.Wrap(err, "could not read directory")
}
var modules []string
for _, dir := range dirs {
if dir.IsDir() {
modules = append(modules, dir.Name())
}
}
return modules, nil
}
// CreateTemplateData creates a TemplateData structure from a directory
// of go source code.
func CreateTemplateData(directory string, packagePrefix string) (*TemplateData, error) {
fmt.Println(directory)
fset := token.NewFileSet()
pkgs, err := parser.ParseDir(fset, directory, nil, parser.ParseComments) //nolint
if err != nil {
return nil, errors.Wrap(err, "could not parse directory")
}
if len(pkgs) != 1 {
return nil, fmt.Errorf("expected 1 package, got %d", len(pkgs))
}
config := &types.Config{
Importer: importer.ForCompiler(fset, "source", nil),
}
var packageName string
var files []*ast.File
for k, v := range pkgs {
packageName = k
for _, f := range v.Files {
files = append(files, f)
}
break
}
pkg, err := config.Check(packageName, fset, files, nil)
if err != nil {
return nil, errors.Wrap(err, "could not check package")
}
if len(pkgs) == 0 {
return nil, errors.New("no packages found")
}
var pkgName string
for k := range pkgs {
pkgName = k
break
}
pkgMain := pkgs[pkgName]
log.Printf("[create] [discover] Package: %s\n", pkgMain.Name)
data := newTemplateData(packagePrefix, pkgMain.Name)
data.typesPackage = pkg
data.gatherPackageData(pkgMain, data)
for item, v := range data.PackageFuncsExtra {
if len(v.Items) == 0 {
delete(data.PackageFuncsExtra, item)
}
}
// map types with corresponding constructors
for constructor := range data.PackageDefinedConstructor {
object:
for k := range data.PackageTypes {
if strings.Contains(constructor, k) {
data.PackageTypes[k] = constructor
break object
}
}
}
for k, v := range data.PackageTypes {
if k == v || v == "" {
data.HasObjects = true
data.PackageTypes[k] = ""
}
}
return data, nil
}
// InitNativeScripts initializes the native scripts array
// with all the exported functions from the runtime
func (d *TemplateData) InitNativeScripts() {
runtime := compiler.InternalGetGeneratorRuntime()
exports := runtime.Get("exports")
if exports == nil {
return
}
exportsObj := exports.Export()
if exportsObj == nil {
return
}
for v := range exportsObj.(map[string]interface{}) {
d.NativeScripts = append(d.NativeScripts, v)
}
}
// gatherPackageData gathers data about the package
func (d *TemplateData) gatherPackageData(astNode ast.Node, data *TemplateData) {
ast.Inspect(astNode, func(node ast.Node) bool {
switch node := node.(type) {
case *ast.FuncDecl:
extra := d.collectFuncDecl(node)
if extra.Name == "" {
return true
}
data.PackageFuncsExtraNoType[node.Name.Name] = extra
data.PackageFuncs[node.Name.Name] = node.Name.Name
case *ast.TypeSpec:
if !node.Name.IsExported() {
return true
}
if node.Type == nil {
return true
}
structDecl, ok := node.Type.(*ast.StructType)
if !ok {
return true
}
packageTypes := PackageTypeExtra{
Fields: make(map[string]string),
}
for _, field := range structDecl.Fields.List {
fieldName := field.Names[0].Name
var fieldTypeValue string
switch fieldType := field.Type.(type) {
case *ast.Ident: // Field type is a simple identifier
fieldTypeValue = fieldType.Name
case *ast.ArrayType:
switch fieldType.Elt.(type) {
case *ast.Ident:
fieldTypeValue = fmt.Sprintf("[]%s", fieldType.Elt.(*ast.Ident).Name)
case *ast.StarExpr:
fieldTypeValue = fmt.Sprintf("[]%s", d.handleStarExpr(fieldType.Elt.(*ast.StarExpr)))
}
case *ast.SelectorExpr: // Field type is a qualified identifier
fieldTypeValue = fmt.Sprintf("%s.%s", fieldType.X, fieldType.Sel)
}
packageTypes.Fields[fieldName] = fieldTypeValue
}
if len(packageTypes.Fields) == 0 {
return true
}
data.PackageTypesExtra[node.Name.Name] = packageTypes
case *ast.GenDecl:
identifyGenDecl(astNode, node, data)
}
return true
})
}
func identifyGenDecl(node ast.Node, decl *ast.GenDecl, data *TemplateData) {
for _, spec := range decl.Specs {
switch spec := spec.(type) {
case *ast.ValueSpec:
if !spec.Names[0].IsExported() {
continue
}
if len(spec.Values) == 0 {
continue
}
data.PackageVars[spec.Names[0].Name] = spec.Names[0].Name
data.PackageVarsValues[spec.Names[0].Name] = spec.Values[0].(*ast.BasicLit).Value
case *ast.TypeSpec:
if !spec.Name.IsExported() {
continue
}
if spec.Type == nil {
continue
}
switch spec.Type.(type) {
case *ast.InterfaceType:
data.PackageInterfaces[spec.Name.Name] = convertCommentsToJavascript(decl.Doc.Text())
case *ast.StructType:
data.PackageFuncsExtra[spec.Name.Name] = PackageFuncExtra{
Items: make(map[string]PackageFunctionExtra),
Doc: convertCommentsToJavascript(decl.Doc.Text()),
}
// Traverse the AST.
collectStructFuncsFromAST(node, spec, data)
data.PackageTypes[spec.Name.Name] = spec.Name.Name
}
}
}
}
func collectStructFuncsFromAST(node ast.Node, spec *ast.TypeSpec, data *TemplateData) {
ast.Inspect(node, func(n ast.Node) bool {
if fn, isFunc := n.(*ast.FuncDecl); isFunc && fn.Name.IsExported() {
processFunc(fn, spec, data)
}
return true
})
}
func processFunc(fn *ast.FuncDecl, spec *ast.TypeSpec, data *TemplateData) {
if fn.Recv == nil || len(fn.Recv.List) == 0 {
return
}
if t, ok := fn.Recv.List[0].Type.(*ast.StarExpr); ok {
if ident, ok := t.X.(*ast.Ident); ok && spec.Name.Name == ident.Name {
processFunctionDetails(fn, ident, data)
}
}
}
func processFunctionDetails(fn *ast.FuncDecl, ident *ast.Ident, data *TemplateData) {
extra := PackageFunctionExtra{
Name: fn.Name.Name,
Args: extractArgs(fn),
Doc: convertCommentsToJavascript(fn.Doc.Text()),
Returns: data.extractReturns(fn),
}
data.PackageFuncsExtra[ident.Name].Items[fn.Name.Name] = extra
}
func extractArgs(fn *ast.FuncDecl) []string {
args := make([]string, 0)
for _, arg := range fn.Type.Params.List {
for _, name := range arg.Names {
args = append(args, name.Name)
}
}
return args
}
func (d *TemplateData) extractReturns(fn *ast.FuncDecl) []string {
returns := make([]string, 0)
if fn.Type.Results == nil {
return returns
}
for _, ret := range fn.Type.Results.List {
returnType := d.extractReturnType(ret)
if returnType != "" {
returns = append(returns, returnType)
}
}
return returns
}
func (d *TemplateData) extractReturnType(ret *ast.Field) string {
switch v := ret.Type.(type) {
case *ast.ArrayType:
if v, ok := v.Elt.(*ast.Ident); ok {
return fmt.Sprintf("[]%s", v.Name)
}
if v, ok := v.Elt.(*ast.StarExpr); ok {
return fmt.Sprintf("[]%s", d.handleStarExpr(v))
}
case *ast.Ident:
return v.Name
case *ast.StarExpr:
return d.handleStarExpr(v)
}
return ""
}
func (d *TemplateData) handleStarExpr(v *ast.StarExpr) string {
switch vk := v.X.(type) {
case *ast.Ident:
return vk.Name
case *ast.SelectorExpr:
if vk.X != nil {
d.collectTypeFromExternal(d.typesPackage, vk.X.(*ast.Ident).Name, vk.Sel.Name)
}
return vk.Sel.Name
}
return ""
}
func (d *TemplateData) collectTypeFromExternal(pkg *types.Package, pkgName, name string) {
if pkgName == "goja" {
// no need to attempt to collect types from goja ( this is metadata )
return
}
extra := PackageTypeExtra{
Fields: make(map[string]string),
}
for _, importValue := range pkg.Imports() {
if importValue.Name() != pkgName {
continue
}
obj := importValue.Scope().Lookup(name)
if obj == nil || !obj.Exported() {
continue
}
typeName, ok := obj.(*types.TypeName)
if !ok {
continue
}
underlying, ok := typeName.Type().Underlying().(*types.Struct)
if !ok {
continue
}
for i := 0; i < underlying.NumFields(); i++ {
field := underlying.Field(i)
fieldType := field.Type().String()
if val, ok := field.Type().Underlying().(*types.Pointer); ok {
fieldType = field.Name()
d.collectTypeFromExternal(pkg, pkgName, val.Elem().(*types.Named).Obj().Name())
}
if _, ok := field.Type().Underlying().(*types.Struct); ok {
fieldType = field.Name()
d.collectTypeFromExternal(pkg, pkgName, field.Name())
}
extra.Fields[field.Name()] = fieldType
}
if len(extra.Fields) > 0 {
d.PackageTypesExtra[name] = extra
}
}
}
func (d *TemplateData) collectFuncDecl(decl *ast.FuncDecl) (extra PackageFunctionExtra) {
if decl.Recv != nil {
return
}
if !decl.Name.IsExported() {
return
}
extra.Name = decl.Name.Name
extra.Doc = convertCommentsToJavascript(decl.Doc.Text())
isConstructor := false
for _, arg := range decl.Type.Params.List {
p := exprToString(arg.Type)
if strings.Contains(p, "goja.ConstructorCall") {
isConstructor = true
}
for _, name := range arg.Names {
extra.Args = append(extra.Args, name.Name)
}
}
if isConstructor {
d.PackageDefinedConstructor[decl.Name.Name] = struct{}{}
}
extra.Returns = d.extractReturns(decl)
return extra
}
// convertCommentsToJavascript converts comments to javascript comments.
func convertCommentsToJavascript(comments string) string {
suffix := strings.Trim(strings.TrimSuffix(strings.ReplaceAll(comments, "\n", "\n// "), "// "), "\n")
return fmt.Sprintf("// %s", suffix)
}
// exprToString converts an expression to a string
func exprToString(expr ast.Expr) string {
switch t := expr.(type) {
case *ast.Ident:
return t.Name
case *ast.SelectorExpr:
return exprToString(t.X) + "." + t.Sel.Name
case *ast.StarExpr:
return exprToString(t.X)
case *ast.ArrayType:
return "[]" + exprToString(t.Elt)
case *ast.InterfaceType:
return "interface{}"
// Add more cases to handle other types
default:
return fmt.Sprintf("%T", expr)
}
}
================================================
FILE: pkg/js/devtools/bindgen/output.go
================================================
package generator
import (
"bytes"
"fmt"
"os"
"os/exec"
"path"
"strings"
"text/template"
"github.com/pkg/errors"
)
// markdownIndexes is a map of markdown modules to their filename index
//
// It is used to generate the index.md file for the documentation
var markdownIndexes = make(map[string]string)
// WriteGoTemplate writes the go template to the output file
func (d *TemplateData) WriteGoTemplate(outputDirectory string, pkgName string) error {
_ = os.MkdirAll(outputDirectory, os.ModePerm)
var err error
tmpl := template.New("go_class")
tmpl = tmpl.Funcs(templateFuncs())
tmpl, err = tmpl.Parse(goClassFile)
if err != nil {
return errors.Wrap(err, "could not parse go class template")
}
filename := path.Join(outputDirectory, fmt.Sprintf("%s.go", pkgName))
output, err := os.Create(filename)
if err != nil {
return errors.Wrap(err, "could not create go class template")
}
if err := tmpl.Execute(output, d); err != nil {
_ = output.Close()
return errors.Wrap(err, "could not execute go class template")
}
_ = output.Close()
cmd := exec.Command("gofmt", "-w", filename)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
return errors.Wrap(err, "could not format go class template")
}
return nil
}
// WriteJSTemplate writes the js template to the output file
func (d *TemplateData) WriteJSTemplate(outputDirectory string, pkgName string) error {
_ = os.MkdirAll(outputDirectory, os.ModePerm)
var err error
tmpl := template.New("js_class")
tmpl, err = tmpl.Parse(jsClassFile)
if err != nil {
return errors.Wrap(err, "could not parse js class template")
}
filename := path.Join(outputDirectory, fmt.Sprintf("%s.js", pkgName))
output, err := os.Create(filename)
if err != nil {
return errors.Wrap(err, "could not create js class template")
}
if err := tmpl.Execute(output, d); err != nil {
_ = output.Close()
return errors.Wrap(err, "could not execute js class template")
}
_ = output.Close()
cmd := exec.Command("js-beautify", "-r", filename)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
if err := cmd.Run(); err != nil {
return err
}
return nil
}
// WriteMarkdownIndexTemplate writes the markdown documentation to the output file
func (d *TemplateData) WriteMarkdownIndexTemplate(outputDirectory string) error {
_ = os.MkdirAll(outputDirectory, os.ModePerm)
filename := path.Join(outputDirectory, "index.md")
output, err := os.Create(filename)
if err != nil {
return errors.Wrap(err, "could not create markdown index template")
}
defer func() {
_ = output.Close()
}()
buffer := &bytes.Buffer{}
_, _ = buffer.WriteString("# Index\n\n")
for _, v := range markdownIndexes {
_, _ = fmt.Fprintf(buffer, "* %s\n", v)
}
_, _ = buffer.WriteString("\n\n")
_, _ = buffer.WriteString("# Scripts\n\n")
for _, v := range d.NativeScripts {
_, _ = fmt.Fprintf(buffer, "* `%s`\n", v)
}
if _, err := output.Write(buffer.Bytes()); err != nil {
return errors.Wrap(err, "could not write markdown index template")
}
return nil
}
// WriteMarkdownLibraryDocumentation writes the markdown documentation for a js library
// to the output file
func (d *TemplateData) WriteMarkdownLibraryDocumentation(outputDirectory string, pkgName string) error {
var err error
_ = os.MkdirAll(outputDirectory, os.ModePerm)
tmpl := template.New("markdown_class")
tmpl = tmpl.Funcs(templateFuncs())
tmpl, err = tmpl.Parse(markdownClassFile)
if err != nil {
return errors.Wrap(err, "could not parse markdown class template")
}
filename := path.Join(outputDirectory, fmt.Sprintf("%s.md", pkgName))
output, err := os.Create(filename)
if err != nil {
return errors.Wrap(err, "could not create markdown class template")
}
markdownIndexes[pkgName] = fmt.Sprintf("[%s](%s.md)", pkgName, pkgName)
if err := tmpl.Execute(output, d); err != nil {
_ = output.Close()
return err
}
_ = output.Close()
return nil
}
// templateFuncs returns the template functions for the generator
func templateFuncs() map[string]interface{} {
return map[string]interface{}{
"exist": func(v map[string]string, key string) bool {
_, exist := v[key]
return exist
},
"toTitle": func(v string) string {
if len(v) == 0 {
return v
}
return strings.ToUpper(string(v[0])) + v[1:]
},
"uncomment": func(v string) string {
return strings.ReplaceAll(strings.ReplaceAll(v, "// ", " "), "\n", " ")
},
}
}
================================================
FILE: pkg/js/devtools/bindgen/templates/go_class.tmpl
================================================
package {{.PackageName}}
{{$pkgName:=(printf "lib_%s" .PackageName) -}}
import (
{{$pkgName}} "{{.PackagePath}}"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/{{.PackageName}}")
)
func init() {
module.Set(
gojs.Objects{
{{- $pkgFuncs:=.PackageFuncs}}
// Functions
{{- range $objName, $objDefine := .PackageFuncs}}
"{{$objName}}": {{$pkgName}}.{{$objDefine}},
{{- end}}
// Var and consts
{{- range $objName, $objDefine := .PackageVars}}
"{{$objName}}": {{$pkgName}}.{{$objDefine}},
{{- end}}
// Objects / Classes
{{- range $objName, $objDefine := .PackageTypes}}
{{- if $objDefine}}
"{{$objName}}": {{$pkgName}}.{{$objDefine}},
{{- else}}
"{{$objName}}": gojs.GetClassConstructor[{{$pkgName}}.{{$objName}}](&{{$pkgName}}.{{$objName}}{}),
{{- end}}
{{- end}}
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/devtools/bindgen/templates/js_class.tmpl
================================================
{{$packageName:=(printf "%s" .PackageName) -}}
/**@module {{$packageName}} */
{{- range $typeName, $methods := .PackageFuncsExtra }}
{{ $methods.Doc }}
class {{$typeName}} {
{{- range $methodName, $method := $methods.Items }}
{{$method.Doc}}
{{ $method.Name }}({{range $index, $arg := $method.Args}}{{if $index}}, {{end}}{{ $arg }}{{end}}) {
return {{range $idx, $arg := $method.Returns}}{{if $idx}}, {{end}}{{ $arg }}{{end}};
};
{{- end }}
};
{{- end }}
{{- range $objName, $method := .PackageFuncsExtraNoType}}
{{$method.Doc}}
function {{$objName}}({{range $index, $arg := $method.Args}}{{if $index}}, {{end}}{{ $arg }}{{end}}) {
return {{range $idx, $arg := $method.Returns}}{{if $idx}}, {{end}}{{ $arg }}{{end}};
};
{{- end}}
module.exports = {
{{- range $typeName, $methods := .PackageFuncsExtra }}
{{$typeName}}: {{$typeName}},
{{- end }}
{{- range $objName, $method := .PackageFuncsExtraNoType}}
{{$objName}}: {{$objName}},
{{- end}}
};
================================================
FILE: pkg/js/devtools/bindgen/templates/markdown_class.tmpl
================================================
{{$packageName:=(printf "%s" .PackageName) -}}
## {{$packageName}}
---
`{{$packageName}}` implements bindings for `{{.PackageName}}` protocol in javascript
to be used from nuclei scanner.
{{ if .PackageFuncsExtra }}
## Types
{{- range $typeName, $methods := .PackageFuncsExtra }}
### {{$typeName}}
{{ uncomment $methods.Doc }}
| Method | Description | Arguments | Returns |
|--------|-------------|-----------|---------|
{{- range $methodName, $method := $methods.Items }}
| `{{$methodName}}` | {{uncomment $method.Doc}} | {{range $index, $arg := $method.Args}}{{if $index}}, {{end}}`{{ $arg }}`{{end}} | {{range $idx, $arg := $method.Returns}}{{if $idx}}, {{end}}`{{ $arg }}`{{end}} |
{{- end }}
{{- end }}
{{- end }}
{{ if .PackageFuncsExtraNoType }}
## Exported Functions
| Name | Description | Arguments | Returns |
|--------|-------------|-----------|---------|
{{- range $objName, $method := .PackageFuncsExtraNoType}}
{{$objName}} | {{uncomment $method.Doc}} | {{range $index, $arg := $method.Args}}{{if $index}}, {{end}}`{{ $arg }}`{{end}} | {{range $idx, $arg := $method.Returns}}{{if $idx}}, {{end}}`{{ $arg }}`{{end}} |
{{- end}}
{{- end}}
{{ if .PackageTypesExtra }}
## Exported Types Fields
{{- range $typeName, $methods := .PackageTypesExtra }}
### {{$typeName}}
| Name | Type |
|--------|-------------|
{{- range $fieldName, $field := $methods.Fields }}
| {{$fieldName}} | `{{ $field }}` |
{{- end }}
{{- end }}
{{- end }}
{{ if .PackageVarsValues }}
## Exported Variables Values
| Name | Value |
|--------|-------------|
{{- range $varName, $var := .PackageVarsValues }}
| {{$varName}} | `{{ $var }}` |
{{- end }}
{{- end}}
{{ if .PackageInterfaces }}
## Exported Interfaces
{{- range $typeName, $doc := .PackageInterfaces }}
### {{$typeName}}
{{ uncomment $doc }}
{{- end }}
{{- end }}
================================================
FILE: pkg/js/devtools/scrapefuncs/README.md
================================================
## scrapefuncs
scrapefuncs is go/ast based tool to scrapes all helper functions exposed in javascript with help of go/ast and generates a js file with jsdoc comments using LLM (OpenAI)
### Usage
```console
Usage of ./scrapefuncs:
-dir string
directory to process (default "pkg/js/global")
-key string
openai api key
-keyfile string
openai api key file
-out string
output js file with declarations of all global functions
```
### Example
```console
$ ./scrapefuncs -keyfile ~/.openai.key
[+] Scraped 7 functions
Name: Rand
Signatures: "Rand(n int) []byte"
Description: Rand returns a random byte slice of length n
Name: RandInt
Signatures: "RandInt() int"
Description: RandInt returns a random int
Name: log
Signatures: "log(msg string)"
Signatures: "log(msg map[string]interface{})"
Description: log prints given input to stdout with [JS] prefix for debugging purposes
Name: getNetworkPort
Signatures: "getNetworkPort(port string, defaultPort string) string"
Description: getNetworkPort registers defaultPort and returns defaultPort if it is a colliding port with other protocols
Name: isPortOpen
Signatures: "isPortOpen(host string, port string, [timeout int]) bool"
Description: isPortOpen checks if given TCP port is open on host. timeout is optional and defaults to 5 seconds
Name: isUDPPortOpen
Signatures: "isUDPPortOpen(host string, port string, [timeout int]) bool"
Description: isUDPPortOpen checks if the given UDP port is open on the host. Timeout is optional and defaults to 5 seconds.
Name: ToBytes
Signatures: "ToBytes(...interface{}) []byte"
Description: ToBytes converts given input to byte slice
Name: ToString
Signatures: "ToString(...interface{}) string"
Description: ToString converts given input to string
[+] Generating jsdoc for all functions
/**
* Rand returns a random byte slice of length n
* Rand(n int) []byte
* @function
* @param {number} n - The length of the byte slice.
*/
function Rand(n) {
// implemented in go
};
/**
* RandInt returns a random int
* RandInt() int
* @function
*/
function RandInt() {
// implemented in go
};
/**
* log prints given input to stdout with [JS] prefix for debugging purposes
* log(msg string)
* log(msg map[string]interface{})
* @function
* @param {string|Object} msg - The message to print.
*/
function log(msg) {
// implemented in go
};
/**
* getNetworkPort registers defaultPort and returns defaultPort if it is a colliding port with other protocols
* getNetworkPort(port string, defaultPort string) string
* @function
* @param {string} port - The port to check.
* @param {string} defaultPort - The default port to return if the port is colliding.
*/
function getNetworkPort(port, defaultPort) {
// implemented in go
};
/**
* isPortOpen checks if given port is open on host. timeout is optional and defaults to 5 seconds
* isPortOpen(host string, port string, [timeout int]) bool
* @function
* @param {string} host - The host to check.
* @param {string} port - The port to check.
* @param {number} [timeout=5] - The timeout in seconds.
*/
function isPortOpen(host, port, timeout = 5) {
// implemented in go
};
/**
* ToBytes converts given input to byte slice
* ToBytes(...interface{}) []byte
* @function
* @param {...any} args - The input to convert.
*/
function ToBytes(...args) {
// implemented in go
};
/**
* ToString converts given input to string
* ToString(...interface{}) string
* @function
* @param {...any} args - The input to convert.
*/
function ToString(...args) {
// implemented in go
};
```
================================================
FILE: pkg/js/devtools/scrapefuncs/main.go
================================================
package main
import (
"flag"
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"sort"
"strings"
"golang.org/x/exp/maps"
)
var (
dir string
out string
)
type DSLHelperFunc struct {
Name string
Description string
Signatures []string
}
var pkg2NameMapping = map[string]string{
"code": "Code Protocol",
"javascript": "JavaScript Protocol",
"global": "Javascript Runtime",
"compiler": "Javascript Runtime",
"flow": "Template Flow",
}
var preferredOrder = []string{"Javascript Runtime", "Template Flow", "Code Protocol", "JavaScript Protocol"}
func main() {
flag.StringVar(&dir, "dir", "pkg/", "directory to process")
flag.StringVar(&out, "out", "", "output markdown file with helper file declarations")
flag.Parse()
dirList := []string{}
if err := filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
dirList = append(dirList, path)
}
return nil
}); err != nil {
panic(err)
}
dslHelpers := map[string][]DSLHelperFunc{}
//for _, pkg := range pkgs {
for _, dir := range dirList {
fset := token.NewFileSet()
list, err := os.ReadDir(dir)
if err != nil {
fmt.Println(err)
return
}
for _, f := range list {
if f.IsDir() {
continue
}
if !strings.HasSuffix(f.Name(), ".go") {
continue
}
astFile, err := parser.ParseFile(fset, filepath.Join(dir, f.Name()), nil, parser.AllErrors|parser.SkipObjectResolution)
if err != nil {
fmt.Println(err)
return
}
ast.Inspect(astFile, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.CallExpr:
if sel, ok := x.Fun.(*ast.SelectorExpr); ok {
if sel.Sel.Name == "RegisterFuncWithSignature" {
hf := DSLHelperFunc{}
for _, arg := range x.Args {
if kv, ok := arg.(*ast.CompositeLit); ok {
for _, elt := range kv.Elts {
if kv, ok := elt.(*ast.KeyValueExpr); ok {
key := kv.Key.(*ast.Ident).Name
switch key {
case "Name":
hf.Name = strings.Trim(kv.Value.(*ast.BasicLit).Value, `"`)
case "Description":
hf.Description = strings.Trim(kv.Value.(*ast.BasicLit).Value, `"`)
case "Signatures":
if comp, ok := kv.Value.(*ast.CompositeLit); ok {
for _, signature := range comp.Elts {
hf.Signatures = append(hf.Signatures, strings.Trim(signature.(*ast.BasicLit).Value, `"`))
}
}
}
}
}
}
}
if hf.Name != "" {
identifier := pkg2NameMapping[astFile.Name.Name]
if identifier == "" {
identifier = astFile.Name.Name + " (" + dir + ")"
}
if dslHelpers[identifier] == nil {
dslHelpers[identifier] = []DSLHelperFunc{}
}
dslHelpers[identifier] = append(dslHelpers[identifier], hf)
}
}
}
}
return true
})
}
}
// DSL Helper functions stats
for pkg, funcs := range dslHelpers {
fmt.Printf("Found %d DSL Helper functions in package %s\n", len(funcs), pkg)
}
// Generate Markdown tables with ## as package name
if out != "" {
var sb strings.Builder
sb.WriteString(`---
title: "Javascript Helper Functions"
description: "Available JS Helper Functions that can be used in global js runtime & protocol specific helpers."
icon: "function"
iconType: "solid"
---
`)
actualKeys := maps.Keys(dslHelpers)
sort.Slice(actualKeys, func(i, j int) bool {
for _, preferredKey := range preferredOrder {
if actualKeys[i] == preferredKey {
return true
}
if actualKeys[j] == preferredKey {
return false
}
}
return actualKeys[i] < actualKeys[j]
})
for _, v := range actualKeys {
pkg := v
funcs := dslHelpers[pkg]
sb.WriteString("## " + pkg + "\n\n")
sb.WriteString("| Name | Description | Signatures |\n")
sb.WriteString("|------|-------------|------------|\n")
for _, f := range funcs {
sigSlice := []string{}
for _, sig := range f.Signatures {
sigSlice = append(sigSlice, "`"+sig+"`")
}
fmt.Fprintf(&sb, "| %s | %s | %s |\n", f.Name, f.Description, strings.Join(sigSlice, ", "))
}
sb.WriteString("\n")
}
if err := os.WriteFile(out, []byte(sb.String()), 0644); err != nil {
fmt.Println(err)
return
}
}
}
================================================
FILE: pkg/js/devtools/tsgen/README.md
================================================
# tsgen
tsgen is devtool to generate dummy typescript code for goja node modules written in golang. These generated typescript code can be compiled to generate .d.ts files to provide intellisense to editors like vscode and documentation for the node modules.
================================================
FILE: pkg/js/devtools/tsgen/astutil.go
================================================
package tsgen
import (
"fmt"
"go/ast"
"strings"
)
// isExported checks if the given name is exported
func isExported(name string) bool {
return ast.IsExported(name)
}
// exprToString converts an expression to a string
func exprToString(expr ast.Expr) string {
switch t := expr.(type) {
case *ast.Ident:
return toTsTypes(t.Name)
case *ast.SelectorExpr:
return exprToString(t.X) + "." + t.Sel.Name
case *ast.StarExpr:
return exprToString(t.X)
case *ast.ArrayType:
return toTsTypes("[]" + exprToString(t.Elt))
case *ast.InterfaceType:
return "interface{}"
case *ast.MapType:
return "Record<" + toTsTypes(exprToString(t.Key)) + ", " + toTsTypes(exprToString(t.Value)) + ">"
// Add more cases to handle other types
default:
return fmt.Sprintf("%T", expr)
}
}
// toTsTypes converts Go types to TypeScript types
func toTsTypes(t string) string {
if strings.Contains(t, "interface{}") {
return "any"
}
if strings.HasPrefix(t, "map[") {
return convertMaptoRecord(t)
}
switch t {
case "string":
return "string"
case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64":
return "number"
case "float32", "float64":
return "number"
case "bool":
return "boolean"
case "[]byte":
return "Uint8Array"
case "interface{}":
return "any"
case "time.Duration":
return "number"
case "time.Time":
return "Date"
default:
if strings.HasPrefix(t, "[]") {
return toTsTypes(strings.TrimPrefix(t, "[]")) + "[]"
}
return t
}
}
func TsDefaultValue(t string) string {
switch t {
case "string":
return `""`
case "number":
return `0`
case "boolean":
return `false`
case "Uint8Array":
return `new Uint8Array(8)`
case "any":
return `undefined`
case "interface{}":
return `undefined`
default:
if strings.Contains(t, "[]") {
return `[]`
}
return "new " + t + "()"
}
}
// Ternary is a ternary operator for strings
func Ternary(condition bool, trueVal, falseVal string) string {
if condition {
return trueVal
}
return falseVal
}
// checkCanFail checks if a function can fail
func checkCanFail(fn *ast.FuncDecl) bool {
if fn.Type.Results != nil {
for _, result := range fn.Type.Results.List {
// Check if any of the return types is an error
if ident, ok := result.Type.(*ast.Ident); ok && ident.Name == "error" {
return true
}
}
}
return false
}
================================================
FILE: pkg/js/devtools/tsgen/cmd/tsgen/main.go
================================================
package main
import (
"bytes"
_ "embed"
"flag"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"text/template"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/js/devtools/tsgen"
fileutil "github.com/projectdiscovery/utils/file"
)
// Define your template
//
//go:embed tsmodule.go.tmpl
var tsTemplate string
var (
source string
out string
)
func main() {
flag.StringVar(&source, "dir", "", "Directory to parse")
flag.StringVar(&out, "out", "src", "Typescript files Output directory")
flag.Parse()
// Create an instance of the template
tmpl := template.New("ts")
tmpl = tmpl.Funcs(template.FuncMap{
"splitLines": func(s string) []string {
tmp := strings.Split(s, "\n")
filtered := []string{}
for _, line := range tmp {
if strings.TrimSpace(line) != "" {
filtered = append(filtered, line)
}
}
return filtered
},
})
var err error
tmpl, err = tmpl.Parse(tsTemplate)
if err != nil {
panic(err)
}
// Create the output directory
_ = fileutil.CreateFolder(out)
dirs := []string{}
_ = filepath.WalkDir(source, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
// only load module directory skip root directory
if d.IsDir() {
files, _ := os.ReadDir(path)
for _, file := range files {
if !file.IsDir() && strings.HasSuffix(file.Name(), ".go") {
dirs = append(dirs, path)
break
}
}
}
return nil
})
// walk each directory
for _, dir := range dirs {
entityList := []tsgen.Entity{}
ep, err := tsgen.NewEntityParser(dir)
if err != nil {
panic(fmt.Errorf("could not create entity parser: %s", err))
}
if err := ep.Parse(); err != nil {
panic(fmt.Errorf("could not parse entities: %s", err))
}
entityList = append(entityList, ep.GetEntities()...)
entityList = sortEntities(entityList)
var buff bytes.Buffer
err = tmpl.Execute(&buff, entityList)
if err != nil {
panic(err)
}
moduleName := filepath.Base(dir)
gologger.Info().Msgf("Writing %s.ts", moduleName)
// create appropriate directory if missing
// _ = fileutil.CreateFolder(filepath.Join(out, moduleName))
_ = os.WriteFile(filepath.Join(out, moduleName)+".ts", buff.Bytes(), 0755)
}
// generating index.ts file
var buff bytes.Buffer
for _, dir := range dirs {
fmt.Fprintf(&buff, "export * as %s from './%s';\n", filepath.Base(dir), filepath.Base(dir))
}
_ = os.WriteFile(filepath.Join(out, "index.ts"), buff.Bytes(), 0755)
}
func sortEntities(entities []tsgen.Entity) []tsgen.Entity {
sort.Slice(entities, func(i, j int) bool {
if entities[i].Type != entities[j].Type {
// Define the order of types
order := map[string]int{"function": 1, "class": 2, "interface": 3}
return order[entities[i].Type] < order[entities[j].Type]
}
// If types are the same, sort by name
return entities[i].Name < entities[j].Name
})
return entities
}
================================================
FILE: pkg/js/devtools/tsgen/cmd/tsgen/tsmodule.go.tmpl
================================================
{{- range .}}
{{ if eq .Type "const" -}}
{{ if .Description }}/** {{ .Description }} */{{- end }}
export const {{ .Name }} = {{ .Value }};
{{- else if eq .Type "class" -}}
/**
{{- range (splitLines .Description) }}
* {{ . }}
{{- end }}
*/
export class {{ .Name }} {
{{"\n"}}
{{- range $property := .Class.Properties }}
{{ if .Description }}
/**
{{- range (splitLines $property.Description) }}
* {{ . }}
{{- end }}
*/
{{ end }}
public {{ $property.Name }}?: {{ $property.Type }};
{{"\n"}}
{{- end }}
// Constructor of {{ .Name}}
{{- if eq (len .Class.Constructor.Parameters) 0 }}
constructor() {}
{{- else }}
constructor(
{{- range $index, $param := .Class.Constructor.Parameters }}
{{- if $index }}, {{ end }}{{ $param.Name }}: {{ $param.Type }}
{{- end }} ) {}
{{"\n"}}
{{- end }}
{{- range $method := .Class.Methods }}
/**
{{- range (splitLines $method.Description) }}
* {{ . }}
{{- end }}
*/
public {{ $method.Name }}({{ range $index, $element := $method.Parameters }}{{ if $index }}, {{ end }}{{ $element.Name }}: {{ $element.Type }}{{ end }}): {{ $method.Returns }} {
{{ $method.ReturnStmt }}
}
{{"\n"}}
{{- end }}
}
{{ else if eq .Type "function" -}}
/**
{{- range (splitLines .Description) }}
* {{ . }}
{{- end }}
*/
export function {{ .Name }}({{ range $index, $element := .Function.Parameters }}{{ if $index }}, {{ end }}{{ $element.Name }}: {{ $element.Type }}{{ end }}): {{ .Function.Returns }} {
{{ .Function.ReturnStmt }}
}
{{ else if eq .Type "interface" -}}
/**
{{- range (splitLines .Description) }}
* {{ . }}
{{- end }}
*/
export interface {{ .Name }} {
{{- range $property := .Object.Properties }}
{{ if .Description }}
/**
{{- range (splitLines .Description) }}
* {{ . }}
{{- end }}
*/
{{ end }}
{{ $property.Name }}?: {{ $property.Type }},
{{- end }}
}
{{ end }}
{{- end }}
================================================
FILE: pkg/js/devtools/tsgen/parser.go
================================================
package tsgen
import (
"errors"
"fmt"
"go/ast"
"go/parser"
"go/token"
"regexp"
"strings"
"github.com/projectdiscovery/gologger"
sliceutil "github.com/projectdiscovery/utils/slice"
"golang.org/x/tools/go/packages"
)
// EntityParser is responsible for parsing a go file and generating
// corresponding typescript entities.
type EntityParser struct {
syntax []*ast.File
structTypes map[string]Entity
imports map[string]*packages.Package
newObjects map[string]*Entity // new objects to create from external packages
vars []Entity
entities []Entity
}
// NewEntityParser creates a new EntityParser
func NewEntityParser(dir string) (*EntityParser, error) {
cfg := &packages.Config{
Mode: packages.NeedName | packages.NeedFiles | packages.NeedImports |
packages.NeedTypes | packages.NeedSyntax | packages.NeedTypes |
packages.NeedModule | packages.NeedTypesInfo,
Tests: false,
Dir: dir,
ParseFile: func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
return parser.ParseFile(fset, filename, src, parser.ParseComments)
},
}
pkgs, err := packages.Load(cfg, ".")
if err != nil {
return nil, err
}
if len(pkgs) == 0 {
return nil, errors.New("no packages found")
}
pkg := pkgs[0]
return &EntityParser{
syntax: pkg.Syntax,
structTypes: map[string]Entity{},
imports: map[string]*packages.Package{},
newObjects: map[string]*Entity{},
}, nil
}
func (p *EntityParser) GetEntities() []Entity {
return p.entities
}
// Parse parses the given file and generates corresponding typescript entities
func (p *EntityParser) Parse() error {
p.extractVarsNConstants()
// extract all struct types from the AST
p.extractStructTypes()
// load all imported packages
if err := p.loadImportedPackages(); err != nil {
return err
}
for _, file := range p.syntax {
// Traverse the AST and find all relevant declarations
ast.Inspect(file, func(n ast.Node) bool {
// look for functions and methods
// and generate entities for them
fn, ok := n.(*ast.FuncDecl)
if ok {
if !isExported(fn.Name.Name) {
return false
}
entity, err := p.extractFunctionFromNode(fn)
if err != nil {
gologger.Error().Msgf("Could not extract function %s: %s\n", fn.Name.Name, err)
return false
}
if entity.IsConstructor {
// add this to the list of entities
p.entities = append(p.entities, entity)
return false
}
// check if function has a receiver
if fn.Recv != nil {
// get the name of the receiver
receiverName := exprToString(fn.Recv.List[0].Type)
// check if the receiver is a struct
if _, ok := p.structTypes[receiverName]; ok {
// add the method to the class
method := Method{
Name: entity.Name,
Description: strings.ReplaceAll(entity.Description, "Function", "Method"),
Parameters: entity.Function.Parameters,
Returns: entity.Function.Returns,
CanFail: entity.Function.CanFail,
ReturnStmt: entity.Function.ReturnStmt,
}
// add this method to corresponding class
allMethods := p.structTypes[receiverName].Class.Methods
if allMethods == nil {
allMethods = []Method{}
}
entity = p.structTypes[receiverName]
entity.Class.Methods = append(allMethods, method)
p.structTypes[receiverName] = entity
return false
}
}
// add the function to the list of global entities
p.entities = append(p.entities, entity)
return false
}
return true
})
}
for _, file := range p.syntax {
ast.Inspect(file, func(n ast.Node) bool {
// logic here to extract all fields and methods from a struct
// and add them to the entities slice
// TODO: we only support structs and not type aliases
typeSpec, ok := n.(*ast.TypeSpec)
if ok {
if !isExported(typeSpec.Name.Name) {
return false
}
structType, ok := typeSpec.Type.(*ast.StructType)
if !ok {
// This is not a struct, so continue traversing the AST
return false
}
entity := Entity{
Name: typeSpec.Name.Name,
Type: "class",
Description: Ternary(strings.TrimSpace(typeSpec.Doc.Text()) != "", typeSpec.Doc.Text(), typeSpec.Name.Name+" Class"),
Class: Class{
Properties: p.extractClassProperties(structType),
},
}
// map struct name to entity and create a new entity if doesn't exist
if _, ok := p.structTypes[typeSpec.Name.Name]; ok {
entity.Class.Methods = p.structTypes[typeSpec.Name.Name].Class.Methods
entity.Description = p.structTypes[typeSpec.Name.Name].Description
p.structTypes[typeSpec.Name.Name] = entity
} else {
p.structTypes[typeSpec.Name.Name] = entity
}
return false
}
// Continue traversing the AST
return true
})
}
// add all struct types to the list of global entities
for k, v := range p.structTypes {
if v.Type == "class" && len(v.Class.Methods) > 0 {
p.entities = append(p.entities, v)
} else if v.Type == "class" && len(v.Class.Methods) == 0 {
if k == "Object" {
continue
}
entity := Entity{
Name: k,
Type: "interface",
Description: strings.TrimSpace(strings.ReplaceAll(v.Description, "Class", "interface")),
Object: Interface{
Properties: v.Class.Properties,
},
}
p.entities = append(p.entities, entity)
}
}
// handle external structs
for k := range p.newObjects {
// if k == "Object" {
// continue
// }
if err := p.scrapeAndCreate(k); err != nil {
return fmt.Errorf("could not scrape and create new object: %s", err)
}
}
interfaceList := map[string]struct{}{}
for _, v := range p.entities {
if v.Type == "interface" {
interfaceList[v.Name] = struct{}{}
}
}
// handle method return types
for index, v := range p.entities {
if len(v.Class.Methods) > 0 {
for i, method := range v.Class.Methods {
if !strings.Contains(method.Returns, "null") {
x := strings.TrimSpace(method.Returns)
if _, ok := interfaceList[x]; ok {
// non nullable interface return type detected
method.Returns = x + " | null"
method.ReturnStmt = "return null;"
p.entities[index].Class.Methods[i] = method
}
}
}
}
}
// handle constructors
for _, v := range p.entities {
if v.IsConstructor {
// correlate it with the class
foundStruct:
for i, class := range p.entities {
if class.Type != "class" {
continue foundStruct
}
if strings.Contains(v.Name, class.Name) {
// add constructor to the class
p.entities[i].Class.Constructor = v.Function
break foundStruct
}
}
}
}
filtered := []Entity{}
for _, v := range p.entities {
if !v.IsConstructor {
filtered = append(filtered, v)
}
}
// add all vars and constants
filtered = append(filtered, p.vars...)
p.entities = filtered
return nil
}
// extractPropertiesFromStruct extracts all properties from the given struct
func (p *EntityParser) extractClassProperties(node *ast.StructType) []Property {
var properties []Property
for _, field := range node.Fields.List {
// Skip unexported fields
if len(field.Names) > 0 && !field.Names[0].IsExported() {
continue
}
// Get the type of the field as a string
typeString := exprToString(field.Type)
// If the field is anonymous (embedded), use the type name as the field name
if len(field.Names) == 0 {
if ident, ok := field.Type.(*ast.Ident); ok {
properties = append(properties, Property{
Name: ident.Name,
Type: typeString,
Description: field.Doc.Text(),
})
}
continue
}
// Iterate through all names (for multiple names in a single declaration)
for _, fieldName := range field.Names {
// Only consider exported fields
if fieldName.IsExported() {
property := Property{
Name: fieldName.Name,
Type: typeString,
Description: field.Doc.Text(),
}
if strings.Contains(property.Type, ".") {
// external struct found
property.Type = p.handleExternalStruct(property.Type)
}
properties = append(properties, property)
}
}
}
return properties
}
var (
constructorRe = `(constructor\([^)]*\))`
constructorReCompiled = regexp.MustCompile(constructorRe)
)
// extractFunctionFromNode extracts a function from the given AST node
func (p *EntityParser) extractFunctionFromNode(fn *ast.FuncDecl) (Entity, error) {
entity := Entity{
Name: fn.Name.Name,
Type: "function",
Description: Ternary(strings.TrimSpace(fn.Doc.Text()) != "", fn.Doc.Text(), fn.Name.Name+" Function"),
Function: Function{
Parameters: p.extractParameters(fn),
Returns: p.extractReturnType(fn),
CanFail: checkCanFail(fn),
},
}
// check if it is a constructor
if strings.Contains(entity.Function.Returns, "Object") && len(entity.Function.Parameters) == 2 {
// this is a constructor defined that accepts something as input
// get constructor signature from comments
constructorSig := constructorReCompiled.FindString(entity.Description)
entity.IsConstructor = true
entity.Function = updateFuncWithConstructorSig(constructorSig, entity.Function)
return entity, nil
}
// fix/adjust return statement
if entity.Function.Returns == "void" {
entity.Function.ReturnStmt = "return;"
} else if strings.Contains(entity.Function.Returns, "null") {
entity.Function.ReturnStmt = "return null;"
} else if fn.Recv != nil && exprToString(fn.Recv.List[0].Type) == entity.Function.Returns {
entity.Function.ReturnStmt = "return this;"
} else {
entity.Function.ReturnStmt = "return " + TsDefaultValue(entity.Function.Returns) + ";"
}
return entity, nil
}
// extractReturnType extracts the return type from the given function
func (p *EntityParser) extractReturnType(fn *ast.FuncDecl) (out string) {
defer func() {
if out == "" {
out = "void"
}
if strings.Contains(out, "interface{}") {
out = strings.ReplaceAll(out, "interface{}", "any")
}
}()
var returns []string
if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 {
for _, result := range fn.Type.Results.List {
tmp := exprToString(result.Type)
if strings.Contains(tmp, ".") && !strings.HasPrefix(tmp, "goja.") {
tmp = p.handleExternalStruct(tmp) + " | null" // external object interfaces can always return null
}
returns = append(returns, tmp)
}
}
if len(returns) == 1 {
val := returns[0]
val = strings.TrimPrefix(val, "*")
if val == "error" {
out = "void"
} else {
out = val
}
return
}
if len(returns) > 1 {
// in goja we stick 2 only 2 values with one being error
for _, val := range returns {
val = strings.TrimPrefix(val, "*")
if val != "error" {
out = val
break
}
}
if sliceutil.Contains(returns, "error") {
// add | null to the return type
out = out + " | null"
return
}
}
return "void"
}
// example: Map[string][]string -> Record
func convertMaptoRecord(input string) (out string) {
var key, value string
input = strings.TrimPrefix(input, "Map[")
key = input[:strings.Index(input, "]")]
value = input[strings.Index(input, "]")+1:]
return "Record<" + toTsTypes(key) + ", " + toTsTypes(value) + ">"
}
// extractParameters extracts all parameters from the given function
func (p *EntityParser) extractParameters(fn *ast.FuncDecl) []Parameter {
var parameters []Parameter
for _, param := range fn.Type.Params.List {
// get the parameter name
name := param.Names[0].Name
// get the parameter type
typ := exprToString(param.Type)
if strings.Contains(typ, ".") {
// replace with any
// we do not support or encourage passing external structs as parameters
typ = "any"
}
// add the parameter to the list of parameters
parameters = append(parameters, Parameter{
Name: name,
Type: toTsTypes(typ),
})
}
return parameters
}
// typeName is in format ssh.ClientConfig
// it first fetches all fields from the struct and creates a new object
// with that name and returns name of that object as type
func (p *EntityParser) handleExternalStruct(typeName string) string {
baseType := typeName[strings.LastIndex(typeName, ".")+1:]
p.newObjects[typeName] = &Entity{
Name: baseType,
Type: "interface",
Description: baseType + " Object",
}
// @tarunKoyalwar: scrape and create new object
// pkg := pkgMap[strings.Split(tmp, ".")[0]]
// if pkg == nil {
// for k := range pkgMap {
// fmt.Println(k)
// }
// panic("package not found")
// }
// props, err := extractFieldsFromType(pkg, tmp[strings.LastIndex(tmp, ".")+1:])
// if err != nil {
// panic(err)
// }
// // newObject := Entity{
// // Name: tmp[strings.LastIndex(tmp, ".")+1:],
// // Type: "interface",
// // Object: Object{
// // Properties: props,
// // },
// // }
// fmt.Println(props)
return baseType
}
// extractStructTypes extracts all struct types from the AST
func (p *EntityParser) extractStructTypes() {
for _, file := range p.syntax {
ast.Inspect(file, func(n ast.Node) bool {
// Check if the node is a type specification (which includes structs)
typeSpec, ok := n.(*ast.TypeSpec)
if ok {
// Check if the type specification is a struct type
_, ok := typeSpec.Type.(*ast.StructType)
if ok {
// Add the struct name to the list of struct names
p.structTypes[typeSpec.Name.Name] = Entity{
Name: typeSpec.Name.Name,
Description: typeSpec.Doc.Text(),
}
}
}
// Continue traversing the AST
return true
})
}
}
// extraGlobalConstant and vars
func (p *EntityParser) extractVarsNConstants() {
p.vars = []Entity{}
for _, file := range p.syntax {
ast.Inspect(file, func(n ast.Node) bool {
// Check if the node is a type specification (which includes structs)
gen, ok := n.(*ast.GenDecl)
if !ok {
return true
}
for _, v := range gen.Specs {
switch spec := v.(type) {
case *ast.ValueSpec:
if !spec.Names[0].IsExported() {
continue
}
if len(spec.Values) == 0 {
continue
}
// get comments or description
p.vars = append(p.vars, Entity{
Name: spec.Names[0].Name,
Type: "const",
Description: strings.TrimSpace(spec.Comment.Text()),
Value: spec.Values[0].(*ast.BasicLit).Value,
})
}
}
// Continue traversing the AST
return true
})
}
}
// loadImportedPackages loads all imported packages
func (p *EntityParser) loadImportedPackages() error {
// get all import statements
// iterate over all imports
for _, file := range p.syntax {
for _, imp := range file.Imports {
// get the package path
path := imp.Path.Value
// remove the quotes from the path
path = path[1 : len(path)-1]
// load the package
pkg, err := loadPackage(path)
if err != nil {
return err
}
importName := path[strings.LastIndex(path, "/")+1:]
if imp.Name != nil {
importName = imp.Name.Name
} else {
if !strings.HasSuffix(imp.Path.Value, pkg.Types.Name()+`"`) {
importName = pkg.Types.Name()
}
}
// add the package to the map
if _, ok := p.imports[importName]; !ok {
p.imports[importName] = pkg
}
}
}
return nil
}
// Load the package containing the type definition
// TODO: we don't support named imports yet
func loadPackage(pkgPath string) (*packages.Package, error) {
cfg := &packages.Config{Mode: packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo}
pkgs, err := packages.Load(cfg, pkgPath)
if err != nil {
return nil, err
}
if len(pkgs) == 0 {
return nil, errors.New("no packages found")
}
return pkgs[0], nil
}
// exprToString converts an expression to a string
func updateFuncWithConstructorSig(sig string, f Function) Function {
sig = strings.TrimSpace(sig)
f.Parameters = []Parameter{}
f.CanFail = true
f.ReturnStmt = ""
f.Returns = ""
if sig == "" {
return f
}
// example: constructor(public domain: string, public controller?: string)
// remove constructor( and )
sig = strings.TrimPrefix(sig, "constructor(")
sig = strings.TrimSuffix(sig, ")")
// split by comma
args := strings.Split(sig, ",")
for _, arg := range args {
arg = strings.TrimSpace(arg)
// check if it is optional
typeData := strings.Split(arg, ":")
if len(typeData) != 2 {
panic("invalid constructor signature")
}
f.Parameters = append(f.Parameters, Parameter{
Name: strings.TrimSpace(typeData[0]),
Type: strings.TrimSpace(typeData[1]),
})
}
return f
}
================================================
FILE: pkg/js/devtools/tsgen/scrape.go
================================================
package tsgen
import (
"fmt"
"go/types"
"regexp"
"strings"
"github.com/projectdiscovery/utils/errkit"
)
// scrape.go scrapes all information of exported type from different package
func (p *EntityParser) scrapeAndCreate(typeName string) error {
if p.newObjects[typeName] == nil {
return nil
}
// get package name
pkgName := strings.Split(typeName, ".")[0]
baseTypeName := strings.Split(typeName, ".")[1]
// get package
pkg, ok := p.imports[pkgName]
if !ok {
return errkit.Newf("package %v for type %v not found", pkgName, typeName)
}
// get type
obj := pkg.Types.Scope().Lookup(baseTypeName)
if obj == nil {
return errkit.Newf("type %v not found in package %+v", typeName, pkg)
}
// Ensure the object is a type name
typeNameObj, ok := obj.(*types.TypeName)
if !ok {
return errkit.Newf("%v is not a type name", typeName)
}
// Ensure the type is a named struct type
namedStruct, ok := typeNameObj.Type().Underlying().(*types.Struct)
if !ok {
return fmt.Errorf("%s is not a named struct type", typeName)
}
// fmt.Printf("got named struct %v\n", namedStruct)
// Iterate over the struct fields
d := &ExtObject{
builtIn: make(map[string]string),
nested: map[string]map[string]*ExtObject{},
}
// fmt.Printf("fields %v\n", namedStruct.NumFields())
for i := 0; i < namedStruct.NumFields(); i++ {
field := namedStruct.Field(i)
fieldName := field.Name()
if field.Exported() {
recursiveScrapeType(nil, fieldName, field.Type(), d)
}
}
entityMap := make(map[string]Entity)
// convert ExtObject to Entity
properties := ConvertExtObjectToEntities(d, entityMap)
entityMap[baseTypeName] = Entity{
Name: baseTypeName,
Type: "interface",
Description: fmt.Sprintf("%v Interface", baseTypeName),
Object: Interface{
Properties: properties,
},
}
for _, entity := range entityMap {
p.entities = append(p.entities, entity)
}
return nil
}
type ExtObject struct {
builtIn map[string]string
nested map[string]map[string]*ExtObject // Changed to map of field names to ExtObject
}
func recursiveScrapeType(parentType types.Type, fieldName string, fieldType types.Type, extObject *ExtObject) {
if named, ok := fieldType.(*types.Named); ok && !named.Obj().Exported() {
// fmt.Printf("type %v is not exported\n", named.Obj().Name())
return
}
if fieldType.String() == "time.Time" {
extObject.builtIn[fieldName] = "Date"
return
}
switch t := fieldType.Underlying().(type) {
case *types.Pointer:
// fmt.Printf("type %v is a pointer\n", fieldType)
recursiveScrapeType(nil, fieldName, t.Elem(), extObject)
case *types.Signature:
// fmt.Printf("type %v is a callback or interface\n", fieldType)
case *types.Basic:
// Check for basic types (built-in types)
if parentType != nil {
switch p := parentType.Underlying().(type) {
case *types.Slice:
extObject.builtIn[fieldName] = "[]" + fieldType.String()
case *types.Array:
extObject.builtIn[fieldName] = fmt.Sprintf("[%v]", p.Len()) + fieldType.String()
}
} else {
extObject.builtIn[fieldName] = fieldType.String()
}
case *types.Struct:
// Check for struct types
if extObject.nested[fieldName] == nil {
// @tarunKoyalwar: it currently does not supported struct arrays
extObject.nested[fieldName] = make(map[string]*ExtObject)
}
nestedExtObject := &ExtObject{
builtIn: make(map[string]string),
nested: map[string]map[string]*ExtObject{},
}
extObject.nested[fieldName][fieldType.String()] = nestedExtObject
for i := 0; i < t.NumFields(); i++ {
field := t.Field(i)
if field.Exported() {
recursiveScrapeType(nil, field.Name(), field.Type(), nestedExtObject)
}
}
case *types.Array:
// fmt.Printf("type %v is an array\n", fieldType)
// get array type
recursiveScrapeType(t, fieldName, t.Elem(), extObject)
case *types.Slice:
// fmt.Printf("type %v is a slice\n", fieldType)
// get slice type
recursiveScrapeType(t, fieldName, t.Elem(), extObject)
default:
// fmt.Printf("type %v is not a builtIn or struct\n", fieldType)
}
}
var re = regexp.MustCompile(`\[[0-9]+\].*`)
// ConvertExtObjectToEntities recursively converts an ExtObject to a list of Entity objects
func ConvertExtObjectToEntities(extObj *ExtObject, nestedTypes map[string]Entity) []Property {
var properties []Property
// Iterate over the built-in types
for fieldName, fieldType := range extObj.builtIn {
var description string
if re.MatchString(fieldType) {
// if it is a fixed size array add len in description
description = fmt.Sprintf("fixed size array of length: %v", fieldType[:strings.Index(fieldType, "]")+1])
// remove length from type
fieldType = "[]" + fieldType[strings.Index(fieldType, "]")+1:]
}
if strings.Contains(fieldType, "time.Duration") {
description = "time in nanoseconds"
}
px := Property{
Name: fieldName,
Type: toTsTypes(fieldType),
Description: description,
}
if strings.HasPrefix(px.Type, "[") {
px.Type = fieldType[strings.Index(px.Type, "]")+1:] + "[]"
}
properties = append(properties, px)
}
// Iterate over the nested types
for fieldName, nestedExtObjects := range extObj.nested {
for origType, nestedExtObject := range nestedExtObjects {
// fix:me this nestedExtObject always has only one element
got := ConvertExtObjectToEntities(nestedExtObject, nestedTypes)
baseTypename := origType[strings.LastIndex(origType, ".")+1:]
// create new nestedType
nestedTypes[baseTypename] = Entity{
Name: baseTypename,
Description: fmt.Sprintf("%v Interface", baseTypename),
Type: "interface",
Object: Interface{
Properties: got,
},
}
// assign current field type to nested type
properties = append(properties, Property{
Name: fieldName,
Type: baseTypename,
})
}
}
return properties
}
================================================
FILE: pkg/js/devtools/tsgen/types.go
================================================
package tsgen
// Define a struct to hold information about your TypeScript entities
type Entity struct {
Name string
Value string
Type string // "class", "function", or "object" or "interface" or "const"
Description string
Example string // this will be part of description with @example jsdoc tag
Class Class // if Type == "class"
Function Function // if Type == "function"
Object Interface // if Type == "object"
IsConstructor bool // true if this is a constructor function
}
// Class represents a TypeScript class data structure
type Class struct {
Properties []Property
Methods []Method
Constructor Function
}
// Function represents a TypeScript function data structure
// If CanFail is true, the function returns a Result type
// So modify the function signature to return a Result type in this case
type Function struct {
Parameters []Parameter
Returns string
CanFail bool
ReturnStmt string
}
type Interface struct {
Properties []Property
}
// Method represents a TypeScript method data structure
// If CanFail is true, the method returns a Result type
// So modify the method signature to return a Result type in this case
type Method struct {
Name string
Description string
Parameters []Parameter
Returns string
CanFail bool
ReturnStmt string
}
// Property represent class or object property
type Property struct {
Name string
Type string
Description string
}
// Parameter represents function or method parameter
type Parameter struct {
Name string
Type string
}
================================================
FILE: pkg/js/generated/README.md
================================================
## generated
!! Warning !! This is generated code, do not edit manually !!
To make any changes to this code, please refer to [bindgen](../devtools/bindgen/README.md)
================================================
FILE: pkg/js/generated/go/libbytes/bytes.go
================================================
package bytes
import (
lib_bytes "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/bytes"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/bytes")
)
func init() {
module.Set(
gojs.Objects{
// Functions
"NewBuffer": lib_bytes.NewBuffer,
// Var and consts
// Objects / Classes
"Buffer": lib_bytes.NewBuffer,
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/generated/go/libfs/fs.go
================================================
package fs
import (
lib_fs "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/fs"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/fs")
)
func init() {
module.Set(
gojs.Objects{
// Functions
"ListDir": lib_fs.ListDir,
"ReadFile": lib_fs.ReadFile,
"ReadFileAsString": lib_fs.ReadFileAsString,
"ReadFilesFromDir": lib_fs.ReadFilesFromDir,
// Var and consts
// Objects / Classes
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/generated/go/libgoconsole/goconsole.go
================================================
package goconsole
import (
lib_goconsole "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/goconsole"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/goconsole")
)
func init() {
module.Set(
gojs.Objects{
// Functions
"NewGoConsolePrinter": lib_goconsole.NewGoConsolePrinter,
// Var and consts
// Objects / Classes
"GoConsolePrinter": gojs.GetClassConstructor[lib_goconsole.GoConsolePrinter](&lib_goconsole.GoConsolePrinter{}),
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/generated/go/libikev2/ikev2.go
================================================
package ikev2
import (
lib_ikev2 "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/ikev2"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/ikev2")
)
func init() {
module.Set(
gojs.Objects{
// Functions
// Var and consts
"IKE_EXCHANGE_AUTH": lib_ikev2.IKE_EXCHANGE_AUTH,
"IKE_EXCHANGE_CREATE_CHILD_SA": lib_ikev2.IKE_EXCHANGE_CREATE_CHILD_SA,
"IKE_EXCHANGE_INFORMATIONAL": lib_ikev2.IKE_EXCHANGE_INFORMATIONAL,
"IKE_EXCHANGE_SA_INIT": lib_ikev2.IKE_EXCHANGE_SA_INIT,
"IKE_FLAGS_InitiatorBitCheck": lib_ikev2.IKE_FLAGS_InitiatorBitCheck,
"IKE_NOTIFY_NO_PROPOSAL_CHOSEN": lib_ikev2.IKE_NOTIFY_NO_PROPOSAL_CHOSEN,
"IKE_NOTIFY_USE_TRANSPORT_MODE": lib_ikev2.IKE_NOTIFY_USE_TRANSPORT_MODE,
"IKE_VERSION_2": lib_ikev2.IKE_VERSION_2,
// Objects / Classes
"IKEMessage": gojs.GetClassConstructor[lib_ikev2.IKEMessage](&lib_ikev2.IKEMessage{}),
"IKENonce": gojs.GetClassConstructor[lib_ikev2.IKENonce](&lib_ikev2.IKENonce{}),
"IKENotification": gojs.GetClassConstructor[lib_ikev2.IKENotification](&lib_ikev2.IKENotification{}),
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/generated/go/libkerberos/kerberos.go
================================================
package kerberos
import (
lib_kerberos "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/kerberos"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/kerberos")
)
func init() {
module.Set(
gojs.Objects{
// Functions
"ASRepToHashcat": lib_kerberos.ASRepToHashcat,
"CheckKrbError": lib_kerberos.CheckKrbError,
"NewKerberosClient": lib_kerberos.NewKerberosClient,
"NewKerberosClientFromString": lib_kerberos.NewKerberosClientFromString,
"SendToKDC": lib_kerberos.SendToKDC,
"TGStoHashcat": lib_kerberos.TGStoHashcat,
// Var and consts
// Objects / Classes
"Client": lib_kerberos.NewKerberosClient,
"Config": gojs.GetClassConstructor[lib_kerberos.Config](&lib_kerberos.Config{}),
"EnumerateUserResponse": gojs.GetClassConstructor[lib_kerberos.EnumerateUserResponse](&lib_kerberos.EnumerateUserResponse{}),
"TGS": gojs.GetClassConstructor[lib_kerberos.TGS](&lib_kerberos.TGS{}),
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/generated/go/libldap/ldap.go
================================================
package ldap
import (
lib_ldap "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/ldap"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/ldap")
)
func init() {
module.Set(
gojs.Objects{
// Functions
"DecodeADTimestamp": lib_ldap.DecodeADTimestamp,
"DecodeSID": lib_ldap.DecodeSID,
"DecodeZuluTimestamp": lib_ldap.DecodeZuluTimestamp,
"JoinFilters": lib_ldap.JoinFilters,
"NegativeFilter": lib_ldap.NegativeFilter,
"NewClient": lib_ldap.NewClient,
// Var and consts
"FilterAccountDisabled": lib_ldap.FilterAccountDisabled,
"FilterAccountEnabled": lib_ldap.FilterAccountEnabled,
"FilterCanSendEncryptedPassword": lib_ldap.FilterCanSendEncryptedPassword,
"FilterDontExpirePassword": lib_ldap.FilterDontExpirePassword,
"FilterDontRequirePreauth": lib_ldap.FilterDontRequirePreauth,
"FilterHasServicePrincipalName": lib_ldap.FilterHasServicePrincipalName,
"FilterHomedirRequired": lib_ldap.FilterHomedirRequired,
"FilterInterdomainTrustAccount": lib_ldap.FilterInterdomainTrustAccount,
"FilterIsAdmin": lib_ldap.FilterIsAdmin,
"FilterIsComputer": lib_ldap.FilterIsComputer,
"FilterIsDuplicateAccount": lib_ldap.FilterIsDuplicateAccount,
"FilterIsGroup": lib_ldap.FilterIsGroup,
"FilterIsNormalAccount": lib_ldap.FilterIsNormalAccount,
"FilterIsPerson": lib_ldap.FilterIsPerson,
"FilterLockout": lib_ldap.FilterLockout,
"FilterLogonScript": lib_ldap.FilterLogonScript,
"FilterMnsLogonAccount": lib_ldap.FilterMnsLogonAccount,
"FilterNotDelegated": lib_ldap.FilterNotDelegated,
"FilterPartialSecretsAccount": lib_ldap.FilterPartialSecretsAccount,
"FilterPasswordCantChange": lib_ldap.FilterPasswordCantChange,
"FilterPasswordExpired": lib_ldap.FilterPasswordExpired,
"FilterPasswordNotRequired": lib_ldap.FilterPasswordNotRequired,
"FilterServerTrustAccount": lib_ldap.FilterServerTrustAccount,
"FilterSmartCardRequired": lib_ldap.FilterSmartCardRequired,
"FilterTrustedForDelegation": lib_ldap.FilterTrustedForDelegation,
"FilterTrustedToAuthForDelegation": lib_ldap.FilterTrustedToAuthForDelegation,
"FilterUseDesKeyOnly": lib_ldap.FilterUseDesKeyOnly,
"FilterWorkstationTrustAccount": lib_ldap.FilterWorkstationTrustAccount,
// Objects / Classes
"Client": lib_ldap.NewClient,
"Config": gojs.GetClassConstructor[lib_ldap.Config](&lib_ldap.Config{}),
"LdapAttributes": gojs.GetClassConstructor[lib_ldap.LdapAttributes](&lib_ldap.LdapAttributes{}),
"LdapEntry": gojs.GetClassConstructor[lib_ldap.LdapEntry](&lib_ldap.LdapEntry{}),
"Metadata": gojs.GetClassConstructor[lib_ldap.Metadata](&lib_ldap.Metadata{}),
"SearchResult": gojs.GetClassConstructor[lib_ldap.SearchResult](&lib_ldap.SearchResult{}),
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/generated/go/libmssql/mssql.go
================================================
package mssql
import (
lib_mssql "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/mssql"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/mssql")
)
func init() {
module.Set(
gojs.Objects{
// Functions
// Var and consts
// Objects / Classes
"MSSQLClient": gojs.GetClassConstructor[lib_mssql.MSSQLClient](&lib_mssql.MSSQLClient{}),
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/generated/go/libmysql/mysql.go
================================================
package mysql
import (
lib_mysql "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/mysql"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/mysql")
)
func init() {
module.Set(
gojs.Objects{
// Functions
"BuildDSN": lib_mysql.BuildDSN,
// Var and consts
// Objects / Classes
"MySQLClient": gojs.GetClassConstructor[lib_mysql.MySQLClient](&lib_mysql.MySQLClient{}),
"MySQLInfo": gojs.GetClassConstructor[lib_mysql.MySQLInfo](&lib_mysql.MySQLInfo{}),
"MySQLOptions": gojs.GetClassConstructor[lib_mysql.MySQLOptions](&lib_mysql.MySQLOptions{}),
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/generated/go/libnet/net.go
================================================
package net
import (
lib_net "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/net"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/net")
)
func init() {
module.Set(
gojs.Objects{
// Functions
"Open": lib_net.Open,
"OpenTLS": lib_net.OpenTLS,
// Var and consts
// Objects / Classes
"NetConn": gojs.GetClassConstructor[lib_net.NetConn](&lib_net.NetConn{}),
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/generated/go/liboracle/oracle.go
================================================
package oracle
import (
lib_oracle "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/oracle"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/oracle")
)
func init() {
module.Set(
gojs.Objects{
// Functions
// Var and consts
// Objects / Classes
"IsOracleResponse": gojs.GetClassConstructor[lib_oracle.IsOracleResponse](&lib_oracle.IsOracleResponse{}),
"OracleClient": gojs.GetClassConstructor[lib_oracle.OracleClient](&lib_oracle.OracleClient{}),
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/generated/go/libpop3/pop3.go
================================================
package pop3
import (
lib_pop3 "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/pop3"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/pop3")
)
func init() {
module.Set(
gojs.Objects{
// Functions
"IsPOP3": lib_pop3.IsPOP3,
// Var and consts
// Objects / Classes
"IsPOP3Response": gojs.GetClassConstructor[lib_pop3.IsPOP3Response](&lib_pop3.IsPOP3Response{}),
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/generated/go/libpostgres/postgres.go
================================================
package postgres
import (
lib_postgres "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/postgres"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/postgres")
)
func init() {
module.Set(
gojs.Objects{
// Functions
// Var and consts
// Objects / Classes
"PGClient": gojs.GetClassConstructor[lib_postgres.PGClient](&lib_postgres.PGClient{}),
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/generated/go/librdp/rdp.go
================================================
package rdp
import (
lib_rdp "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/rdp"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/rdp")
)
func init() {
module.Set(
gojs.Objects{
// Functions
"CheckRDPAuth": lib_rdp.CheckRDPAuth,
"CheckRDPEncryption": lib_rdp.CheckRDPEncryption,
"IsRDP": lib_rdp.IsRDP,
// Var and consts
// Objects / Classes
"CheckRDPAuthResponse": gojs.GetClassConstructor[lib_rdp.CheckRDPAuthResponse](&lib_rdp.CheckRDPAuthResponse{}),
"CheckRDPEncryptionResponse": gojs.GetClassConstructor[lib_rdp.RDPEncryptionResponse](&lib_rdp.RDPEncryptionResponse{}),
"IsRDPResponse": gojs.GetClassConstructor[lib_rdp.IsRDPResponse](&lib_rdp.IsRDPResponse{}),
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/generated/go/libredis/redis.go
================================================
package redis
import (
lib_redis "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/redis"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/redis")
)
func init() {
module.Set(
gojs.Objects{
// Functions
"Connect": lib_redis.Connect,
"GetServerInfo": lib_redis.GetServerInfo,
"GetServerInfoAuth": lib_redis.GetServerInfoAuth,
"IsAuthenticated": lib_redis.IsAuthenticated,
"RunLuaScript": lib_redis.RunLuaScript,
// Var and consts
// Objects / Classes
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/generated/go/librsync/rsync.go
================================================
package rsync
import (
lib_rsync "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/rsync"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/rsync")
)
func init() {
module.Set(
gojs.Objects{
// Functions
"IsRsync": lib_rsync.IsRsync,
// Var and consts
// Objects / Classes
"IsRsyncResponse": gojs.GetClassConstructor[lib_rsync.IsRsyncResponse](&lib_rsync.IsRsyncResponse{}),
"RsyncClient": gojs.GetClassConstructor[lib_rsync.RsyncClient](&lib_rsync.RsyncClient{}),
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/generated/go/libsmb/smb.go
================================================
package smb
import (
lib_smb "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/smb"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/smb")
)
func init() {
module.Set(
gojs.Objects{
// Functions
// Var and consts
// Objects / Classes
"SMBClient": gojs.GetClassConstructor[lib_smb.SMBClient](&lib_smb.SMBClient{}),
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/generated/go/libsmtp/smtp.go
================================================
package smtp
import (
lib_smtp "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/smtp"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/smtp")
)
func init() {
module.Set(
gojs.Objects{
// Functions
"NewSMTPClient": lib_smtp.NewSMTPClient,
// Var and consts
// Objects / Classes
"Client": lib_smtp.NewSMTPClient,
"SMTPMessage": gojs.GetClassConstructor[lib_smtp.SMTPMessage](&lib_smtp.SMTPMessage{}),
"SMTPResponse": gojs.GetClassConstructor[lib_smtp.SMTPResponse](&lib_smtp.SMTPResponse{}),
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/generated/go/libssh/ssh.go
================================================
package ssh
import (
lib_ssh "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/ssh"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/ssh")
)
func init() {
module.Set(
gojs.Objects{
// Functions
// Var and consts
// Objects / Classes
"SSHClient": gojs.GetClassConstructor[lib_ssh.SSHClient](&lib_ssh.SSHClient{}),
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/generated/go/libstructs/structs.go
================================================
package structs
import (
lib_structs "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/structs"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/structs")
)
func init() {
module.Set(
gojs.Objects{
// Functions
"Pack": lib_structs.Pack,
"StructsCalcSize": lib_structs.StructsCalcSize,
"Unpack": lib_structs.Unpack,
// Var and consts
// Objects / Classes
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/generated/go/libtelnet/telnet.go
================================================
package telnet
import (
lib_telnet "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/telnet"
telnetmini "github.com/projectdiscovery/nuclei/v3/pkg/utils/telnetmini"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/telnet")
)
func init() {
module.Set(
gojs.Objects{
// Functions
"IsTelnet": lib_telnet.IsTelnet,
// Var and consts
// Objects / Classes
"TelnetClient": gojs.GetClassConstructor[lib_telnet.TelnetClient](&lib_telnet.TelnetClient{}),
"IsTelnetResponse": gojs.GetClassConstructor[lib_telnet.IsTelnetResponse](&lib_telnet.IsTelnetResponse{}),
"TelnetInfoResponse": gojs.GetClassConstructor[lib_telnet.TelnetInfoResponse](&lib_telnet.TelnetInfoResponse{}),
"NTLMInfoResponse": gojs.GetClassConstructor[telnetmini.NTLMInfoResponse](&telnetmini.NTLMInfoResponse{}),
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/generated/go/libvnc/vnc.go
================================================
package vnc
import (
lib_vnc "github.com/projectdiscovery/nuclei/v3/pkg/js/libs/vnc"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
var (
module = gojs.NewGojaModule("nuclei/vnc")
)
func init() {
module.Set(
gojs.Objects{
// Functions
"IsVNC": lib_vnc.IsVNC,
// Var and consts
// Objects / Classes
"IsVNCResponse": gojs.GetClassConstructor[lib_vnc.IsVNCResponse](&lib_vnc.IsVNCResponse{}),
"VNCClient": gojs.GetClassConstructor[lib_vnc.VNCClient](&lib_vnc.VNCClient{}),
},
).Register()
}
func Enable(runtime *goja.Runtime) {
module.Enable(runtime)
}
================================================
FILE: pkg/js/generated/ts/bytes.ts
================================================
/**
* Buffer is a bytes/Uint8Array type in javascript
* @example
* ```javascript
* const bytes = require('nuclei/bytes');
* const bytes = new bytes.Buffer();
* ```
* @example
* ```javascript
* const bytes = require('nuclei/bytes');
* // optionally it can accept existing byte/Uint8Array as input
* const bytes = new bytes.Buffer([1, 2, 3]);
* ```
*/
export class Buffer {
// Constructor of Buffer
constructor() {}
/**
* Write appends the given data to the buffer.
* @example
* ```javascript
* const bytes = require('nuclei/bytes');
* const buffer = new bytes.Buffer();
* buffer.Write([1, 2, 3]);
* ```
*/
public Write(data: Uint8Array): Buffer {
return this;
}
/**
* WriteString appends the given string data to the buffer.
* @example
* ```javascript
* const bytes = require('nuclei/bytes');
* const buffer = new bytes.Buffer();
* buffer.WriteString('hello');
* ```
*/
public WriteString(data: string): Buffer {
return this;
}
/**
* Bytes returns the byte representation of the buffer.
* @example
* ```javascript
* const bytes = require('nuclei/bytes');
* const buffer = new bytes.Buffer();
* buffer.WriteString('hello');
* log(buffer.Bytes());
* ```
*/
public Bytes(): Uint8Array {
return new Uint8Array(8);
}
/**
* String returns the string representation of the buffer.
* @example
* ```javascript
* const bytes = require('nuclei/bytes');
* const buffer = new bytes.Buffer();
* buffer.WriteString('hello');
* log(buffer.String());
* ```
*/
public String(): string {
return "";
}
/**
* Len returns the length of the buffer.
* @example
* ```javascript
* const bytes = require('nuclei/bytes');
* const buffer = new bytes.Buffer();
* buffer.WriteString('hello');
* log(buffer.Len());
* ```
*/
public Len(): number {
return 0;
}
/**
* Hex returns the hex representation of the buffer.
* @example
* ```javascript
* const bytes = require('nuclei/bytes');
* const buffer = new bytes.Buffer();
* buffer.WriteString('hello');
* log(buffer.Hex());
* ```
*/
public Hex(): string {
return "";
}
/**
* Hexdump returns the hexdump representation of the buffer.
* @example
* ```javascript
* const bytes = require('nuclei/bytes');
* const buffer = new bytes.Buffer();
* buffer.WriteString('hello');
* log(buffer.Hexdump());
* ```
*/
public Hexdump(): string {
return "";
}
/**
* Pack uses structs.Pack and packs given data and appends it to the buffer.
* it packs the data according to the given format.
* @example
* ```javascript
* const bytes = require('nuclei/bytes');
* const buffer = new bytes.Buffer();
* buffer.Pack('I', 123);
* ```
*/
public Pack(formatStr: string, msg: any): void {
return;
}
}
================================================
FILE: pkg/js/generated/ts/fs.ts
================================================
/**
* ListDir lists itemType values within a directory
* depending on the itemType provided
* itemType can be any one of ['file','dir',”]
* @example
* ```javascript
* const fs = require('nuclei/fs');
* // this will only return files in /tmp directory
* const files = fs.ListDir('/tmp', 'file');
* ```
* @example
* ```javascript
* const fs = require('nuclei/fs');
* // this will only return directories in /tmp directory
* const dirs = fs.ListDir('/tmp', 'dir');
* ```
* @example
* ```javascript
* const fs = require('nuclei/fs');
* // when no itemType is provided, it will return both files and directories
* const items = fs.ListDir('/tmp');
* ```
*/
export function ListDir(path: string, itemType: string): string[] | null {
return null;
}
/**
* ReadFile reads file contents within permitted paths
* and returns content as byte array
* @example
* ```javascript
* const fs = require('nuclei/fs');
* // here permitted directories are $HOME/nuclei-templates/*
* const content = fs.ReadFile('helpers/usernames.txt');
* ```
*/
export function ReadFile(path: string): Uint8Array | null {
return null;
}
/**
* ReadFileAsString reads file contents within permitted paths
* and returns content as string
* @example
* ```javascript
* const fs = require('nuclei/fs');
* // here permitted directories are $HOME/nuclei-templates/*
* const content = fs.ReadFileAsString('helpers/usernames.txt');
* ```
*/
export function ReadFileAsString(path: string): string | null {
return null;
}
/**
* ReadFilesFromDir reads all files from a directory
* and returns a string array with file contents of all files
* @example
* ```javascript
* const fs = require('nuclei/fs');
* // here permitted directories are $HOME/nuclei-templates/*
* const contents = fs.ReadFilesFromDir('helpers/ssh-keys');
* log(contents);
* ```
*/
export function ReadFilesFromDir(dir: string): string[] | null {
return null;
}
================================================
FILE: pkg/js/generated/ts/goconsole.ts
================================================
/**
* NewGoConsolePrinter Function
*/
export function NewGoConsolePrinter(): GoConsolePrinter {
return new GoConsolePrinter();
}
/**
*/
export class GoConsolePrinter {
// Constructor of GoConsolePrinter
constructor() {}
/**
* Log Method
*/
public Log(msg: string): void {
return;
}
/**
* Warn Method
*/
public Warn(msg: string): void {
return;
}
/**
* Error Method
*/
public Error(msg: string): void {
return;
}
}
================================================
FILE: pkg/js/generated/ts/ikev2.ts
================================================
export const IKE_EXCHANGE_AUTH = 35;
export const IKE_EXCHANGE_CREATE_CHILD_SA = 36;
export const IKE_EXCHANGE_INFORMATIONAL = 37;
export const IKE_EXCHANGE_SA_INIT = 34;
export const IKE_FLAGS_InitiatorBitCheck = 0x08;
export const IKE_NOTIFY_NO_PROPOSAL_CHOSEN = 14;
export const IKE_NOTIFY_USE_TRANSPORT_MODE = 16391;
export const IKE_VERSION_2 = 0x20;
/**
* IKEMessage is the IKEv2 message
* IKEv2 implements a limited subset of IKEv2 Protocol, specifically
* the IKE_NOTIFY and IKE_NONCE payloads and the IKE_SA_INIT exchange.
*/
export class IKEMessage {
public InitiatorSPI?: number;
public Version?: number;
public ExchangeType?: number;
public Flags?: number;
// Constructor of IKEMessage
constructor() {}
/**
* AppendPayload appends a payload to the IKE message
* payload can be any of the payloads like IKENotification, IKENonce, etc.
* @example
* ```javascript
* const ikev2 = require('nuclei/ikev2');
* const message = new ikev2.IKEMessage();
* const nonce = new ikev2.IKENonce();
* nonce.NonceData = [1, 2, 3];
* message.AppendPayload(nonce);
* ```
*/
public AppendPayload(payload: any): void {
return;
}
/**
* Encode encodes the final IKE message
* @example
* ```javascript
* const ikev2 = require('nuclei/ikev2');
* const message = new ikev2.IKEMessage();
* const nonce = new ikev2.IKENonce();
* nonce.NonceData = [1, 2, 3];
* message.AppendPayload(nonce);
* log(message.Encode());
* ```
*/
public Encode(): Uint8Array | null {
return null;
}
}
/**
* IKENonce is the IKEv2 Nonce payload
* this implements the IKEPayload interface
* @example
* ```javascript
* const ikev2 = require('nuclei/ikev2');
* const nonce = new ikev2.IKENonce();
* nonce.NonceData = [1, 2, 3];
* ```
*/
export interface IKENonce {
NonceData?: Uint8Array,
}
/**
* IKEv2Notify is the IKEv2 Notification payload
* this implements the IKEPayload interface
* @example
* ```javascript
* const ikev2 = require('nuclei/ikev2');
* const notify = new ikev2.IKENotification();
* notify.NotifyMessageType = ikev2.IKE_NOTIFY_NO_PROPOSAL_CHOSEN;
* notify.NotificationData = [1, 2, 3];
* ```
*/
export interface IKENotification {
NotifyMessageType?: number,
NotificationData?: Uint8Array,
}
================================================
FILE: pkg/js/generated/ts/index.ts
================================================
export * as bytes from './bytes';
export * as fs from './fs';
export * as goconsole from './goconsole';
export * as ikev2 from './ikev2';
export * as kerberos from './kerberos';
export * as ldap from './ldap';
export * as mssql from './mssql';
export * as mysql from './mysql';
export * as net from './net';
export * as oracle from './oracle';
export * as pop3 from './pop3';
export * as postgres from './postgres';
export * as rdp from './rdp';
export * as redis from './redis';
export * as rsync from './rsync';
export * as smb from './smb';
export * as smtp from './smtp';
export * as ssh from './ssh';
export * as structs from './structs';
export * as telnet from './telnet';
export * as vnc from './vnc';
================================================
FILE: pkg/js/generated/ts/kerberos.ts
================================================
/**
* ASRepToHashcat converts an AS-REP message to a hashcat format
*/
export function ASRepToHashcat(asrep: any): string | null {
return null;
}
/**
* CheckKrbError checks if the response bytes from the KDC are a KRBError.
*/
export function CheckKrbError(b: Uint8Array): Uint8Array | null {
return null;
}
/**
* NewKerberosClientFromString creates a new kerberos client from a string
* by parsing krb5.conf
* @example
* ```javascript
* const kerberos = require('nuclei/kerberos');
* const client = kerberos.NewKerberosClientFromString(`
* [libdefaults]
* default_realm = ACME.COM
* dns_lookup_kdc = true
* `);
* ```
*/
export function NewKerberosClientFromString(cfg: string): Client | null {
return null;
}
/**
* sendtokdc.go deals with actual sending and receiving responses from KDC
* SendToKDC sends a message to the KDC and returns the response.
* It first tries to send the message over TCP, and if that fails, it falls back to UDP.(and vice versa)
* @example
* ```javascript
* const kerberos = require('nuclei/kerberos');
* const client = new kerberos.Client('acme.com');
* const response = kerberos.SendToKDC(client, 'message');
* ```
*/
export function SendToKDC(kclient: Client, msg: string): string | null {
return null;
}
/**
* TGStoHashcat converts a TGS to a hashcat format.
*/
export function TGStoHashcat(tgs: any, username: string): string | null {
return null;
}
/**
* Known Issues:
* Hardcoded timeout in gokrb5 library
* TGT / Session Handling not exposed
* Client is kerberos client
* @example
* ```javascript
* const kerberos = require('nuclei/kerberos');
* // if controller is empty a dns lookup for default kdc server will be performed
* const client = new kerberos.Client('acme.com', 'kdc.acme.com');
* ```
*/
export class Client {
public Krb5Config?: Config;
public Realm?: string;
// Constructor of Client
constructor(public domain: string, public controller?: string ) {}
/**
* SetConfig sets additional config for the kerberos client
* Note: as of now ip and timeout overrides are only supported
* in EnumerateUser due to fastdialer but can be extended to other methods currently
* @example
* ```javascript
* const kerberos = require('nuclei/kerberos');
* const client = new kerberos.Client('acme.com', 'kdc.acme.com');
* const cfg = new kerberos.Config();
* cfg.SetIPAddress('192.168.100.22');
* cfg.SetTimeout(5);
* client.SetConfig(cfg);
* ```
*/
public SetConfig(cfg: Config): void {
return;
}
/**
* EnumerateUser and attempt to get AS-REP hash by disabling PA-FX-FAST
* @example
* ```javascript
* const kerberos = require('nuclei/kerberos');
* const client = new kerberos.Client('acme.com', 'kdc.acme.com');
* const resp = client.EnumerateUser('pdtm');
* log(resp);
* ```
*/
public EnumerateUser(username: string): EnumerateUserResponse | null {
return null;
}
/**
* GetServiceTicket returns a TGS for a given user, password and SPN
* @example
* ```javascript
* const kerberos = require('nuclei/kerberos');
* const client = new kerberos.Client('acme.com', 'kdc.acme.com');
* const resp = client.GetServiceTicket('pdtm', 'password', 'HOST/CLIENT1');
* log(resp);
* ```
*/
public GetServiceTicket(User: string): TGS | null {
return null;
}
}
/**
* Config is extra configuration for the kerberos client
*/
export class Config {
// Constructor of Config
constructor() {}
/**
* SetIPAddress sets the IP address for the kerberos client
* @example
* ```javascript
* const kerberos = require('nuclei/kerberos');
* const cfg = new kerberos.Config();
* cfg.SetIPAddress('10.10.10.1');
* ```
*/
public SetIPAddress(ip: string): Config | null {
return null;
}
/**
* SetTimeout sets the RW timeout for the kerberos client
* @example
* ```javascript
* const kerberos = require('nuclei/kerberos');
* const cfg = new kerberos.Config();
* cfg.SetTimeout(5);
* ```
*/
public SetTimeout(timeout: number): Config | null {
return null;
}
}
/**
* AuthorizationDataEntry Interface
*/
export interface AuthorizationDataEntry {
ADData?: Uint8Array,
ADType?: number,
}
/**
* BitString Interface
*/
export interface BitString {
Bytes?: Uint8Array,
BitLength?: number,
}
/**
* BitString Interface
*/
export interface BitString {
Bytes?: Uint8Array,
BitLength?: number,
}
/**
* Config Interface
*/
export interface Config {
LibDefaults?: LibDefaults,
Realms?: Realm,
}
/**
* EncTicketPart Interface
*/
export interface EncTicketPart {
EndTime?: Date,
RenewTill?: Date,
CRealm?: string,
AuthTime?: Date,
StartTime?: Date,
Flags?: BitString,
Key?: EncryptionKey,
CName?: PrincipalName,
Transited?: TransitedEncoding,
CAddr?: HostAddress,
AuthorizationData?: AuthorizationDataEntry,
}
/**
* EncryptedData Interface
*/
export interface EncryptedData {
EType?: number,
KVNO?: number,
Cipher?: Uint8Array,
}
/**
* EncryptionKey Interface
*/
export interface EncryptionKey {
KeyType?: number,
KeyValue?: Uint8Array,
}
/**
* EnumerateUserResponse is the response from EnumerateUser
*/
export interface EnumerateUserResponse {
Valid?: boolean,
ASREPHash?: string,
Error?: string,
}
/**
* HostAddress Interface
*/
export interface HostAddress {
AddrType?: number,
Address?: Uint8Array,
}
/**
* LibDefaults Interface
*/
export interface LibDefaults {
CCacheType?: number,
K5LoginAuthoritative?: boolean,
Proxiable?: boolean,
RDNS?: boolean,
K5LoginDirectory?: string,
KDCTimeSync?: number,
VerifyAPReqNofail?: boolean,
DefaultTGSEnctypes?: string[],
DefaultTGSEnctypeIDs?: number[],
DNSCanonicalizeHostname?: boolean,
Forwardable?: boolean,
/**
* time in nanoseconds
*/
RenewLifetime?: number,
/**
* time in nanoseconds
*/
TicketLifetime?: number,
DefaultClientKeytabName?: string,
DefaultTktEnctypeIDs?: number[],
DNSLookupRealm?: boolean,
ExtraAddresses?: Uint8Array,
DefaultRealm?: string,
NoAddresses?: boolean,
PreferredPreauthTypes?: number[],
PermittedEnctypeIDs?: number[],
RealmTryDomains?: number,
DefaultKeytabName?: string,
DefaultTktEnctypes?: string[],
DNSLookupKDC?: boolean,
IgnoreAcceptorHostname?: boolean,
AllowWeakCrypto?: boolean,
Canonicalize?: boolean,
SafeChecksumType?: number,
UDPPreferenceLimit?: number,
/**
* time in nanoseconds
*/
Clockskew?: number,
PermittedEnctypes?: string[],
KDCDefaultOptions?: BitString,
}
/**
* PrincipalName Interface
*/
export interface PrincipalName {
NameString?: string[],
NameType?: number,
}
/**
* Realm Interface
*/
export interface Realm {
Realm?: string,
AdminServer?: string[],
DefaultDomain?: string,
KDC?: string[],
KPasswdServer?: string[],
MasterKDC?: string[],
}
/**
* TGS is the response from GetServiceTicket
*/
export interface TGS {
Ticket?: Ticket,
Hash?: string,
ErrMsg?: string,
}
/**
* Ticket Interface
*/
export interface Ticket {
TktVNO?: number,
Realm?: string,
SName?: PrincipalName,
EncPart?: EncryptedData,
DecryptedEncPart?: EncTicketPart,
}
/**
* TransitedEncoding Interface
*/
export interface TransitedEncoding {
TRType?: number,
Contents?: Uint8Array,
}
================================================
FILE: pkg/js/generated/ts/ldap.ts
================================================
/** The user account is disabled. */
export const FilterAccountDisabled = "(userAccountControl:1.2.840.113556.1.4.803:=2)";
/** The user account is enabled. */
export const FilterAccountEnabled = "(!(userAccountControl:1.2.840.113556.1.4.803:=2))";
/** The user can send an encrypted password. */
export const FilterCanSendEncryptedPassword = "(userAccountControl:1.2.840.113556.1.4.803:=128)";
/** Represents the password, which should never expire on the account. */
export const FilterDontExpirePassword = "(userAccountControl:1.2.840.113556.1.4.803:=65536)";
/** This account doesn't require Kerberos pre-authentication for logging on. */
export const FilterDontRequirePreauth = "(userAccountControl:1.2.840.113556.1.4.803:=4194304)";
/** The object has a service principal name. */
export const FilterHasServicePrincipalName = "(servicePrincipalName=*)";
/** The home folder is required. */
export const FilterHomedirRequired = "(userAccountControl:1.2.840.113556.1.4.803:=8)";
/** It's a permit to trust an account for a system domain that trusts other domains. */
export const FilterInterdomainTrustAccount = "(userAccountControl:1.2.840.113556.1.4.803:=2048)";
/** The object is an admin. */
export const FilterIsAdmin = "(adminCount=1)";
/** The object is a computer. */
export const FilterIsComputer = "(objectCategory=computer)";
/** It's an account for users whose primary account is in another domain. */
export const FilterIsDuplicateAccount = "(userAccountControl:1.2.840.113556.1.4.803:=256)";
/** The object is a group. */
export const FilterIsGroup = "(objectCategory=group)";
/** It's a default account type that represents a typical user. */
export const FilterIsNormalAccount = "(userAccountControl:1.2.840.113556.1.4.803:=512)";
/** The object is a person. */
export const FilterIsPerson = "(objectCategory=person)";
/** The user is locked out. */
export const FilterLockout = "(userAccountControl:1.2.840.113556.1.4.803:=16)";
/** The logon script will be run. */
export const FilterLogonScript = "(userAccountControl:1.2.840.113556.1.4.803:=1)";
/** It's an MNS logon account. */
export const FilterMnsLogonAccount = "(userAccountControl:1.2.840.113556.1.4.803:=131072)";
/** When this flag is set, the security context of the user isn't delegated to a service even if the service account is set as trusted for Kerberos delegation. */
export const FilterNotDelegated = "(userAccountControl:1.2.840.113556.1.4.803:=1048576)";
/** The account is a read-only domain controller (RODC). */
export const FilterPartialSecretsAccount = "(userAccountControl:1.2.840.113556.1.4.803:=67108864)";
/** The user can't change the password. */
export const FilterPasswordCantChange = "(userAccountControl:1.2.840.113556.1.4.803:=64)";
/** The user's password has expired. */
export const FilterPasswordExpired = "(userAccountControl:1.2.840.113556.1.4.803:=8388608)";
/** No password is required. */
export const FilterPasswordNotRequired = "(userAccountControl:1.2.840.113556.1.4.803:=32)";
/** It's a computer account for a domain controller that is a member of this domain. */
export const FilterServerTrustAccount = "(userAccountControl:1.2.840.113556.1.4.803:=8192)";
/** When this flag is set, it forces the user to log on by using a smart card. */
export const FilterSmartCardRequired = "(userAccountControl:1.2.840.113556.1.4.803:=262144)";
/** When this flag is set, the service account (the user or computer account) under which a service runs is trusted for Kerberos delegation. */
export const FilterTrustedForDelegation = "(userAccountControl:1.2.840.113556.1.4.803:=524288)";
/** The account is enabled for delegation. */
export const FilterTrustedToAuthForDelegation = "(userAccountControl:1.2.840.113556.1.4.803:=16777216)";
/** Restrict this principal to use only Data Encryption Standard (DES) encryption types for keys. */
export const FilterUseDesKeyOnly = "(userAccountControl:1.2.840.113556.1.4.803:=2097152)";
/** It's a computer account for a computer that is running old Windows builds. */
export const FilterWorkstationTrustAccount = "(userAccountControl:1.2.840.113556.1.4.803:=4096)";
/**
* DecodeADTimestamp decodes an Active Directory timestamp
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const timestamp = ldap.DecodeADTimestamp('132036744000000000');
* log(timestamp);
* ```
*/
export function DecodeADTimestamp(timestamp: string): string {
return "";
}
/**
* DecodeSID decodes a SID string
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const sid = ldap.DecodeSID('S-1-5-21-3623811015-3361044348-30300820-1013');
* log(sid);
* ```
*/
export function DecodeSID(s: string): string {
return "";
}
/**
* DecodeZuluTimestamp decodes a Zulu timestamp
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const timestamp = ldap.DecodeZuluTimestamp('2021-08-25T10:00:00Z');
* log(timestamp);
* ```
*/
export function DecodeZuluTimestamp(timestamp: string): string {
return "";
}
/**
* JoinFilters joins multiple filters into a single filter
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const filter = ldap.JoinFilters(ldap.FilterIsPerson, ldap.FilterAccountEnabled);
* ```
*/
export function JoinFilters(filters: any): string {
return "";
}
/**
* NegativeFilter returns a negative filter for a given filter
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const filter = ldap.NegativeFilter(ldap.FilterIsPerson);
* ```
*/
export function NegativeFilter(filter: string): string {
return "";
}
/**
* Client is a client for ldap protocol in nuclei
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* // here ldap.example.com is the ldap server and acme.com is the realm
* const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
* ```
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const cfg = new ldap.Config();
* cfg.Timeout = 10;
* cfg.ServerName = 'ldap.internal.acme.com';
* // optional config can be passed as third argument
* const client = new ldap.Client('ldap://ldap.example.com', 'acme.com', cfg);
* ```
*/
export class Client {
public Host?: string;
public Port?: number;
public Realm?: string;
public BaseDN?: string;
// Constructor of Client
constructor(public ldapUrl: string, public realm: string, public config?: Config ) {}
/**
* FindADObjects finds AD objects based on a filter
* and returns them as a list of ADObject
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
* const users = client.FindADObjects(ldap.FilterIsPerson);
* log(to_json(users));
* ```
*/
public FindADObjects(filter: string): SearchResult | null {
return null;
}
/**
* GetADUsers returns all AD users
* using FilterIsPerson filter query
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
* const users = client.GetADUsers();
* log(to_json(users));
* ```
*/
public GetADUsers(): SearchResult | null {
return null;
}
/**
* GetADActiveUsers returns all AD users
* using FilterIsPerson and FilterAccountEnabled filter query
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
* const users = client.GetADActiveUsers();
* log(to_json(users));
* ```
*/
public GetADActiveUsers(): SearchResult | null {
return null;
}
/**
* GetAdUserWithNeverExpiringPasswords returns all AD users
* using FilterIsPerson and FilterDontExpirePassword filter query
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
* const users = client.GetADUserWithNeverExpiringPasswords();
* log(to_json(users));
* ```
*/
public GetADUserWithNeverExpiringPasswords(): SearchResult | null {
return null;
}
/**
* GetADUserTrustedForDelegation returns all AD users that are trusted for delegation
* using FilterIsPerson and FilterTrustedForDelegation filter query
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
* const users = client.GetADUserTrustedForDelegation();
* log(to_json(users));
* ```
*/
public GetADUserTrustedForDelegation(): SearchResult | null {
return null;
}
/**
* GetADUserWithPasswordNotRequired returns all AD users that do not require a password
* using FilterIsPerson and FilterPasswordNotRequired filter query
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
* const users = client.GetADUserWithPasswordNotRequired();
* log(to_json(users));
* ```
*/
public GetADUserWithPasswordNotRequired(): SearchResult | null {
return null;
}
/**
* GetADGroups returns all AD groups
* using FilterIsGroup filter query
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
* const groups = client.GetADGroups();
* log(to_json(groups));
* ```
*/
public GetADGroups(): SearchResult | null {
return null;
}
/**
* GetADDCList returns all AD domain controllers
* using FilterIsComputer, FilterAccountEnabled and FilterServerTrustAccount filter query
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
* const dcs = client.GetADDCList();
* log(to_json(dcs));
* ```
*/
public GetADDCList(): SearchResult | null {
return null;
}
/**
* GetADAdmins returns all AD admins
* using FilterIsPerson, FilterAccountEnabled and FilterIsAdmin filter query
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
* const admins = client.GetADAdmins();
* log(to_json(admins));
* ```
*/
public GetADAdmins(): SearchResult | null {
return null;
}
/**
* GetADUserKerberoastable returns all AD users that are kerberoastable
* using FilterIsPerson, FilterAccountEnabled and FilterHasServicePrincipalName filter query
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
* const kerberoastable = client.GetADUserKerberoastable();
* log(to_json(kerberoastable));
* ```
*/
public GetADUserKerberoastable(): SearchResult | null {
return null;
}
/**
* GetADUserAsRepRoastable returns all AD users that are AsRepRoastable
* using FilterIsPerson, and FilterDontRequirePreauth filter query
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
* const AsRepRoastable = client.GetADUserAsRepRoastable();
* log(to_json(AsRepRoastable));
* ```
*/
public GetADUserAsRepRoastable(): SearchResult | null {
return null;
}
/**
* GetADDomainSID returns the SID of the AD domain
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
* const domainSID = client.GetADDomainSID();
* log(domainSID);
* ```
*/
public GetADDomainSID(): string {
return "";
}
/**
* Authenticate authenticates with the ldap server using the given username and password
* performs NTLMBind first and then Bind/UnauthenticatedBind if NTLMBind fails
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
* client.Authenticate('user', 'password');
* ```
*/
public Authenticate(username: string): void {
return;
}
/**
* AuthenticateWithNTLMHash authenticates with the ldap server using the given username and NTLM hash
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
* client.AuthenticateWithNTLMHash('pdtm', 'hash');
* ```
*/
public AuthenticateWithNTLMHash(username: string): void {
return;
}
/**
* Search accepts whatever filter and returns a list of maps having provided attributes
* as keys and associated values mirroring the ones returned by ldap
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
* const results = client.Search('(objectClass=*)', 'cn', 'mail');
* ```
*/
public Search(filter: string, attributes: any): SearchResult | null {
return null;
}
/**
* AdvancedSearch accepts all values of search request type and return Ldap Entry
* its up to user to handle the response
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
* const results = client.AdvancedSearch(ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, '(objectClass=*)', ['cn', 'mail'], []);
* ```
*/
public AdvancedSearch(Scope: number, TypesOnly: boolean, Filter: string, Attributes: string[], Controls: any): SearchResult | null {
return null;
}
/**
* CollectLdapMetadata collects metadata from ldap server.
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
* const metadata = client.CollectMetadata();
* log(to_json(metadata));
* ```
*/
public CollectMetadata(): Metadata | null {
return null;
}
/**
* close the ldap connection
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
* client.Close();
* ```
*/
public Close(): void {
return;
}
}
/**
* Config is extra configuration for the ldap client
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const cfg = new ldap.Config();
* cfg.Timeout = 10;
* cfg.ServerName = 'ldap.internal.acme.com';
* cfg.Upgrade = true; // upgrade to tls
* ```
*/
export interface Config {
/**
* Timeout is the timeout for the ldap client in seconds
*/
Timeout?: number,
ServerName?: string,
Upgrade?: boolean,
}
/**
* LdapAttributes represents all LDAP attributes of a particular
* ldap entry
*/
export interface LdapAttributes {
/**
* CurrentTime contains current time
*/
CurrentTime?: string[],
/**
* SubschemaSubentry contains subschema subentry
*/
SubschemaSubentry?: string[],
/**
* DsServiceName contains ds service name
*/
DsServiceName?: string[],
/**
* NamingContexts contains naming contexts
*/
NamingContexts?: string[],
/**
* DefaultNamingContext contains default naming context
*/
DefaultNamingContext?: string[],
/**
* SchemaNamingContext contains schema naming context
*/
SchemaNamingContext?: string[],
/**
* ConfigurationNamingContext contains configuration naming context
*/
ConfigurationNamingContext?: string[],
/**
* RootDomainNamingContext contains root domain naming context
*/
RootDomainNamingContext?: string[],
/**
* SupportedLDAPVersion contains supported LDAP version
*/
SupportedLDAPVersion?: string[],
/**
* HighestCommittedUSN contains highest committed USN
*/
HighestCommittedUSN?: string[],
/**
* SupportedSASLMechanisms contains supported SASL mechanisms
*/
SupportedSASLMechanisms?: string[],
/**
* DnsHostName contains DNS host name
*/
DnsHostName?: string[],
/**
* LdapServiceName contains LDAP service name
*/
LdapServiceName?: string[],
/**
* ServerName contains server name
*/
ServerName?: string[],
/**
* IsSynchronized contains is synchronized
*/
IsSynchronized?: string[],
/**
* IsGlobalCatalogReady contains is global catalog ready
*/
IsGlobalCatalogReady?: string[],
/**
* DomainFunctionality contains domain functionality
*/
DomainFunctionality?: string[],
/**
* ForestFunctionality contains forest functionality
*/
ForestFunctionality?: string[],
/**
* DomainControllerFunctionality contains domain controller functionality
*/
DomainControllerFunctionality?: string[],
/**
* DistinguishedName contains the distinguished name
*/
DistinguishedName?: string[],
/**
* SAMAccountName contains the SAM account name
*/
SAMAccountName?: string[],
/**
* PWDLastSet contains the password last set time
*/
PWDLastSet?: string[],
/**
* LastLogon contains the last logon time
*/
LastLogon?: string[],
/**
* MemberOf contains the groups the entry is a member of
*/
MemberOf?: string[],
/**
* ServicePrincipalName contains the service principal names
*/
ServicePrincipalName?: string[],
/**
* Extra contains other extra fields which might be present
*/
Extra?: Record,
}
/**
* LdapEntry represents a single LDAP entry
*/
export interface LdapEntry {
/**
* DN contains distinguished name
*/
DN?: string,
/**
* Attributes contains list of attributes
*/
Attributes?: LdapAttributes,
}
/**
* Metadata is the metadata for ldap server.
* this is returned by CollectMetadata method
*/
export interface Metadata {
BaseDN?: string,
Domain?: string,
DefaultNamingContext?: string,
DomainFunctionality?: string,
ForestFunctionality?: string,
DomainControllerFunctionality?: string,
DnsHostName?: string,
}
/**
* SearchResult contains search result of any / all ldap search request
* @example
* ```javascript
* const ldap = require('nuclei/ldap');
* const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
* const results = client.Search('(objectinterface=*)', 'cn', 'mail');
* ```
*/
export interface SearchResult {
/**
* Referrals contains list of referrals
*/
Referrals?: string[],
/**
* Controls contains list of controls
*/
Controls?: string[],
/**
* Entries contains list of entries
*/
Entries?: LdapEntry[],
}
================================================
FILE: pkg/js/generated/ts/mssql.ts
================================================
/**
* Client is a client for MS SQL database.
* Internally client uses microsoft/go-mssqldb driver.
* @example
* ```javascript
* const mssql = require('nuclei/mssql');
* const client = new mssql.MSSQLClient;
* ```
*/
export class MSSQLClient {
// Constructor of MSSQLClient
constructor() {}
/**
* Connect connects to MS SQL database using given credentials.
* If connection is successful, it returns true.
* If connection is unsuccessful, it returns false and error.
* The connection is closed after the function returns.
* @example
* ```javascript
* const mssql = require('nuclei/mssql');
* const client = new mssql.MSSQLClient;
* const connected = client.Connect('acme.com', 1433, 'username', 'password');
* ```
*/
public Connect(host: string, port: number, username: string): boolean | null {
return null;
}
/**
* ConnectWithDB connects to MS SQL database using given credentials and database name.
* If connection is successful, it returns true.
* If connection is unsuccessful, it returns false and error.
* The connection is closed after the function returns.
* @example
* ```javascript
* const mssql = require('nuclei/mssql');
* const client = new mssql.MSSQLClient;
* const connected = client.ConnectWithDB('acme.com', 1433, 'username', 'password', 'master');
* ```
*/
public ConnectWithDB(host: string, port: number, username: string): boolean | null {
return null;
}
/**
* IsMssql checks if the given host is running MS SQL database.
* If the host is running MS SQL database, it returns true.
* If the host is not running MS SQL database, it returns false.
* @example
* ```javascript
* const mssql = require('nuclei/mssql');
* const isMssql = mssql.IsMssql('acme.com', 1433);
* ```
*/
public IsMssql(host: string, port: number): boolean | null {
return null;
}
/**
* ExecuteQuery connects to MS SQL database using given credentials and executes a query.
* It returns the results of the query or an error if something goes wrong.
* @example
* ```javascript
* const mssql = require('nuclei/mssql');
* const client = new mssql.MSSQLClient;
* const result = client.ExecuteQuery('acme.com', 1433, 'username', 'password', 'master', 'SELECT @@version');
* log(to_json(result));
* ```
*/
public ExecuteQuery(host: string, port: number, username: string): SQLResult | null | null {
return null;
}
}
/**
* SQLResult Interface
*/
export interface SQLResult {
Count?: number,
Columns?: string[],
}
================================================
FILE: pkg/js/generated/ts/mysql.ts
================================================
/**
* BuildDSN builds a MySQL data source name (DSN) from the given options.
* @example
* ```javascript
* const mysql = require('nuclei/mysql');
* const options = new mysql.MySQLOptions();
* options.Host = 'acme.com';
* options.Port = 3306;
* const dsn = mysql.BuildDSN(options);
* ```
*/
export function BuildDSN(opts: MySQLOptions): string | null {
return null;
}
/**
* MySQLClient is a client for MySQL database.
* Internally client uses go-sql-driver/mysql driver.
* @example
* ```javascript
* const mysql = require('nuclei/mysql');
* const client = new mysql.MySQLClient;
* ```
*/
export class MySQLClient {
// Constructor of MySQLClient
constructor() {}
/**
* IsMySQL checks if the given host is running MySQL database.
* If the host is running MySQL database, it returns true.
* If the host is not running MySQL database, it returns false.
* @example
* ```javascript
* const mysql = require('nuclei/mysql');
* const isMySQL = mysql.IsMySQL('acme.com', 3306);
* ```
*/
public IsMySQL(host: string, port: number): boolean | null {
return null;
}
/**
* Connect connects to MySQL database using given credentials.
* If connection is successful, it returns true.
* If connection is unsuccessful, it returns false and error.
* The connection is closed after the function returns.
* @example
* ```javascript
* const mysql = require('nuclei/mysql');
* const client = new mysql.MySQLClient;
* const connected = client.Connect('acme.com', 3306, 'username', 'password');
* ```
*/
public Connect(host: string, port: number, username: string): boolean | null {
return null;
}
/**
* returns MySQLInfo when fingerprint is successful
* @example
* ```javascript
* const mysql = require('nuclei/mysql');
* const info = mysql.FingerprintMySQL('acme.com', 3306);
* log(to_json(info));
* ```
*/
public FingerprintMySQL(host: string, port: number): MySQLInfo | null {
return null;
}
/**
* ConnectWithDSN connects to MySQL database using given DSN.
* we override mysql dialer with fastdialer so it respects network policy
* If connection is successful, it returns true.
* @example
* ```javascript
* const mysql = require('nuclei/mysql');
* const client = new mysql.MySQLClient;
* const connected = client.ConnectWithDSN('username:password@tcp(acme.com:3306)/');
* ```
*/
public ConnectWithDSN(dsn: string): boolean | null {
return null;
}
/**
* ExecuteQueryWithOpts connects to Mysql database using given credentials
* and executes a query on the db.
* @example
* ```javascript
* const mysql = require('nuclei/mysql');
* const options = new mysql.MySQLOptions();
* options.Host = 'acme.com';
* options.Port = 3306;
* const result = mysql.ExecuteQueryWithOpts(options, 'SELECT * FROM users');
* log(to_json(result));
* ```
*/
public ExecuteQueryWithOpts(opts: MySQLOptions, query: string): SQLResult | null | null {
return null;
}
/**
* ExecuteQuery connects to Mysql database using given credentials
* and executes a query on the db.
* @example
* ```javascript
* const mysql = require('nuclei/mysql');
* const result = mysql.ExecuteQuery('acme.com', 3306, 'username', 'password', 'SELECT * FROM users');
* log(to_json(result));
* ```
*/
public ExecuteQuery(host: string, port: number, username: string): SQLResult | null | null {
return null;
}
/**
* ExecuteQuery connects to Mysql database using given credentials
* and executes a query on the db.
* @example
* ```javascript
* const mysql = require('nuclei/mysql');
* const result = mysql.ExecuteQueryOnDB('acme.com', 3306, 'username', 'password', 'dbname', 'SELECT * FROM users');
* log(to_json(result));
* ```
*/
public ExecuteQueryOnDB(host: string, port: number, username: string): SQLResult | null | null {
return null;
}
}
/**
* MySQLInfo contains information about MySQL server.
* this is returned when fingerprint is successful
*/
export interface MySQLInfo {
Host?: string,
IP?: string,
Port?: number,
Protocol?: string,
TLS?: boolean,
Transport?: string,
Version?: string,
Debug?: ServiceMySQL,
Raw?: string,
}
/**
* MySQLOptions defines the data source name (DSN) options required to connect to a MySQL database.
* along with other options like Timeout etc
* @example
* ```javascript
* const mysql = require('nuclei/mysql');
* const options = new mysql.MySQLOptions();
* options.Host = 'acme.com';
* options.Port = 3306;
* ```
*/
export interface MySQLOptions {
Host?: string,
Port?: number,
Protocol?: string,
Username?: string,
Password?: string,
DbName?: string,
RawQuery?: string,
Timeout?: number,
}
/**
* SQLResult Interface
*/
export interface SQLResult {
Count?: number,
Columns?: string[],
}
/**
* ServiceMySQL Interface
*/
export interface ServiceMySQL {
PacketType?: string,
ErrorMessage?: string,
ErrorCode?: number,
}
================================================
FILE: pkg/js/generated/ts/net.ts
================================================
/**
* Open opens a new connection to the address with a timeout.
* supported protocols: tcp, udp
* @example
* ```javascript
* const net = require('nuclei/net');
* const conn = net.Open('tcp', 'acme.com:80');
* ```
*/
export function Open(protocol: string): NetConn | null {
return null;
}
/**
* Open opens a new connection to the address with a timeout.
* supported protocols: tcp, udp
* @example
* ```javascript
* const net = require('nuclei/net');
* const conn = net.OpenTLS('tcp', 'acme.com:443');
* ```
*/
export function OpenTLS(protocol: string): NetConn | null {
return null;
}
/**
* NetConn is a connection to a remote host.
* this is returned/create by Open and OpenTLS functions.
* @example
* ```javascript
* const net = require('nuclei/net');
* const conn = net.Open('tcp', 'acme.com:80');
* ```
*/
export class NetConn {
// Constructor of NetConn
constructor() {}
/**
* Close closes the connection.
* @example
* ```javascript
* const net = require('nuclei/net');
* const conn = net.Open('tcp', 'acme.com:80');
* conn.Close();
* ```
*/
public Close(): void {
return;
}
/**
* SetTimeout sets read/write timeout for the connection (in seconds).
* @example
* ```javascript
* const net = require('nuclei/net');
* const conn = net.Open('tcp', 'acme.com:80');
* conn.SetTimeout(10);
* ```
*/
public SetTimeout(value: number): void {
return;
}
/**
* SendArray sends array data to connection
* @example
* ```javascript
* const net = require('nuclei/net');
* const conn = net.Open('tcp', 'acme.com:80');
* conn.SendArray(['hello', 'world']);
* ```
*/
public SendArray(data: any): void {
return;
}
/**
* SendHex sends hex data to connection
* @example
* ```javascript
* const net = require('nuclei/net');
* const conn = net.Open('tcp', 'acme.com:80');
* conn.SendHex('68656c6c6f');
* ```
*/
public SendHex(data: string): void {
return;
}
/**
* Send sends data to the connection with a timeout.
* @example
* ```javascript
* const net = require('nuclei/net');
* const conn = net.Open('tcp', 'acme.com:80');
* conn.Send('hello');
* ```
*/
public Send(data: string): void {
return;
}
/**
* RecvFull receives data from the connection with a timeout.
* If N is 0, it will read all data sent by the server with 8MB limit.
* it tries to read until N bytes or timeout is reached.
* @example
* ```javascript
* const net = require('nuclei/net');
* const conn = net.Open('tcp', 'acme.com:80');
* const data = conn.RecvFull(1024);
* ```
*/
public RecvFull(N: number): Uint8Array | null {
return null;
}
/**
* Recv is similar to RecvFull but does not guarantee full read instead
* it creates a buffer of N bytes and returns whatever is returned by the connection
* for reading headers or initial bytes from the server this is usually used.
* for reading a fixed number of already known bytes (ex: body based on content-length) use RecvFull.
* @example
* ```javascript
* const net = require('nuclei/net');
* const conn = net.Open('tcp', 'acme.com:80');
* const data = conn.Recv(1024);
* log(`Received ${data.length} bytes from the server`)
* ```
*/
public Recv(N: number): Uint8Array | null {
return null;
}
/**
* RecvFullString receives data from the connection with a timeout
* output is returned as a string.
* If N is 0, it will read all data sent by the server with 8MB limit.
* @example
* ```javascript
* const net = require('nuclei/net');
* const conn = net.Open('tcp', 'acme.com:80');
* const data = conn.RecvFullString(1024);
* ```
*/
public RecvFullString(N: number): string | null {
return null;
}
/**
* RecvString is similar to RecvFullString but does not guarantee full read, instead
* it creates a buffer of N bytes and returns whatever is returned by the connection
* for reading headers or initial bytes from the server this is usually used.
* for reading a fixed number of already known bytes (ex: body based on content-length) use RecvFullString.
* @example
* ```javascript
* const net = require('nuclei/net');
* const conn = net.Open('tcp', 'acme.com:80');
* const data = conn.RecvString(1024);
* ```
*/
public RecvString(N: number): string | null {
return null;
}
/**
* RecvFullHex receives data from the connection with a timeout
* in hex format.
* If N is 0,it will read all data sent by the server with 8MB limit.
* until N bytes or timeout is reached.
* @example
* ```javascript
* const net = require('nuclei/net');
* const conn = net.Open('tcp', 'acme.com:80');
* const data = conn.RecvFullHex(1024);
* ```
*/
public RecvFullHex(N: number): string | null {
return null;
}
/**
* RecvHex is similar to RecvFullHex but does not guarantee full read instead
* it creates a buffer of N bytes and returns whatever is returned by the connection
* for reading headers or initial bytes from the server this is usually used.
* for reading a fixed number of already known bytes (ex: body based on content-length) use RecvFull.
* @example
* ```javascript
* const net = require('nuclei/net');
* const conn = net.Open('tcp', 'acme.com:80');
* const data = conn.RecvHex(1024);
* ```
*/
public RecvHex(N: number): string | null {
return null;
}
}
================================================
FILE: pkg/js/generated/ts/oracle.ts
================================================
/**
* IsOracleResponse is the response from the IsOracle function.
* this is returned by IsOracle function.
* @example
* ```javascript
* const oracle = require('nuclei/oracle');
* const client = new oracle.OracleClient();
* const isOracle = client.IsOracle('acme.com', 1521);
* ```
*/
export interface IsOracleResponse {
IsOracle?: boolean,
Banner?: string,
}
/**
* Client is a client for Oracle database.
* Internally client uses go-ora driver.
* @example
* ```javascript
* const oracle = require('nuclei/oracle');
* const client = new oracle.OracleClient();
* ```
*/
export class OracleClient {
// Constructor of OracleClient
constructor() {}
/**
* Connect connects to an Oracle database
* @example
* ```javascript
* const oracle = require('nuclei/oracle');
* const client = new oracle.OracleClient();
* client.Connect('acme.com', 1521, 'XE', 'user', 'password');
* ```
*/
public Connect(host: string, port: number, serviceName: string, username: string, password: string): boolean | null {
return null;
}
/**
* ConnectWithDSN connects to an Oracle database using a DSN string
* @example
* ```javascript
* const oracle = require('nuclei/oracle');
* const client = new oracle.OracleClient();
* client.ConnectWithDSN('oracle://user:password@host:port/service', 'SELECT @@version');
* ```
*/
public ConnectWithDSN(dsn: string): boolean | null {
return null;
}
/**
* IsOracle checks if a host is running an Oracle server
* @example
* ```javascript
* const oracle = require('nuclei/oracle');
* const isOracle = oracle.IsOracle('acme.com', 1521);
* ```
*/
public IsOracle(host: string, port: number): IsOracleResponse | null {
return null;
}
/**
* ExecuteQuery connects to Oracle database using given credentials and executes a query.
* It returns the results of the query or an error if something goes wrong.
* @example
* ```javascript
* const oracle = require('nuclei/oracle');
* const client = new oracle.OracleClient();
* const result = client.ExecuteQuery('acme.com', 1521, 'username', 'password', 'XE', 'SELECT * FROM dual');
* log(to_json(result));
* ```
*/
public ExecuteQuery(host: string, port: number, username: string, password: string, dbName: string, query: string): SQLResult | null {
return null;
}
/**
* ExecuteQueryWithDSN executes a query on an Oracle database using a DSN
* @example
* ```javascript
* const oracle = require('nuclei/oracle');
* const client = new oracle.OracleClient();
* const result = client.ExecuteQueryWithDSN('oracle://user:password@host:port/service', 'SELECT * FROM dual');
* log(to_json(result));
* ```
*/
public ExecuteQueryWithDSN(dsn: string, query: string): SQLResult | null {
return null;
}
}
/**
* SQLResult Interface
*/
export interface SQLResult {
Count?: number,
Columns?: string[],
Rows?: any[],
}
================================================
FILE: pkg/js/generated/ts/pop3.ts
================================================
/**
* IsPOP3 checks if a host is running a POP3 server.
* @example
* ```javascript
* const pop3 = require('nuclei/pop3');
* const isPOP3 = pop3.IsPOP3('acme.com', 110);
* log(toJSON(isPOP3));
* ```
*/
export function IsPOP3(host: string, port: number): IsPOP3Response | null {
return null;
}
/**
* IsPOP3Response is the response from the IsPOP3 function.
* this is returned by IsPOP3 function.
* @example
* ```javascript
* const pop3 = require('nuclei/pop3');
* const isPOP3 = pop3.IsPOP3('acme.com', 110);
* log(toJSON(isPOP3));
* ```
*/
export interface IsPOP3Response {
IsPOP3?: boolean,
Banner?: string,
}
================================================
FILE: pkg/js/generated/ts/postgres.ts
================================================
/**
* PGClient is a client for Postgres database.
* Internally client uses go-pg/pg driver.
* @example
* ```javascript
* const postgres = require('nuclei/postgres');
* const client = new postgres.PGClient;
* ```
*/
export class PGClient {
// Constructor of PGClient
constructor() {}
/**
* IsPostgres checks if the given host and port are running Postgres database.
* If connection is successful, it returns true.
* If connection is unsuccessful, it returns false and error.
* @example
* ```javascript
* const postgres = require('nuclei/postgres');
* const isPostgres = postgres.IsPostgres('acme.com', 5432);
* ```
*/
public IsPostgres(host: string, port: number): boolean | null {
return null;
}
/**
* Connect connects to Postgres database using given credentials.
* If connection is successful, it returns true.
* If connection is unsuccessful, it returns false and error.
* The connection is closed after the function returns.
* @example
* ```javascript
* const postgres = require('nuclei/postgres');
* const client = new postgres.PGClient;
* const connected = client.Connect('acme.com', 5432, 'username', 'password');
* ```
*/
public Connect(host: string, port: number, username: string): boolean | null {
return null;
}
/**
* ExecuteQuery connects to Postgres database using given credentials and database name.
* and executes a query on the db.
* If connection is successful, it returns the result of the query.
* @example
* ```javascript
* const postgres = require('nuclei/postgres');
* const client = new postgres.PGClient;
* const result = client.ExecuteQuery('acme.com', 5432, 'username', 'password', 'dbname', 'select * from users');
* log(to_json(result));
* ```
*/
public ExecuteQuery(host: string, port: number, username: string): SQLResult | null | null {
return null;
}
/**
* ConnectWithDB connects to Postgres database using given credentials and database name.
* If connection is successful, it returns true.
* If connection is unsuccessful, it returns false and error.
* The connection is closed after the function returns.
* @example
* ```javascript
* const postgres = require('nuclei/postgres');
* const client = new postgres.PGClient;
* const connected = client.ConnectWithDB('acme.com', 5432, 'username', 'password', 'dbname');
* ```
*/
public ConnectWithDB(host: string, port: number, username: string): boolean | null {
return null;
}
}
/**
* SQLResult Interface
*/
export interface SQLResult {
Count?: number,
Columns?: string[],
}
================================================
FILE: pkg/js/generated/ts/rdp.ts
================================================
/**
* CheckRDPAuth checks if the given host and port are running rdp server
* with authentication and returns their metadata.
* If connection is successful, it returns true.
* @example
* ```javascript
* const rdp = require('nuclei/rdp');
* const checkRDPAuth = rdp.CheckRDPAuth('acme.com', 3389);
* log(toJSON(checkRDPAuth));
* ```
*/
export function CheckRDPAuth(host: string, port: number): CheckRDPAuthResponse | null {
return null;
}
/**
* CheckRDPEncryption checks the RDP server's supported security layers and encryption levels.
* It tests different protocols and ciphers to determine what is supported.
* @example
* ```javascript
* const rdp = require('nuclei/rdp');
* const encryption = rdp.CheckRDPEncryption('acme.com', 3389);
* log(toJSON(encryption));
* ```
*/
export function CheckRDPEncryption(host: string, port: number): RDPEncryptionResponse | null {
return null;
}
/**
* IsRDP checks if the given host and port are running rdp server.
* If connection is successful, it returns true.
* If connection is unsuccessful, it returns false and error.
* The Name of the OS is also returned if the connection is successful.
* @example
* ```javascript
* const rdp = require('nuclei/rdp');
* const isRDP = rdp.IsRDP('acme.com', 3389);
* log(toJSON(isRDP));
* ```
*/
export function IsRDP(host: string, port: number): IsRDPResponse | null {
return null;
}
/**
* CheckRDPAuthResponse is the response from the CheckRDPAuth function.
* this is returned by CheckRDPAuth function.
* @example
* ```javascript
* const rdp = require('nuclei/rdp');
* const checkRDPAuth = rdp.CheckRDPAuth('acme.com', 3389);
* log(toJSON(checkRDPAuth));
* ```
*/
export interface CheckRDPAuthResponse {
PluginInfo?: ServiceRDP,
Auth?: boolean,
}
/**
* RDPEncryptionResponse is the response from the CheckRDPEncryption function.
* This is returned by CheckRDPEncryption function.
* @example
* ```javascript
* const rdp = require('nuclei/rdp');
* const encryption = rdp.CheckRDPEncryption('acme.com', 3389);
* log(toJSON(encryption));
* ```
*/
export interface RDPEncryptionResponse {
// Security Layer Protocols
NativeRDP: boolean;
SSL: boolean;
CredSSP: boolean;
RDSTLS: boolean;
CredSSPWithEarlyUserAuth: boolean;
// Encryption Levels
RC4_40bit: boolean;
RC4_56bit: boolean;
RC4_128bit: boolean;
FIPS140_1: boolean;
}
/**
* IsRDPResponse is the response from the IsRDP function.
* this is returned by IsRDP function.
* @example
* ```javascript
* const rdp = require('nuclei/rdp');
* const isRDP = rdp.IsRDP('acme.com', 3389);
* log(toJSON(isRDP));
* ```
*/
export interface IsRDPResponse {
IsRDP?: boolean,
OS?: string,
}
/**
* ServiceRDP Interface
*/
export interface ServiceRDP {
ForestName?: string,
OSFingerprint?: string,
OSVersion?: string,
TargetName?: string,
NetBIOSComputerName?: string,
NetBIOSDomainName?: string,
DNSComputerName?: string,
DNSDomainName?: string,
}
================================================
FILE: pkg/js/generated/ts/redis.ts
================================================
/**
* Connect tries to connect redis server with password
* @example
* ```javascript
* const redis = require('nuclei/redis');
* const connected = redis.Connect('acme.com', 6379, 'password');
* ```
*/
export function Connect(host: string, port: number, password: string): boolean | null {
return null;
}
/**
* GetServerInfo returns the server info for a redis server
* @example
* ```javascript
* const redis = require('nuclei/redis');
* const info = redis.GetServerInfo('acme.com', 6379);
* ```
*/
export function GetServerInfo(host: string, port: number): string | null {
return null;
}
/**
* GetServerInfoAuth returns the server info for a redis server
* @example
* ```javascript
* const redis = require('nuclei/redis');
* const info = redis.GetServerInfoAuth('acme.com', 6379, 'password');
* ```
*/
export function GetServerInfoAuth(host: string, port: number, password: string): string | null {
return null;
}
/**
* IsAuthenticated checks if the redis server requires authentication
* @example
* ```javascript
* const redis = require('nuclei/redis');
* const isAuthenticated = redis.IsAuthenticated('acme.com', 6379);
* ```
*/
export function IsAuthenticated(host: string, port: number): boolean | null {
return null;
}
/**
* RunLuaScript runs a lua script on the redis server
* @example
* ```javascript
* const redis = require('nuclei/redis');
* const result = redis.RunLuaScript('acme.com', 6379, 'password', 'return redis.call("get", KEYS[1])');
* ```
*/
export function RunLuaScript(host: string, port: number, password: string, script: string): any | null {
return null;
}
================================================
FILE: pkg/js/generated/ts/rsync.ts
================================================
/**
* IsRsync checks if a host is running a Rsync server.
* @example
* ```javascript
* const rsync = require('nuclei/rsync');
* const isRsync = rsync.IsRsync('acme.com', 873);
* log(toJSON(isRsync));
* ```
*/
export function IsRsync(host: string, port: number): IsRsyncResponse | null {
return null;
}
/**
* RsyncClient is a client for RSYNC servers.
* Internally client uses https://github.com/gokrazy/rsync driver.
* @example
* ```javascript
* const rsync = require('nuclei/rsync');
* const client = new rsync.RsyncClient();
* ```
*/
export class RsyncClient {
// Constructor of RsyncClient
constructor() {}
/**
* Connect establishes a connection to the rsync server with authentication.
* @example
* ```javascript
* const rsync = require('nuclei/rsync');
* const client = new rsync.RsyncClient();
* const connected = client.Connect('acme.com', 873, 'username', 'password', 'backup');
* ```
*/
public Connect(host: string, port: number, username: string, password: string, module: string): boolean | null {
return null;
}
/**
* ListModules lists available modules on the rsync server.
* @example
* ```javascript
* const rsync = require('nuclei/rsync');
* const client = new rsync.RsyncClient();
* const modules = client.ListModules('acme.com', 873, 'username', 'password');
* log(toJSON(modules));
* ```
*/
public ListModules(host: string, port: number, username: string, password: string): string[] | null {
return null;
}
/**
* ListFilesInModule lists files in a specific module on the rsync server.
* @example
* ```javascript
* const rsync = require('nuclei/rsync');
* const client = new rsync.RsyncClient();
* const files = client.ListFilesInModule('acme.com', 873, 'username', 'password', 'backup');
* log(toJSON(files));
* ```
*/
public ListFilesInModule(host: string, port: number, username: string, password: string, module: string): string[] | null {
return null;
}
}
/**
* IsRsyncResponse is the response from the IsRsync function.
* this is returned by IsRsync function.
* @example
* ```javascript
* const rsync = require('nuclei/rsync');
* const isRsync = rsync.IsRsync('acme.com', 873);
* log(toJSON(isRsync));
* ```
*/
export interface IsRsyncResponse {
IsRsync?: boolean,
Banner?: string,
}
================================================
FILE: pkg/js/generated/ts/smb.ts
================================================
/**
* SMBClient is a client for SMB servers.
* Internally client uses github.com/zmap/zgrab2/lib/smb/smb driver.
* github.com/projectdiscovery/go-smb2 driver
* @example
* ```javascript
* const smb = require('nuclei/smb');
* const client = new smb.SMBClient();
* ```
*/
export class SMBClient {
// Constructor of SMBClient
constructor() {}
/**
* ConnectSMBInfoMode tries to connect to provided host and port
* and discovery SMB information
* Returns handshake log and error. If error is not nil,
* state will be false
* @example
* ```javascript
* const smb = require('nuclei/smb');
* const client = new smb.SMBClient();
* const info = client.ConnectSMBInfoMode('acme.com', 445);
* log(to_json(info));
* ```
*/
public ConnectSMBInfoMode(host: string, port: number): SMBLog | null | null {
return null;
}
/**
* ListSMBv2Metadata tries to connect to provided host and port
* and list SMBv2 metadata.
* Returns metadata and error. If error is not nil,
* state will be false
* @example
* ```javascript
* const smb = require('nuclei/smb');
* const client = new smb.SMBClient();
* const metadata = client.ListSMBv2Metadata('acme.com', 445);
* log(to_json(metadata));
* ```
*/
public ListSMBv2Metadata(host: string, port: number): ServiceSMB | null | null {
return null;
}
/**
* ListShares tries to connect to provided host and port
* and list shares by using given credentials.
* Credentials cannot be blank. guest or anonymous credentials
* can be used by providing empty password.
* @example
* ```javascript
* const smb = require('nuclei/smb');
* const client = new smb.SMBClient();
* const shares = client.ListShares('acme.com', 445, 'username', 'password');
* for (const share of shares) {
* log(share);
* }
* ```
*/
public ListShares(host: string, port: number, user: string): string[] | null {
return null;
}
/**
* DetectSMBGhost tries to detect SMBGhost vulnerability
* by using SMBv3 compression feature.
* If the host is vulnerable, it returns true.
* @example
* ```javascript
* const smb = require('nuclei/smb');
* const isSMBGhost = smb.DetectSMBGhost('acme.com', 445);
* ```
*/
public DetectSMBGhost(host: string, port: number): boolean | null {
return null;
}
}
/**
* HeaderLog Interface
*/
export interface HeaderLog {
ProtocolID?: Uint8Array,
Status?: number,
Command?: number,
Credits?: number,
Flags?: number,
}
/**
* NegotiationLog Interface
*/
export interface NegotiationLog {
SecurityMode?: number,
DialectRevision?: number,
ServerGuid?: Uint8Array,
Capabilities?: number,
SystemTime?: number,
ServerStartTime?: number,
AuthenticationTypes?: string[],
HeaderLog?: HeaderLog,
}
/**
* SMBCapabilities Interface
*/
export interface SMBCapabilities {
DFSSupport?: boolean,
Leasing?: boolean,
LargeMTU?: boolean,
MultiChan?: boolean,
Persist?: boolean,
DirLeasing?: boolean,
Encryption?: boolean,
}
/**
* SMBLog Interface
*/
export interface SMBLog {
NTLM?: string,
GroupName?: string,
HasNTLM?: boolean,
SupportV1?: boolean,
NativeOs?: string,
Version?: SMBVersions,
Capabilities?: SMBCapabilities,
NegotiationLog?: NegotiationLog,
SessionSetupLog?: SessionSetupLog,
}
/**
* SMBVersions Interface
*/
export interface SMBVersions {
Major?: number,
Minor?: number,
Revision?: number,
VerString?: string,
}
/**
* ServiceSMB Interface
*/
export interface ServiceSMB {
OSVersion?: string,
NetBIOSComputerName?: string,
NetBIOSDomainName?: string,
DNSComputerName?: string,
DNSDomainName?: string,
ForestName?: string,
SigningEnabled?: boolean,
SigningRequired?: boolean,
}
/**
* SessionSetupLog Interface
*/
export interface SessionSetupLog {
SetupFlags?: number,
TargetName?: string,
NegotiateFlags?: number,
HeaderLog?: HeaderLog,
}
================================================
FILE: pkg/js/generated/ts/smtp.ts
================================================
/**
* Client is a minimal SMTP client for nuclei scripts.
* @example
* ```javascript
* const smtp = require('nuclei/smtp');
* const client = new smtp.Client('acme.com', 25);
* ```
*/
export class Client {
// Constructor of Client
constructor(public host: string, public port: string ) {}
/**
* IsSMTP checks if a host is running a SMTP server.
* @example
* ```javascript
* const smtp = require('nuclei/smtp');
* const client = new smtp.Client('acme.com', 25);
* const isSMTP = client.IsSMTP();
* log(isSMTP)
* ```
*/
public IsSMTP(): SMTPResponse | null {
return null;
}
/**
* IsOpenRelay checks if a host is an open relay.
* @example
* ```javascript
* const smtp = require('nuclei/smtp');
* const message = new smtp.SMTPMessage();
* message.From('xyz@projectdiscovery.io');
* message.To('xyz2@projectdiscoveyr.io');
* message.Subject('hello');
* message.Body('hello');
* const client = new smtp.Client('acme.com', 25);
* const isRelay = client.IsOpenRelay(message);
* ```
*/
public IsOpenRelay(msg: SMTPMessage): boolean | null {
return null;
}
/**
* SendMail sends an email using the SMTP protocol.
* @example
* ```javascript
* const smtp = require('nuclei/smtp');
* const message = new smtp.SMTPMessage();
* message.From('xyz@projectdiscovery.io');
* message.To('xyz2@projectdiscoveyr.io');
* message.Subject('hello');
* message.Body('hello');
* const client = new smtp.Client('acme.com', 25);
* const isSent = client.SendMail(message);
* log(isSent)
* ```
*/
public SendMail(msg: SMTPMessage): boolean | null {
return null;
}
}
/**
* SMTPMessage is a message to be sent over SMTP
* @example
* ```javascript
* const smtp = require('nuclei/smtp');
* const message = new smtp.SMTPMessage();
* message.From('xyz@projectdiscovery.io');
* ```
*/
export class SMTPMessage {
// Constructor of SMTPMessage
constructor() {}
/**
* From adds the from field to the message
* @example
* ```javascript
* const smtp = require('nuclei/smtp');
* const message = new smtp.SMTPMessage();
* message.From('xyz@projectdiscovery.io');
* ```
*/
public From(email: string): SMTPMessage {
return this;
}
/**
* To adds the to field to the message
* @example
* ```javascript
* const smtp = require('nuclei/smtp');
* const message = new smtp.SMTPMessage();
* message.To('xyz@projectdiscovery.io');
* ```
*/
public To(email: string): SMTPMessage {
return this;
}
/**
* Subject adds the subject field to the message
* @example
* ```javascript
* const smtp = require('nuclei/smtp');
* const message = new smtp.SMTPMessage();
* message.Subject('hello');
* ```
*/
public Subject(sub: string): SMTPMessage {
return this;
}
/**
* Body adds the message body to the message
* @example
* ```javascript
* const smtp = require('nuclei/smtp');
* const message = new smtp.SMTPMessage();
* message.Body('hello');
* ```
*/
public Body(msg: Uint8Array): SMTPMessage {
return this;
}
/**
* Auth when called authenticates using username and password before sending the message
* @example
* ```javascript
* const smtp = require('nuclei/smtp');
* const message = new smtp.SMTPMessage();
* message.Auth('username', 'password');
* ```
*/
public Auth(username: string): SMTPMessage {
return this;
}
/**
* String returns the string representation of the message
* @example
* ```javascript
* const smtp = require('nuclei/smtp');
* const message = new smtp.SMTPMessage();
* message.From('xyz@projectdiscovery.io');
* message.To('xyz2@projectdiscoveyr.io');
* message.Subject('hello');
* message.Body('hello');
* log(message.String());
* ```
*/
public String(): string {
return "";
}
}
/**
* SMTPResponse is the response from the IsSMTP function.
* @example
* ```javascript
* const smtp = require('nuclei/smtp');
* const client = new smtp.Client('acme.com', 25);
* const isSMTP = client.IsSMTP();
* log(isSMTP)
* ```
*/
export interface SMTPResponse {
IsSMTP?: boolean,
Banner?: string,
}
================================================
FILE: pkg/js/generated/ts/ssh.ts
================================================
/**
* SSHClient is a client for SSH servers.
* Internally client uses github.com/zmap/zgrab2/lib/ssh driver.
* @example
* ```javascript
* const ssh = require('nuclei/ssh');
* const client = new ssh.SSHClient();
* ```
*/
export class SSHClient {
// Constructor of SSHClient
constructor() {}
/**
* SetTimeout sets the timeout for the SSH connection in seconds
* @example
* ```javascript
* const ssh = require('nuclei/ssh');
* const client = new ssh.SSHClient();
* client.SetTimeout(10);
* ```
*/
public SetTimeout(sec: number): void {
return;
}
/**
* Connect tries to connect to provided host and port
* with provided username and password with ssh.
* Returns state of connection and error. If error is not nil,
* state will be false
* @example
* ```javascript
* const ssh = require('nuclei/ssh');
* const client = new ssh.SSHClient();
* const connected = client.Connect('acme.com', 22, 'username', 'password');
* ```
*/
public Connect(host: string, port: number, username: string): boolean | null {
return null;
}
/**
* ConnectWithKey tries to connect to provided host and port
* with provided username and private_key.
* Returns state of connection and error. If error is not nil,
* state will be false
* @example
* ```javascript
* const ssh = require('nuclei/ssh');
* const client = new ssh.SSHClient();
* const privateKey = `-----BEGIN RSA PRIVATE KEY----- ...`;
* const connected = client.ConnectWithKey('acme.com', 22, 'username', privateKey);
* ```
*/
public ConnectWithKey(host: string, port: number, username: string): boolean | null {
return null;
}
/**
* ConnectSSHInfoMode tries to connect to provided host and port
* with provided host and port
* Returns HandshakeLog and error. If error is not nil,
* state will be false
* HandshakeLog is a struct that contains information about the
* ssh connection
* @example
* ```javascript
* const ssh = require('nuclei/ssh');
* const client = new ssh.SSHClient();
* const info = client.ConnectSSHInfoMode('acme.com', 22);
* log(to_json(info));
* ```
*/
public ConnectSSHInfoMode(host: string, port: number): HandshakeLog | null | null {
return null;
}
/**
* Run tries to open a new SSH session, then tries to execute
* the provided command in said session
* Returns string and error. If error is not nil,
* state will be false
* The string contains the command output
* @example
* ```javascript
* const ssh = require('nuclei/ssh');
* const client = new ssh.SSHClient();
* client.Connect('acme.com', 22, 'username', 'password');
* const output = client.Run('id');
* log(output);
* ```
*/
public Run(cmd: string): string | null {
return null;
}
/**
* Close closes the SSH connection and destroys the client
* Returns the success state and error. If error is not nil,
* state will be false
* @example
* ```javascript
* const ssh = require('nuclei/ssh');
* const client = new ssh.SSHClient();
* client.Connect('acme.com', 22, 'username', 'password');
* const closed = client.Close();
* ```
*/
public Close(): boolean | null {
return null;
}
}
/**
* Algorithms Interface
*/
export interface Algorithms {
Kex?: string,
HostKey?: string,
W?: DirectionAlgorithms,
R?: DirectionAlgorithms,
}
/**
* DirectionAlgorithms Interface
*/
export interface DirectionAlgorithms {
Cipher?: string,
MAC?: string,
Compression?: string,
}
/**
* EndpointId Interface
*/
export interface EndpointId {
SoftwareVersion?: string,
Comment?: string,
Raw?: string,
ProtoVersion?: string,
}
/**
* HandshakeLog Interface
*/
export interface HandshakeLog {
Banner?: string,
UserAuth?: string[],
ServerID?: EndpointId,
ClientID?: EndpointId,
ServerKex?: KexInitMsg,
ClientKex?: KexInitMsg,
AlgorithmSelection?: Algorithms,
}
/**
* KexInitMsg Interface
*/
export interface KexInitMsg {
KexAlgos?: string[],
CiphersClientServer?: string[],
MACsServerClient?: string[],
LanguagesClientServer?: string[],
CompressionClientServer?: string[],
CompressionServerClient?: string[],
Reserved?: number,
MACsClientServer?: string[],
/**
* fixed size array of length: [16]
*/
Cookie?: Uint8Array,
ServerHostKeyAlgos?: string[],
CiphersServerClient?: string[],
LanguagesServerClient?: string[],
FirstKexFollows?: boolean,
}
================================================
FILE: pkg/js/generated/ts/structs.ts
================================================
/**
* StructsPack returns a byte slice containing the values of msg slice packed according to the given format.
* The items of msg slice must match the values required by the format exactly.
* Ex: structs.pack("H", 0)
* @example
* ```javascript
* const structs = require('nuclei/structs');
* const packed = structs.Pack('H', [0]);
* ```
*/
export function Pack(formatStr: string, msg: any): Uint8Array | null {
return null;
}
/**
* StructsCalcSize returns the number of bytes needed to pack the values according to the given format.
* Ex: structs.CalcSize("H")
* @example
* ```javascript
* const structs = require('nuclei/structs');
* const size = structs.CalcSize('H');
* ```
*/
export function StructsCalcSize(format: string): number | null {
return null;
}
/**
* StructsUnpack the byte slice (presumably packed by Pack(format, msg)) according to the given format.
* The result is a []interface{} slice even if it contains exactly one item.
* The byte slice must contain not less the amount of data required by the format
* (len(msg) must more or equal CalcSize(format)).
* Ex: structs.Unpack(">I", buff[:nb])
* @example
* ```javascript
* const structs = require('nuclei/structs');
* const result = structs.Unpack('H', [0]);
* ```
*/
export function Unpack(format: string, msg: Uint8Array): any | null {
return null;
}
================================================
FILE: pkg/js/generated/ts/telnet.ts
================================================
/**
* IsTelnet checks if a host is running a Telnet server.
* @example
* ```javascript
* const telnet = require('nuclei/telnet');
* const isTelnet = telnet.IsTelnet('acme.com', 23);
* log(toJSON(isTelnet));
* ```
*/
export function IsTelnet(host: string, port: number): IsTelnetResponse | null {
return null;
}
/**
* TelnetClient is a client for Telnet servers.
* @example
* ```javascript
* const telnet = require('nuclei/telnet');
* const client = new telnet.TelnetClient();
* ```
*/
export class TelnetClient {
/**
* Connect tries to connect to provided host and port with telnet.
* Optionally provides username and password for authentication.
* Returns state of connection. If the connection is successful,
* the function will return true, otherwise false.
* @example
* ```javascript
* const telnet = require('nuclei/telnet');
* const client = new telnet.TelnetClient();
* const connected = client.Connect('acme.com', 23, 'username', 'password');
* ```
*/
public Connect(host: string, port: number, username: string, password: string): boolean {
return false;
}
/**
* Info gathers information about the telnet server including encryption support.
* Uses the telnetmini library's DetectEncryption helper function.
* WARNING: The connection used for detection becomes unusable after this call.
* @example
* ```javascript
* const telnet = require('nuclei/telnet');
* const client = new telnet.TelnetClient();
* const info = client.Info('acme.com', 23);
* log(toJSON(info));
* ```
*/
public Info(host: string, port: number): TelnetInfoResponse | null {
return null;
}
/**
* GetTelnetNTLMInfo implements the Nmap telnet-ntlm-info.nse script functionality.
* This function uses the telnetmini library and SMB packet crafting functions to send
* MS-TNAP NTLM authentication requests with null credentials. It might work only on
* Microsoft Telnet servers.
* @example
* ```javascript
* const telnet = require('nuclei/telnet');
* const client = new telnet.TelnetClient();
* const ntlmInfo = client.GetTelnetNTLMInfo('acme.com', 23);
* log(toJSON(ntlmInfo));
* ```
*/
public GetTelnetNTLMInfo(host: string, port: number): NTLMInfoResponse | null {
return null;
}
}
/**
* IsTelnetResponse is the response from the IsTelnet function.
* this is returned by IsTelnet function.
* @example
* ```javascript
* const telnet = require('nuclei/telnet');
* const isTelnet = telnet.IsTelnet('acme.com', 23);
* log(toJSON(isTelnet));
* ```
*/
export interface IsTelnetResponse {
IsTelnet?: boolean,
Banner?: string,
}
/**
* TelnetInfoResponse is the response from the Info function.
* @example
* ```javascript
* const telnet = require('nuclei/telnet');
* const client = new telnet.TelnetClient();
* const info = client.Info('acme.com', 23);
* log(toJSON(info));
* ```
*/
export interface TelnetInfoResponse {
SupportsEncryption?: boolean,
Banner?: string,
Options?: { [key: number]: number[] },
}
/**
* NTLMInfoResponse represents the response from NTLM information gathering.
* This matches exactly the output structure from the Nmap telnet-ntlm-info.nse script.
* @example
* ```javascript
* const telnet = require('nuclei/telnet');
* const client = new telnet.TelnetClient();
* const ntlmInfo = client.GetTelnetNTLMInfo('acme.com', 23);
* log(toJSON(ntlmInfo));
* ```
*/
export interface NTLMInfoResponse {
/**
* Target_Name from script (target_realm in script)
*/
TargetName?: string,
/**
* NetBIOS_Domain_Name from script
*/
NetBIOSDomainName?: string,
/**
* NetBIOS_Computer_Name from script
*/
NetBIOSComputerName?: string,
/**
* DNS_Domain_Name from script
*/
DNSDomainName?: string,
/**
* DNS_Computer_Name from script (fqdn in script)
*/
DNSComputerName?: string,
/**
* DNS_Tree_Name from script (dns_forest_name in script)
*/
DNSTreeName?: string,
/**
* Product_Version from script
*/
ProductVersion?: string,
/**
* Raw timestamp for skew calculation
*/
Timestamp?: number,
}
================================================
FILE: pkg/js/generated/ts/vnc.ts
================================================
/**
* IsVNC checks if a host is running a VNC server.
* It returns a boolean indicating if the host is running a VNC server
* and the banner of the VNC server.
* @example
* ```javascript
* const vnc = require('nuclei/vnc');
* const isVNC = vnc.IsVNC('acme.com', 5900);
* log(toJSON(isVNC));
* ```
*/
export function IsVNC(host: string, port: number): IsVNCResponse | null {
return null;
}
/**
* IsVNCResponse is the response from the IsVNC function.
* @example
* ```javascript
* const vnc = require('nuclei/vnc');
* const isVNC = vnc.IsVNC('acme.com', 5900);
* log(toJSON(isVNC));
* ```
*/
export interface IsVNCResponse {
IsVNC?: boolean,
Banner?: string,
}
/**
* VNCClient is a client for VNC servers.
* @example
* ```javascript
* const vnc = require('nuclei/vnc');
* const client = new vnc.VNCClient();
* ```
*/
export class VNCClient {
// Constructor of VNCClient
constructor() {}
/**
* Connect connects to VNC server using given password.
* If connection and authentication is successful, it returns true.
* If connection or authentication is unsuccessful, it returns false and error.
* The connection is closed after the function returns.
* @example
* ```javascript
* const vnc = require('nuclei/vnc');
* const client = new vnc.VNCClient();
* const connected = client.Connect('acme.com', 5900, 'password');
* ```
*/
public Connect(host: string, port: number, password: string): boolean | null {
return null;
}
}
================================================
FILE: pkg/js/global/exports.js
================================================
exports = {
// General
dump_json: dump_json,
to_json: to_json,
to_array: to_array,
hex_to_ascii: hex_to_ascii,
// Active Directory
getDomainControllerName: getDomainControllerName,
};
================================================
FILE: pkg/js/global/helpers.go
================================================
package global
import (
"encoding/base64"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
)
func registerAdditionalHelpers(runtime *goja.Runtime) {
_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{
Name: "atob",
Signatures: []string{
"atob(string) string",
},
Description: "Base64 decodes a given string",
FuncDecl: func(call goja.FunctionCall) goja.Value {
input := call.Argument(0).String()
decoded, err := base64.StdEncoding.DecodeString(input)
if err != nil {
return goja.Null()
}
return runtime.ToValue(string(decoded))
},
})
_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{
Name: "btoa",
Signatures: []string{
"bota(string) string",
},
Description: "Base64 encodes a given string",
FuncDecl: func(call goja.FunctionCall) goja.Value {
input := call.Argument(0).String()
encoded := base64.StdEncoding.EncodeToString([]byte(input))
return runtime.ToValue(encoded)
},
})
}
func init() {
// these are dummy functions we use trigger documentation generation
// actual definitions are in exports.js
_ = gojs.RegisterFuncWithSignature(nil, gojs.FuncOpts{
Name: "to_json",
Signatures: []string{
"to_json(any) object",
},
Description: "Converts a given object to JSON",
})
_ = gojs.RegisterFuncWithSignature(nil, gojs.FuncOpts{
Name: "dump_json",
Signatures: []string{
"dump_json(any)",
},
Description: "Prints a given object as JSON in console",
})
_ = gojs.RegisterFuncWithSignature(nil, gojs.FuncOpts{
Name: "to_array",
Signatures: []string{
"to_array(any) array",
},
Description: "Sets/Updates objects prototype to array to enable Array.XXX functions",
})
_ = gojs.RegisterFuncWithSignature(nil, gojs.FuncOpts{
Name: "hex_to_ascii",
Signatures: []string{
"hex_to_ascii(string) string",
},
Description: "Converts a given hex string to ascii",
})
}
================================================
FILE: pkg/js/global/js/active_directory.js
================================================
// getDomainControllerName returns the domain controller
// name for a host.
//
// If the name is not empty, it is returned.
// Otherwise, the host is used to query the domain controller name.
// from SMB, LDAP, etc
function getDomainControllerName(name, host) {
if (name != "") {
return name;
}
// First try LDAP and then SMB
try {
var name = getDomainControllerNameByLDAP(host);
if (name != "") {
return name;
}
} catch (e) {
console.log("[ldap] Error getting domain controller name", e);
}
try {
var name = getDomainControllerNameBySMB(host);
if (name != "") {
return name;
}
} catch (e) {
console.log("[smb] Error getting domain controller name", e);
}
return "";
}
function getDomainControllerNameBySMB(host) {
const s = require("nuclei/libsmb");
const sc = s.Client();
var list = sc.ListSMBv2Metadata(host, 445);
if (!list) {
return "";
}
if (list && list.DNSDomainName != "") {
console.log("[smb] Got domain controller", list.DNSDomainName);
return list.DNSDomainName;
}
}
function getDomainControllerNameByLDAP(host) {
const l = require("nuclei/libldap");
const lc = l.Client();
var list = lc.CollectLdapMetadata("", host);
if (!list) {
return "";
}
if (list && list.domain != "") {
console.log("[ldap] Got domain controller", list.domain);
return list.domain;
}
}
================================================
FILE: pkg/js/global/js/dump.js
================================================
// dump_json dumps the data as JSON to the console.
// It returns beautified JSON.
function dump_json(data) {
console.log(JSON.stringify(data, null, 2));
}
// to_json returns beautified JSON.
function to_json(data) {
return JSON.stringify(data, null, 2);
}
// to_array sets object type as array
function to_array(data) {
return Object.setPrototypeOf(data, Array.prototype);
}
// hex_to_ascii converts a hex string to ascii.
function hex_to_ascii(str1) {
var hex = str1.toString();
var str = "";
for (var n = 0; n < hex.length; n += 2) {
str += String.fromCharCode(parseInt(hex.substr(n, 2), 16));
}
return str;
}
================================================
FILE: pkg/js/global/scripts.go
================================================
package global
import (
"bytes"
"context"
"crypto/rand"
"embed"
"math/big"
"net"
"reflect"
"time"
"github.com/Mzack9999/goja"
"github.com/logrusorgru/aurora"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/utils/errkit"
stringsutil "github.com/projectdiscovery/utils/strings"
)
var (
//go:embed js
embedFS embed.FS
//go:embed exports.js
exports string
// knownPorts is a list of known ports for protocols implemented in nuclei
knowPorts = []string{"80", "443", "8080", "8081", "8443", "53"}
)
// default imported modules
// there might be other methods to achieve this
// but this is most straightforward
var (
defaultImports = `
var structs = require("nuclei/structs");
var bytes = require("nuclei/bytes");
`
)
// initBuiltInFunc initializes runtime with builtin functions
func initBuiltInFunc(runtime *goja.Runtime) {
_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{
Name: "Rand",
Signatures: []string{"Rand(n int) []byte"},
Description: "Rand returns a random byte slice of length n",
FuncDecl: func(n int) []byte {
b := make([]byte, n)
if _, err := rand.Read(b); err != nil {
return nil
}
return b
},
})
_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{
Name: "RandInt",
Signatures: []string{"RandInt() int"},
Description: "RandInt returns a random int",
FuncDecl: func() int64 {
n, err := rand.Int(rand.Reader, new(big.Int).SetInt64(1<<63-1))
if err != nil {
return 0
}
return n.Int64()
},
})
_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{
Name: "log",
Signatures: []string{
"log(msg string)",
"log(msg map[string]interface{})",
},
Description: "log prints given input to stdout with [JS] prefix for debugging purposes ",
FuncDecl: func(call goja.FunctionCall) goja.Value {
arg := call.Argument(0).Export()
switch value := arg.(type) {
case string:
gologger.DefaultLogger.Print().Msgf("[%v] %v", aurora.BrightCyan("JS"), value)
case map[string]interface{}:
gologger.DefaultLogger.Print().Msgf("[%v] %v", aurora.BrightCyan("JS"), vardump.DumpVariables(value))
default:
gologger.DefaultLogger.Print().Msgf("[%v] %v", aurora.BrightCyan("JS"), value)
}
return call.Argument(0)
},
})
_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{
Name: "getNetworkPort",
Signatures: []string{
"getNetworkPort(port string, defaultPort string) string",
},
Description: "getNetworkPort registers defaultPort and returns defaultPort if it is a colliding port with other protocols",
FuncDecl: func(call goja.FunctionCall) goja.Value {
inputPort := call.Argument(0).String()
if inputPort == "" || stringsutil.EqualFoldAny(inputPort, knowPorts...) {
// if inputPort is empty or a know port of other protocol
// return given defaultPort
return call.Argument(1)
}
return call.Argument(0)
},
})
// is port open check is port is actually open
// it can be invoked as isPortOpen(host, port, [timeout])
// where timeout is optional and defaults to 5 seconds
_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{
Name: "isPortOpen",
Signatures: []string{
"isPortOpen(host string, port string, [timeout int]) bool",
},
Description: "isPortOpen checks if given TCP port is open on host. timeout is optional and defaults to 5 seconds",
FuncDecl: func(ctx context.Context, host string, port string, timeout ...int) (bool, error) {
if len(timeout) > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Duration(timeout[0])*time.Second)
defer cancel()
}
if host == "" || port == "" {
return false, errkit.New("isPortOpen: host or port is empty")
}
executionId := ctx.Value("executionId").(string)
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
panic("dialers with executionId " + executionId + " not found")
}
conn, err := dialer.Fastdialer.Dial(ctx, "tcp", net.JoinHostPort(host, port))
if err != nil {
return false, err
}
_ = conn.Close()
return true, nil
},
})
_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{
Name: "isUDPPortOpen",
Signatures: []string{
"isUDPPortOpen(host string, port string, [timeout int]) bool",
},
Description: "isUDPPortOpen checks if the given UDP port is open on the host. Timeout is optional and defaults to 5 seconds.",
FuncDecl: func(ctx context.Context, host string, port string, timeout ...int) (bool, error) {
if len(timeout) > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Duration(timeout[0])*time.Second)
defer cancel()
}
if host == "" || port == "" {
return false, errkit.New("isPortOpen: host or port is empty")
}
executionId := ctx.Value("executionId").(string)
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
panic("dialers with executionId " + executionId + " not found")
}
conn, err := dialer.Fastdialer.Dial(ctx, "udp", net.JoinHostPort(host, port))
if err != nil {
return false, err
}
_ = conn.Close()
return true, nil
},
})
_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{
Name: "ToBytes",
Signatures: []string{
"ToBytes(...interface{}) []byte",
},
Description: "ToBytes converts given input to byte slice",
FuncDecl: func(call goja.FunctionCall) goja.Value {
var buff bytes.Buffer
allVars := []any{}
for _, v := range call.Arguments {
if v.Export() == nil {
continue
}
if v.ExportType().Kind() == reflect.Slice {
// convert []datatype to []interface{}
// since it cannot be type asserted to []interface{} directly
rfValue := reflect.ValueOf(v.Export())
for i := 0; i < rfValue.Len(); i++ {
allVars = append(allVars, rfValue.Index(i).Interface())
}
} else {
allVars = append(allVars, v.Export())
}
}
for _, v := range allVars {
buff.WriteString(types.ToString(v))
}
return runtime.ToValue(buff.Bytes())
},
})
_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{
Name: "ToString",
Signatures: []string{
"ToString(...interface{}) string",
},
Description: "ToString converts given input to string",
FuncDecl: func(call goja.FunctionCall) goja.Value {
var buff bytes.Buffer
for _, v := range call.Arguments {
exported := v.Export()
if exported != nil {
buff.WriteString(types.ToString(exported))
}
}
return runtime.ToValue(buff.String())
},
})
// register additional helpers
registerAdditionalHelpers(runtime)
}
// RegisterNativeScripts are js scripts that were added for convenience
// and abstraction purposes we execute them in every runtime and make them
// available for use in any js script
// see: scripts/ for examples
func RegisterNativeScripts(runtime *goja.Runtime) error {
initBuiltInFunc(runtime)
dirs, err := embedFS.ReadDir("js")
if err != nil {
return err
}
for _, dir := range dirs {
if dir.IsDir() {
continue
}
// embeds have / as path separator (on all os)
contents, err := embedFS.ReadFile("js" + "/" + dir.Name())
if err != nil {
return err
}
// run all built in js helper functions or scripts
_, err = runtime.RunString(string(contents))
if err != nil {
return err
}
}
// exports defines the exports object
_, err = runtime.RunString(exports)
if err != nil {
return err
}
// import default modules
_, err = runtime.RunString(defaultImports)
if err != nil {
return errkit.Wrapf(err, "could not import default modules %v", defaultImports)
}
return nil
}
================================================
FILE: pkg/js/global/scripts_test.go
================================================
package global
import (
"testing"
"github.com/Mzack9999/goja"
"github.com/Mzack9999/goja_nodejs/console"
"github.com/Mzack9999/goja_nodejs/require"
)
func TestScriptsRuntime(t *testing.T) {
defaultImports = ""
runtime := goja.New()
registry := new(require.Registry)
registry.Enable(runtime)
console.Enable(runtime)
err := RegisterNativeScripts(runtime)
if err != nil {
t.Fatal(err)
}
value, err := runtime.RunString("dump_json({a: 1, b: 2})")
if err != nil {
t.Fatal(err)
}
_ = value
}
================================================
FILE: pkg/js/gojs/gojs.go
================================================
package gojs
import (
"context"
"maps"
"reflect"
"sync"
"github.com/Mzack9999/goja"
"github.com/Mzack9999/goja_nodejs/require"
"github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
)
type Objects map[string]interface{}
type Runtime interface {
Set(string, interface{}) error
}
type Object interface {
Set(string, interface{})
Get(string) interface{}
}
type Module interface {
Name() string
Set(objects Objects) Module
Enable(Runtime)
Register() Module
}
type GojaModule struct {
name string
sets map[string]interface{}
once sync.Once
}
func NewGojaModule(name string) Module {
return &GojaModule{
name: name,
sets: make(map[string]interface{}),
}
}
func (p *GojaModule) String() string {
return p.name
}
func (p *GojaModule) Name() string {
return p.name
}
// wrapModuleFunc wraps a Go function with context injection for modules
// nolint
func wrapModuleFunc(runtime *goja.Runtime, fn interface{}) interface{} {
fnType := reflect.TypeOf(fn)
if fnType.Kind() != reflect.Func {
return fn
}
// Only wrap if first parameter is context.Context
if fnType.NumIn() == 0 || fnType.In(0) != reflect.TypeFor[context.Context]() {
return fn // Return original function unchanged if it doesn't have context.Context as first arg
}
// Create input and output type slices
inTypes := make([]reflect.Type, fnType.NumIn())
for i := 0; i < fnType.NumIn(); i++ {
inTypes[i] = fnType.In(i)
}
outTypes := make([]reflect.Type, fnType.NumOut())
for i := 0; i < fnType.NumOut(); i++ {
outTypes[i] = fnType.Out(i)
}
// Create a new function with same signature
newFnType := reflect.FuncOf(inTypes, outTypes, fnType.IsVariadic())
newFn := reflect.MakeFunc(newFnType, func(args []reflect.Value) []reflect.Value {
// Get context from runtime
var ctx context.Context
if ctxVal := runtime.Get("context"); ctxVal != nil {
if ctxObj, ok := ctxVal.Export().(context.Context); ok {
ctx = ctxObj
}
}
if ctx == nil {
ctx = context.Background()
}
// Add execution ID to context if available
if execID := runtime.Get("executionId"); execID != nil {
//nolint
ctx = context.WithValue(ctx, "executionId", execID.String())
}
// Replace first argument (context) with our context
args[0] = reflect.ValueOf(ctx)
// Call original function with modified arguments
return reflect.ValueOf(fn).Call(args)
})
return newFn.Interface()
}
func (p *GojaModule) Set(objects Objects) Module {
maps.Copy(p.sets, objects)
return p
}
func (p *GojaModule) Require(runtime *goja.Runtime, module *goja.Object) {
o := module.Get("exports").(*goja.Object)
for k, v := range p.sets {
_ = o.Set(k, v)
}
}
func (p *GojaModule) Enable(runtime Runtime) {
_ = runtime.Set(p.Name(), require.Require(runtime.(*goja.Runtime), p.Name()))
}
func (p *GojaModule) Register() Module {
p.once.Do(func() {
require.RegisterNativeModule(p.Name(), p.Require)
})
return p
}
// GetClassConstructor returns a constructor for any given go struct type for goja runtime
func GetClassConstructor[T any](instance *T) func(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {
return func(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {
return utils.LinkConstructor[*T](call, runtime, instance)
}
}
================================================
FILE: pkg/js/gojs/set.go
================================================
package gojs
import (
"context"
"reflect"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/utils/errkit"
)
var (
ErrInvalidFuncOpts = errkit.New("invalid function options")
ErrNilRuntime = errkit.New("runtime is nil")
)
type FuncOpts struct {
Name string
Signatures []string
Description string
FuncDecl interface{}
}
// valid checks if the function options are valid
func (f *FuncOpts) valid() bool {
return f.Name != "" && f.FuncDecl != nil && len(f.Signatures) > 0 && f.Description != ""
}
// wrapWithContext wraps a Go function with context injection
// nolint
func wrapWithContext(runtime *goja.Runtime, fn interface{}) interface{} {
fnType := reflect.TypeOf(fn)
if fnType.Kind() != reflect.Func {
return fn
}
// Only wrap if first parameter is context.Context
if fnType.NumIn() == 0 || fnType.In(0) != reflect.TypeFor[context.Context]() {
return fn // Return original function unchanged if it doesn't have context.Context as first arg
}
// Create input and output type slices
inTypes := make([]reflect.Type, fnType.NumIn())
for i := 0; i < fnType.NumIn(); i++ {
inTypes[i] = fnType.In(i)
}
outTypes := make([]reflect.Type, fnType.NumOut())
for i := 0; i < fnType.NumOut(); i++ {
outTypes[i] = fnType.Out(i)
}
// Create a new function with same signature
newFnType := reflect.FuncOf(inTypes, outTypes, fnType.IsVariadic())
newFn := reflect.MakeFunc(newFnType, func(args []reflect.Value) []reflect.Value {
// Get context from runtime
var ctx context.Context
if ctxVal := runtime.Get("context"); ctxVal != nil {
if ctxObj, ok := ctxVal.Export().(context.Context); ok {
ctx = ctxObj
}
}
if ctx == nil {
ctx = context.Background()
}
// Add execution ID to context if available
if execID := runtime.Get("executionId"); execID != nil {
ctx = context.WithValue(ctx, "executionId", execID.String())
}
// Replace first argument (context) with our context
args[0] = reflect.ValueOf(ctx)
// Call original function with modified arguments
return reflect.ValueOf(fn).Call(args)
})
return newFn.Interface()
}
// RegisterFunc registers a function with given name, signatures and description
func RegisterFuncWithSignature(runtime *goja.Runtime, opts FuncOpts) error {
if runtime == nil {
return ErrNilRuntime
}
if !opts.valid() {
return errkit.Newf("invalid function options: name: %s, signatures: %v, description: %s", opts.Name, opts.Signatures, opts.Description)
}
// Wrap the function with context injection
// wrappedFn := wrapWithContext(runtime, opts.FuncDecl)
return runtime.Set(opts.Name, opts.FuncDecl /* wrappedFn */)
}
================================================
FILE: pkg/js/libs/LICENSE.md
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
This project uses modules from praetorian/fingerprintx for detection of network protocols which is licensed under Apache 2.0.
================================================
FILE: pkg/js/libs/bytes/buffer.go
================================================
package bytes
import (
"encoding/hex"
"github.com/Mzack9999/goja"
"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/structs"
"github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
)
type (
// Buffer is a bytes/Uint8Array type in javascript
// @example
// ```javascript
// const bytes = require('nuclei/bytes');
// const bytes = new bytes.Buffer();
// ```
// @example
// ```javascript
// const bytes = require('nuclei/bytes');
// // optionally it can accept existing byte/Uint8Array as input
// const bytes = new bytes.Buffer([1, 2, 3]);
// ```
Buffer struct {
buf []byte
}
)
// NewBuffer creates a new buffer from a byte slice.
func NewBuffer(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {
if len(call.Arguments) > 0 {
if arg, ok := call.Argument(0).Export().([]byte); ok {
return utils.LinkConstructor(call, runtime, &Buffer{buf: arg})
} else {
utils.NewNucleiJS(runtime).Throw("Invalid argument type. Expected bytes/Uint8Array as input but got %T", call.Argument(0).Export())
}
}
return utils.LinkConstructor(call, runtime, &Buffer{})
}
// Write appends the given data to the buffer.
// @example
// ```javascript
// const bytes = require('nuclei/bytes');
// const buffer = new bytes.Buffer();
// buffer.Write([1, 2, 3]);
// ```
func (b *Buffer) Write(data []byte) *Buffer {
b.buf = append(b.buf, data...)
return b
}
// WriteString appends the given string data to the buffer.
// @example
// ```javascript
// const bytes = require('nuclei/bytes');
// const buffer = new bytes.Buffer();
// buffer.WriteString('hello');
// ```
func (b *Buffer) WriteString(data string) *Buffer {
b.buf = append(b.buf, []byte(data)...)
return b
}
// Bytes returns the byte representation of the buffer.
// @example
// ```javascript
// const bytes = require('nuclei/bytes');
// const buffer = new bytes.Buffer();
// buffer.WriteString('hello');
// log(buffer.Bytes());
// ```
func (b *Buffer) Bytes() []byte {
return b.buf
}
// String returns the string representation of the buffer.
// @example
// ```javascript
// const bytes = require('nuclei/bytes');
// const buffer = new bytes.Buffer();
// buffer.WriteString('hello');
// log(buffer.String());
// ```
func (b *Buffer) String() string {
return string(b.buf)
}
// Len returns the length of the buffer.
// @example
// ```javascript
// const bytes = require('nuclei/bytes');
// const buffer = new bytes.Buffer();
// buffer.WriteString('hello');
// log(buffer.Len());
// ```
func (b *Buffer) Len() int {
return len(b.buf)
}
// Hex returns the hex representation of the buffer.
// @example
// ```javascript
// const bytes = require('nuclei/bytes');
// const buffer = new bytes.Buffer();
// buffer.WriteString('hello');
// log(buffer.Hex());
// ```
func (b *Buffer) Hex() string {
return hex.EncodeToString(b.buf)
}
// Hexdump returns the hexdump representation of the buffer.
// @example
// ```javascript
// const bytes = require('nuclei/bytes');
// const buffer = new bytes.Buffer();
// buffer.WriteString('hello');
// log(buffer.Hexdump());
// ```
func (b *Buffer) Hexdump() string {
return hex.Dump(b.buf)
}
// Pack uses structs.Pack and packs given data and appends it to the buffer.
// it packs the data according to the given format.
// @example
// ```javascript
// const bytes = require('nuclei/bytes');
// const buffer = new bytes.Buffer();
// buffer.Pack('I', 123);
// ```
func (b *Buffer) Pack(formatStr string, msg any) error {
bin, err := structs.Pack(formatStr, msg)
if err != nil {
return err
}
b.Write(bin)
return nil
}
================================================
FILE: pkg/js/libs/fs/fs.go
================================================
package fs
import (
"context"
"os"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
// ListDir lists itemType values within a directory
// depending on the itemType provided
// itemType can be any one of ['file','dir',”]
// @example
// ```javascript
// const fs = require('nuclei/fs');
// // this will only return files in /tmp directory
// const files = fs.ListDir('/tmp', 'file');
// ```
// @example
// ```javascript
// const fs = require('nuclei/fs');
// // this will only return directories in /tmp directory
// const dirs = fs.ListDir('/tmp', 'dir');
// ```
// @example
// ```javascript
// const fs = require('nuclei/fs');
// // when no itemType is provided, it will return both files and directories
// const items = fs.ListDir('/tmp');
// ```
func ListDir(ctx context.Context, path string, itemType string) ([]string, error) {
executionId := ctx.Value("executionId").(string)
finalPath, err := protocolstate.NormalizePathWithExecutionId(executionId, path)
if err != nil {
return nil, err
}
values, err := os.ReadDir(finalPath)
if err != nil {
return nil, err
}
var results []string
for _, value := range values {
if itemType == "file" && value.IsDir() {
continue
}
if itemType == "dir" && !value.IsDir() {
continue
}
results = append(results, value.Name())
}
return results, nil
}
// ReadFile reads file contents within permitted paths
// and returns content as byte array
// @example
// ```javascript
// const fs = require('nuclei/fs');
// // here permitted directories are $HOME/nuclei-templates/*
// const content = fs.ReadFile('helpers/usernames.txt');
// ```
func ReadFile(ctx context.Context, path string) ([]byte, error) {
executionId := ctx.Value("executionId").(string)
finalPath, err := protocolstate.NormalizePathWithExecutionId(executionId, path)
if err != nil {
return nil, err
}
bin, err := os.ReadFile(finalPath)
return bin, err
}
// ReadFileAsString reads file contents within permitted paths
// and returns content as string
// @example
// ```javascript
// const fs = require('nuclei/fs');
// // here permitted directories are $HOME/nuclei-templates/*
// const content = fs.ReadFileAsString('helpers/usernames.txt');
// ```
func ReadFileAsString(ctx context.Context, path string) (string, error) {
bin, err := ReadFile(ctx, path)
if err != nil {
return "", err
}
return string(bin), nil
}
// ReadFilesFromDir reads all files from a directory
// and returns a string array with file contents of all files
// @example
// ```javascript
// const fs = require('nuclei/fs');
// // here permitted directories are $HOME/nuclei-templates/*
// const contents = fs.ReadFilesFromDir('helpers/ssh-keys');
// log(contents);
// ```
func ReadFilesFromDir(ctx context.Context, dir string) ([]string, error) {
files, err := ListDir(ctx, dir, "file")
if err != nil {
return nil, err
}
var results []string
for _, file := range files {
content, err := ReadFileAsString(ctx, dir+"/"+file)
if err != nil {
return nil, err
}
results = append(results, content)
}
return results, nil
}
================================================
FILE: pkg/js/libs/goconsole/log.go
================================================
package goconsole
import (
"github.com/Mzack9999/goja_nodejs/console"
"github.com/projectdiscovery/gologger"
)
var _ console.Printer = &GoConsolePrinter{}
// GoConsolePrinter is a console printer for nuclei using gologger
type GoConsolePrinter struct {
logger *gologger.Logger
}
func NewGoConsolePrinter() *GoConsolePrinter {
return &GoConsolePrinter{
logger: gologger.DefaultLogger,
}
}
func (p *GoConsolePrinter) Log(msg string) {
p.logger.Info().Msg(msg)
}
func (p *GoConsolePrinter) Warn(msg string) {
p.logger.Warning().Msg(msg)
}
func (p *GoConsolePrinter) Error(msg string) {
p.logger.Error().Msg(msg)
}
================================================
FILE: pkg/js/libs/ikev2/ikev2.go
================================================
package ikev2
import (
"fmt"
"io"
"github.com/projectdiscovery/n3iwf/pkg/ike/message"
"github.com/projectdiscovery/n3iwf/pkg/logger"
)
func init() {
logger.Log.SetOutput(io.Discard)
}
type (
// IKEMessage is the IKEv2 message
//
// IKEv2 implements a limited subset of IKEv2 Protocol, specifically
// the IKE_NOTIFY and IKE_NONCE payloads and the IKE_SA_INIT exchange.
IKEMessage struct {
InitiatorSPI uint64
Version uint8
ExchangeType uint8
Flags uint8
payloads []IKEPayload
}
)
// AppendPayload appends a payload to the IKE message
// payload can be any of the payloads like IKENotification, IKENonce, etc.
// @example
// ```javascript
// const ikev2 = require('nuclei/ikev2');
// const message = new ikev2.IKEMessage();
// const nonce = new ikev2.IKENonce();
// nonce.NonceData = [1, 2, 3];
// message.AppendPayload(nonce);
// ```
func (m *IKEMessage) AppendPayload(payload any) error {
if _, ok := payload.(IKEPayload); !ok {
return fmt.Errorf("invalid payload type only types defined in ikev module like IKENotification, IKENonce, etc. are allowed")
}
m.payloads = append(m.payloads, payload.(IKEPayload))
return nil
}
// Encode encodes the final IKE message
// @example
// ```javascript
// const ikev2 = require('nuclei/ikev2');
// const message = new ikev2.IKEMessage();
// const nonce = new ikev2.IKENonce();
// nonce.NonceData = [1, 2, 3];
// message.AppendPayload(nonce);
// log(message.Encode());
// ```
func (m *IKEMessage) Encode() ([]byte, error) {
var payloads message.IKEPayloadContainer
for _, payload := range m.payloads {
p, err := payload.encode()
if err != nil {
return nil, err
}
payloads = append(payloads, p)
}
msg := &message.IKEMessage{
InitiatorSPI: m.InitiatorSPI,
Version: m.Version,
ExchangeType: m.ExchangeType,
Flags: m.Flags,
Payloads: payloads,
}
encoded, err := msg.Encode()
return encoded, err
}
// IKEPayload is the IKEv2 payload interface
// All the payloads like IKENotification, IKENonce, etc. implement
// this interface.
type IKEPayload interface {
encode() (message.IKEPayload, error)
}
type (
// IKEv2Notify is the IKEv2 Notification payload
// this implements the IKEPayload interface
// @example
// ```javascript
// const ikev2 = require('nuclei/ikev2');
// const notify = new ikev2.IKENotification();
// notify.NotifyMessageType = ikev2.IKE_NOTIFY_NO_PROPOSAL_CHOSEN;
// notify.NotificationData = [1, 2, 3];
// ```
IKENotification struct {
NotifyMessageType uint16
NotificationData []byte
}
)
// encode encodes the IKEv2 Notification payload
func (i *IKENotification) encode() (message.IKEPayload, error) {
notify := message.Notification{
NotifyMessageType: i.NotifyMessageType,
NotificationData: i.NotificationData,
}
return ¬ify, nil
}
const (
// Notify message types
IKE_NOTIFY_NO_PROPOSAL_CHOSEN = 14
IKE_NOTIFY_USE_TRANSPORT_MODE = 16391
IKE_VERSION_2 = 0x20
// Exchange Type
IKE_EXCHANGE_SA_INIT = 34
IKE_EXCHANGE_AUTH = 35
IKE_EXCHANGE_CREATE_CHILD_SA = 36
IKE_EXCHANGE_INFORMATIONAL = 37
// Flags
IKE_FLAGS_InitiatorBitCheck = 0x08
)
type (
// IKENonce is the IKEv2 Nonce payload
// this implements the IKEPayload interface
// @example
// ```javascript
// const ikev2 = require('nuclei/ikev2');
// const nonce = new ikev2.IKENonce();
// nonce.NonceData = [1, 2, 3];
// ```
IKENonce struct {
NonceData []byte
}
)
// encode encodes the IKEv2 Nonce payload
func (i *IKENonce) encode() (message.IKEPayload, error) {
nonce := message.Nonce{
NonceData: i.NonceData,
}
return &nonce, nil
}
================================================
FILE: pkg/js/libs/kerberos/kerberosx.go
================================================
package kerberos
import (
"strings"
"github.com/Mzack9999/goja"
kclient "github.com/jcmturner/gokrb5/v8/client"
kconfig "github.com/jcmturner/gokrb5/v8/config"
"github.com/jcmturner/gokrb5/v8/iana/errorcode"
"github.com/jcmturner/gokrb5/v8/messages"
"github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
ConversionUtil "github.com/projectdiscovery/utils/conversion"
)
type (
// EnumerateUserResponse is the response from EnumerateUser
EnumerateUserResponse struct {
Valid bool `json:"valid"`
ASREPHash string `json:"asrep_hash"`
Error string `json:"error"`
}
)
type (
// TGS is the response from GetServiceTicket
TGS struct {
Ticket messages.Ticket `json:"ticket"`
Hash string `json:"hash"`
ErrMsg string `json:"error"`
}
)
type (
// Config is extra configuration for the kerberos client
Config struct {
ip string
timeout int // in seconds
}
)
// SetIPAddress sets the IP address for the kerberos client
// @example
// ```javascript
// const kerberos = require('nuclei/kerberos');
// const cfg = new kerberos.Config();
// cfg.SetIPAddress('10.10.10.1');
// ```
func (c *Config) SetIPAddress(ip string) *Config {
c.ip = ip
return c
}
// SetTimeout sets the RW timeout for the kerberos client
// @example
// ```javascript
// const kerberos = require('nuclei/kerberos');
// const cfg = new kerberos.Config();
// cfg.SetTimeout(5);
// ```
func (c *Config) SetTimeout(timeout int) *Config {
c.timeout = timeout
return c
}
// Example Values for jargons
// Realm: ACME.COM (Authentical zone / security area)
// Domain: acme.com (Public website / domain)
// DomainController: dc.acme.com (Domain Controller / Active Directory Server)
// KDC: kdc.acme.com (Key Distribution Center / Authentication Server)
type (
// Known Issues:
// Hardcoded timeout in gokrb5 library
// TGT / Session Handling not exposed
// Client is kerberos client
// @example
// ```javascript
// const kerberos = require('nuclei/kerberos');
// // if controller is empty a dns lookup for default kdc server will be performed
// const client = new kerberos.Client('acme.com', 'kdc.acme.com');
// ```
Client struct {
nj *utils.NucleiJS // helper functions/bindings
Krb5Config *kconfig.Config
Realm string
config Config
}
)
// Constructor for Kerberos Client
// Constructor: constructor(public domain: string, public controller?: string)
// When controller is empty or not given krb5 will perform a DNS lookup for the default KDC server
// and retrieve its address from the DNS server
func NewKerberosClient(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {
// setup nucleijs utils
c := &Client{nj: utils.NewNucleiJS(runtime)}
c.nj.ObjectSig = "Client(domain, {controller})" // will be included in error messages
// get arguments (type assertion is efficient than reflection)
// when accepting type as input like net.Conn we can use utils.GetArg
domain, _ := c.nj.GetArg(call.Arguments, 0).(string)
controller, _ := c.nj.GetArg(call.Arguments, 1).(string)
// validate arguments
c.nj.Require(domain != "", "domain cannot be empty")
cfg := kconfig.New()
if controller != "" {
// validate controller hostport
executionId := c.nj.ExecutionId()
if !protocolstate.IsHostAllowed(executionId, controller) {
c.nj.Throw("domain controller address blacklisted by network policy")
}
tmp := strings.Split(controller, ":")
if len(tmp) == 1 {
tmp = append(tmp, "88")
}
realm := strings.ToUpper(domain)
cfg.LibDefaults.DefaultRealm = realm // set default realm
cfg.Realms = []kconfig.Realm{
{
Realm: realm,
KDC: []string{tmp[0] + ":" + tmp[1]},
AdminServer: []string{tmp[0] + ":" + tmp[1]},
KPasswdServer: []string{tmp[0] + ":464"}, // default password server port
},
}
cfg.DomainRealm = make(kconfig.DomainRealm)
} else {
// if controller is empty use DNS lookup
cfg.LibDefaults.DNSLookupKDC = true
cfg.LibDefaults.DefaultRealm = strings.ToUpper(domain)
cfg.DomainRealm = make(kconfig.DomainRealm)
}
c.Krb5Config = cfg
c.Realm = strings.ToUpper(domain)
// Link Constructor to Client and return
return utils.LinkConstructor(call, runtime, c)
}
// NewKerberosClientFromString creates a new kerberos client from a string
// by parsing krb5.conf
// @example
// ```javascript
// const kerberos = require('nuclei/kerberos');
// const client = kerberos.NewKerberosClientFromString(`
// [libdefaults]
// default_realm = ACME.COM
// dns_lookup_kdc = true
// `);
// ```
func NewKerberosClientFromString(cfg string) (*Client, error) {
config, err := kconfig.NewFromString(cfg)
if err != nil {
return nil, err
}
return &Client{Krb5Config: config}, nil
}
// SetConfig sets additional config for the kerberos client
// Note: as of now ip and timeout overrides are only supported
// in EnumerateUser due to fastdialer but can be extended to other methods currently
// @example
// ```javascript
// const kerberos = require('nuclei/kerberos');
// const client = new kerberos.Client('acme.com', 'kdc.acme.com');
// const cfg = new kerberos.Config();
// cfg.SetIPAddress('192.168.100.22');
// cfg.SetTimeout(5);
// client.SetConfig(cfg);
// ```
func (c *Client) SetConfig(cfg *Config) {
if cfg == nil {
c.nj.Throw("config cannot be nil")
}
c.config = *cfg
}
// EnumerateUser and attempt to get AS-REP hash by disabling PA-FX-FAST
// @example
// ```javascript
// const kerberos = require('nuclei/kerberos');
// const client = new kerberos.Client('acme.com', 'kdc.acme.com');
// const resp = client.EnumerateUser('pdtm');
// log(resp);
// ```
func (c *Client) EnumerateUser(username string) (EnumerateUserResponse, error) {
c.nj.Require(c.Krb5Config != nil, "Kerberos client not initialized")
password := "password"
// client does not actually attempt connection it manages state here
client := kclient.NewWithPassword(username, c.Realm, password, c.Krb5Config, kclient.DisablePAFXFAST(true))
defer client.Destroy()
// generate ASReq hash
req, err := messages.NewASReqForTGT(client.Credentials.Domain(), client.Config, client.Credentials.CName())
c.nj.HandleError(err, "failed to generate TGT request")
// marshal request
b, err := req.Marshal()
c.nj.HandleError(err, "failed to marshal TGT request")
data, err := SendToKDC(c, string(b))
rb := ConversionUtil.Bytes(data)
if err == nil {
var ASRep messages.ASRep
resp := EnumerateUserResponse{Valid: true}
err = ASRep.Unmarshal(rb)
if err != nil {
resp.Error = err.Error()
return resp, nil
}
hashcatString, _ := ASRepToHashcat(ASRep)
resp.ASREPHash = hashcatString
return resp, nil
}
resp := EnumerateUserResponse{}
e, ok := err.(messages.KRBError)
if !ok {
return resp, err
}
if e.ErrorCode == errorcode.KDC_ERR_PREAUTH_REQUIRED {
resp.Valid = true
resp.Error = errorcode.Lookup(e.ErrorCode)
return resp, nil
}
resp.Error = errorcode.Lookup(e.ErrorCode)
return resp, nil
}
// GetServiceTicket returns a TGS for a given user, password and SPN
// @example
// ```javascript
// const kerberos = require('nuclei/kerberos');
// const client = new kerberos.Client('acme.com', 'kdc.acme.com');
// const resp = client.GetServiceTicket('pdtm', 'password', 'HOST/CLIENT1');
// log(resp);
// ```
func (c *Client) GetServiceTicket(User, Pass, SPN string) (TGS, error) {
c.nj.Require(c.Krb5Config != nil, "Kerberos client not initialized")
c.nj.Require(User != "", "User cannot be empty")
c.nj.Require(Pass != "", "Pass cannot be empty")
c.nj.Require(SPN != "", "SPN cannot be empty")
executionId := c.nj.ExecutionId()
if len(c.Krb5Config.Realms) > 0 {
// this means dc address was given
for _, r := range c.Krb5Config.Realms {
for _, kdc := range r.KDC {
if !protocolstate.IsHostAllowed(executionId, kdc) {
c.nj.Throw("KDC address %v blacklisted by network policy", kdc)
}
}
for _, kpasswd := range r.KPasswdServer {
if !protocolstate.IsHostAllowed(executionId, kpasswd) {
c.nj.Throw("Kpasswd address %v blacklisted by network policy", kpasswd)
}
}
}
} else {
// here net.Dialer is used instead of fastdialer hence get possible addresses
// and check if they are allowed by network policy
_, kdcs, _ := c.Krb5Config.GetKDCs(c.Realm, true)
for _, v := range kdcs {
if !protocolstate.IsHostAllowed(executionId, v) {
c.nj.Throw("KDC address %v blacklisted by network policy", v)
}
}
}
// client does not actually attempt connection it manages state here
client := kclient.NewWithPassword(User, c.Realm, Pass, c.Krb5Config, kclient.DisablePAFXFAST(true))
defer client.Destroy()
resp := TGS{}
ticket, _, err := client.GetServiceTicket(SPN)
resp.Ticket = ticket
if err != nil {
if code, ok := err.(messages.KRBError); ok {
resp.ErrMsg = errorcode.Lookup(code.ErrorCode)
return resp, err
}
return resp, err
}
// convert AS-REP to hashcat format
hashcat, err := TGStoHashcat(ticket, c.Realm)
if err != nil {
if code, ok := err.(messages.KRBError); ok {
resp.ErrMsg = errorcode.Lookup(code.ErrorCode)
return resp, err
}
return resp, err
}
resp.Ticket = ticket
resp.Hash = hashcat
return resp, nil
}
// // GetASREP returns AS-REP for a given user and password
// // it contains Client's TGT , Principal and Session Key
// // Signature: GetASREP(User, Pass)
// // @param User: string
// // @param Pass: string
// func (c *Client) GetASREP(User, Pass string) messages.ASRep {
// c.nj.Require(c.Krb5Config != nil, "Kerberos client not initialized")
// c.nj.Require(User != "", "User cannot be empty")
// c.nj.Require(Pass != "", "Pass cannot be empty")
// if len(c.Krb5Config.Realms) > 0 {
// // this means dc address was given
// for _, r := range c.Krb5Config.Realms {
// for _, kdc := range r.KDC {
// if !protocolstate.IsHostAllowed(kdc) {
// c.nj.Throw("KDC address blacklisted by network policy")
// }
// }
// for _, kpasswd := range r.KPasswdServer {
// if !protocolstate.IsHostAllowed(kpasswd) {
// c.nj.Throw("Kpasswd address blacklisted by network policy")
// }
// }
// }
// } else {
// // here net.Dialer is used instead of fastdialer hence get possible addresses
// // and check if they are allowed by network policy
// _, kdcs, _ := c.Krb5Config.GetKDCs(c.Realm, true)
// for _, v := range kdcs {
// if !protocolstate.IsHostAllowed(v) {
// c.nj.Throw("KDC address blacklisted by network policy")
// }
// }
// }
// // login to get TGT
// cl := kclient.NewWithPassword(User, c.Realm, Pass, c.Krb5Config, kclient.DisablePAFXFAST(true))
// defer cl.Destroy()
// // generate ASReq
// ASReq, err := messages.NewASReqForTGT(cl.Credentials.Domain(), cl.Config, cl.Credentials.CName())
// c.nj.HandleError(err, "failed to generate TGT request")
// // exchange AS-REQ for AS-REP
// resp, err := cl.ASExchange(c.Realm, ASReq, 0)
// c.nj.HandleError(err, "failed to exchange AS-REQ")
// // try to decrypt encrypted parts of the response and TGT
// key, err := resp.DecryptEncPart(cl.Credentials)
// if err == nil {
// _ = resp.Ticket.Decrypt(key)
// }
// return resp
// }
================================================
FILE: pkg/js/libs/kerberos/sendtokdc.go
================================================
package kerberos
// the following code is adapted from the original library
// https://github.com/jcmturner/gokrb5/blob/855dbc707a37a21467aef6c0245fcf3328dc39ed/v8/client/network.go
// it is copied here because the library does not export "SendToKDC()"
import (
"context"
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"net"
"strings"
"time"
"github.com/jcmturner/gokrb5/v8/messages"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
// sendtokdc.go deals with actual sending and receiving responses from KDC
// SendToKDC sends a message to the KDC and returns the response.
// It first tries to send the message over TCP, and if that fails, it falls back to UDP.(and vice versa)
// @example
// ```javascript
// const kerberos = require('nuclei/kerberos');
// const client = new kerberos.Client('acme.com');
// const response = kerberos.SendToKDC(client, 'message');
// ```
func SendToKDC(kclient *Client, msg string) (string, error) {
if kclient == nil || kclient.nj == nil || kclient.Krb5Config == nil || kclient.Realm == "" {
return "", fmt.Errorf("kerberos client is not initialized")
}
if kclient.config.timeout == 0 {
kclient.config.timeout = 5 // default timeout 5 seconds
}
var response []byte
var err error
response, err = sendToKDCTcp(kclient, msg)
if err == nil {
// if it related to tcp
bin, err := CheckKrbError(response)
if err == nil {
return string(bin), nil
}
// if it is krb error no need to do udp
if e, ok := err.(messages.KRBError); ok {
return string(response), e
}
}
// fallback to udp
response, err = sendToKDCUdp(kclient, msg)
if err == nil {
// if it related to udp
bin, err := CheckKrbError(response)
if err == nil {
return string(bin), nil
}
}
return string(response), err
}
// sendToKDCTcp sends a message to the KDC via TCP.
func sendToKDCTcp(kclient *Client, msg string) ([]byte, error) {
_, kdcs, err := kclient.Krb5Config.GetKDCs(kclient.Realm, true)
kclient.nj.HandleError(err, "error getting KDCs")
kclient.nj.Require(len(kdcs) > 0, "no KDCs found")
executionId := kclient.nj.ExecutionId()
dialers := protocolstate.GetDialersWithId(executionId)
if dialers == nil {
return nil, fmt.Errorf("dialers not initialized for %s", executionId)
}
var errs []string
for i := 1; i <= len(kdcs); i++ {
host, port, err := net.SplitHostPort(kdcs[i])
if err == nil && kclient.config.ip != "" {
// use that ip address instead of realm/domain for resolving
host = kclient.config.ip
}
tcpConn, err := dialers.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, port))
if err != nil {
errs = append(errs, fmt.Sprintf("error establishing connection to %s: %v", kdcs[i], err))
continue
}
defer func() {
_ = tcpConn.Close()
}()
_ = tcpConn.SetDeadline(time.Now().Add(time.Duration(kclient.config.timeout) * time.Second)) //read and write deadline
rb, err := sendTCP(tcpConn.(*net.TCPConn), []byte(msg))
if err != nil {
errs = append(errs, fmt.Sprintf("error sending to %s: %v", kdcs[i], err))
continue
}
return rb, nil
}
if len(errs) > 0 {
return nil, fmt.Errorf("error sending to a KDC: %s", strings.Join(errs, "; "))
}
return nil, nil
}
// sendToKDCUdp sends a message to the KDC via UDP.
func sendToKDCUdp(kclient *Client, msg string) ([]byte, error) {
_, kdcs, err := kclient.Krb5Config.GetKDCs(kclient.Realm, true)
kclient.nj.HandleError(err, "error getting KDCs")
kclient.nj.Require(len(kdcs) > 0, "no KDCs found")
executionId := kclient.nj.ExecutionId()
dialers := protocolstate.GetDialersWithId(executionId)
if dialers == nil {
return nil, fmt.Errorf("dialers not initialized for %s", executionId)
}
var errs []string
for i := 1; i <= len(kdcs); i++ {
host, port, err := net.SplitHostPort(kdcs[i])
if err == nil && kclient.config.ip != "" {
// use that ip address instead of realm/domain for resolving
host = kclient.config.ip
}
udpConn, err := dialers.Fastdialer.Dial(context.TODO(), "udp", net.JoinHostPort(host, port))
if err != nil {
errs = append(errs, fmt.Sprintf("error establishing connection to %s: %v", kdcs[i], err))
continue
}
defer func() {
_ = udpConn.Close()
}()
_ = udpConn.SetDeadline(time.Now().Add(time.Duration(kclient.config.timeout) * time.Second)) //read and write deadline
rb, err := sendUDP(udpConn.(*net.UDPConn), []byte(msg))
if err != nil {
errs = append(errs, fmt.Sprintf("error sending to %s: %v", kdcs[i], err))
continue
}
return rb, nil
}
if len(errs) > 0 {
// fallback to tcp
return nil, fmt.Errorf("error sending to a KDC: %s", strings.Join(errs, "; "))
}
return nil, nil
}
// sendUDP sends bytes to connection over UDP.
func sendUDP(conn *net.UDPConn, b []byte) ([]byte, error) {
var r []byte
defer func() {
_ = conn.Close()
}()
_, err := conn.Write(b)
if err != nil {
return r, fmt.Errorf("error sending to (%s): %v", conn.RemoteAddr().String(), err)
}
udpbuf := make([]byte, 4096)
n, _, err := conn.ReadFrom(udpbuf)
r = udpbuf[:n]
if err != nil {
return r, fmt.Errorf("sending over UDP failed to %s: %v", conn.RemoteAddr().String(), err)
}
if len(r) < 1 {
return r, fmt.Errorf("no response data from %s", conn.RemoteAddr().String())
}
return r, nil
}
// sendTCP sends bytes to connection over TCP.
func sendTCP(conn *net.TCPConn, b []byte) ([]byte, error) {
defer func() {
_ = conn.Close()
}()
var r []byte
// RFC 4120 7.2.2 specifies the first 4 bytes indicate the length of the message in big endian order.
hb := make([]byte, 4)
binary.BigEndian.PutUint32(hb, uint32(len(b)))
b = append(hb, b...)
_, err := conn.Write(b)
if err != nil {
return r, fmt.Errorf("error sending to KDC (%s): %v", conn.RemoteAddr().String(), err)
}
sh := make([]byte, 4)
_, err = conn.Read(sh)
if err != nil {
return r, fmt.Errorf("error reading response size header: %v", err)
}
s := binary.BigEndian.Uint32(sh)
rb := make([]byte, s)
_, err = io.ReadFull(conn, rb)
if err != nil {
return r, fmt.Errorf("error reading response: %v", err)
}
if len(rb) < 1 {
return r, fmt.Errorf("no response data from KDC %s", conn.RemoteAddr().String())
}
return rb, nil
}
// CheckKrbError checks if the response bytes from the KDC are a KRBError.
func CheckKrbError(b []byte) ([]byte, error) {
var KRBErr messages.KRBError
if err := KRBErr.Unmarshal(b); err == nil {
return b, KRBErr
}
return b, nil
}
// TGStoHashcat converts a TGS to a hashcat format.
func TGStoHashcat(tgs messages.Ticket, username string) (string, error) {
return fmt.Sprintf("$krb5tgs$%d$*%s$%s$%s*$%s$%s",
tgs.EncPart.EType,
username,
tgs.Realm,
strings.Join(tgs.SName.NameString[:], "/"),
hex.EncodeToString(tgs.EncPart.Cipher[:16]),
hex.EncodeToString(tgs.EncPart.Cipher[16:]),
), nil
}
// ASRepToHashcat converts an AS-REP message to a hashcat format
func ASRepToHashcat(asrep messages.ASRep) (string, error) {
return fmt.Sprintf("$krb5asrep$%d$%s@%s:%s$%s",
asrep.EncPart.EType,
asrep.CName.PrincipalNameString(),
asrep.CRealm,
hex.EncodeToString(asrep.EncPart.Cipher[:16]),
hex.EncodeToString(asrep.EncPart.Cipher[16:])), nil
}
================================================
FILE: pkg/js/libs/ldap/adenum.go
================================================
package ldap
import (
"fmt"
"strings"
"github.com/go-ldap/ldap/v3"
)
// LDAP makes you search using an OID
// http://oid-info.com/get/1.2.840.113556.1.4.803
//
// The one for the userAccountControl in MS Active Directory is
// 1.2.840.113556.1.4.803 (LDAP_MATCHING_RULE_BIT_AND)
//
// We can look at the enabled flags using a query like (!(userAccountControl:1.2.840.113556.1.4.803:=2))
//
// https://learn.microsoft.com/en-us/troubleshoot/windows-server/identity/useraccountcontrol-manipulate-account-properties
const (
FilterIsPerson = "(objectCategory=person)" // The object is a person.
FilterIsGroup = "(objectCategory=group)" // The object is a group.
FilterIsComputer = "(objectCategory=computer)" // The object is a computer.
FilterIsAdmin = "(adminCount=1)" // The object is an admin.
FilterHasServicePrincipalName = "(servicePrincipalName=*)" // The object has a service principal name.
FilterLogonScript = "(userAccountControl:1.2.840.113556.1.4.803:=1)" // The logon script will be run.
FilterAccountDisabled = "(userAccountControl:1.2.840.113556.1.4.803:=2)" // The user account is disabled.
FilterAccountEnabled = "(!(userAccountControl:1.2.840.113556.1.4.803:=2))" // The user account is enabled.
FilterHomedirRequired = "(userAccountControl:1.2.840.113556.1.4.803:=8)" // The home folder is required.
FilterLockout = "(userAccountControl:1.2.840.113556.1.4.803:=16)" // The user is locked out.
FilterPasswordNotRequired = "(userAccountControl:1.2.840.113556.1.4.803:=32)" // No password is required.
FilterPasswordCantChange = "(userAccountControl:1.2.840.113556.1.4.803:=64)" // The user can't change the password.
FilterCanSendEncryptedPassword = "(userAccountControl:1.2.840.113556.1.4.803:=128)" // The user can send an encrypted password.
FilterIsDuplicateAccount = "(userAccountControl:1.2.840.113556.1.4.803:=256)" // It's an account for users whose primary account is in another domain.
FilterIsNormalAccount = "(userAccountControl:1.2.840.113556.1.4.803:=512)" // It's a default account type that represents a typical user.
FilterInterdomainTrustAccount = "(userAccountControl:1.2.840.113556.1.4.803:=2048)" // It's a permit to trust an account for a system domain that trusts other domains.
FilterWorkstationTrustAccount = "(userAccountControl:1.2.840.113556.1.4.803:=4096)" // It's a computer account for a computer that is running old Windows builds.
FilterServerTrustAccount = "(userAccountControl:1.2.840.113556.1.4.803:=8192)" // It's a computer account for a domain controller that is a member of this domain.
FilterDontExpirePassword = "(userAccountControl:1.2.840.113556.1.4.803:=65536)" // Represents the password, which should never expire on the account.
FilterMnsLogonAccount = "(userAccountControl:1.2.840.113556.1.4.803:=131072)" // It's an MNS logon account.
FilterSmartCardRequired = "(userAccountControl:1.2.840.113556.1.4.803:=262144)" // When this flag is set, it forces the user to log on by using a smart card.
FilterTrustedForDelegation = "(userAccountControl:1.2.840.113556.1.4.803:=524288)" // When this flag is set, the service account (the user or computer account) under which a service runs is trusted for Kerberos delegation.
FilterNotDelegated = "(userAccountControl:1.2.840.113556.1.4.803:=1048576)" // When this flag is set, the security context of the user isn't delegated to a service even if the service account is set as trusted for Kerberos delegation.
FilterUseDesKeyOnly = "(userAccountControl:1.2.840.113556.1.4.803:=2097152)" // Restrict this principal to use only Data Encryption Standard (DES) encryption types for keys.
FilterDontRequirePreauth = "(userAccountControl:1.2.840.113556.1.4.803:=4194304)" // This account doesn't require Kerberos pre-authentication for logging on.
FilterPasswordExpired = "(userAccountControl:1.2.840.113556.1.4.803:=8388608)" // The user's password has expired.
FilterTrustedToAuthForDelegation = "(userAccountControl:1.2.840.113556.1.4.803:=16777216)" // The account is enabled for delegation.
FilterPartialSecretsAccount = "(userAccountControl:1.2.840.113556.1.4.803:=67108864)" // The account is a read-only domain controller (RODC).
)
// JoinFilters joins multiple filters into a single filter
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const filter = ldap.JoinFilters(ldap.FilterIsPerson, ldap.FilterAccountEnabled);
// ```
func JoinFilters(filters ...string) string {
var builder strings.Builder
builder.WriteString("(&")
for _, s := range filters {
builder.WriteString(s)
}
builder.WriteString(")")
return builder.String()
}
// NegativeFilter returns a negative filter for a given filter
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const filter = ldap.NegativeFilter(ldap.FilterIsPerson);
// ```
func NegativeFilter(filter string) string {
return fmt.Sprintf("(!%s)", filter)
}
// FindADObjects finds AD objects based on a filter
// and returns them as a list of ADObject
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// const users = client.FindADObjects(ldap.FilterIsPerson);
// log(to_json(users));
// ```
func (c *Client) FindADObjects(filter string) SearchResult {
c.nj.Require(c.conn != nil, "no existing connection")
sr := ldap.NewSearchRequest(
c.BaseDN, ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases, 0, 0, false,
filter,
[]string{
"distinguishedName",
"sAMAccountName",
"pwdLastSet",
"lastLogon",
"memberOf",
"servicePrincipalName",
},
nil,
)
res, err := c.conn.Search(sr)
c.nj.HandleError(err, "ldap search request failed")
return *getSearchResult(res)
}
// GetADUsers returns all AD users
// using FilterIsPerson filter query
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// const users = client.GetADUsers();
// log(to_json(users));
// ```
func (c *Client) GetADUsers() SearchResult {
return c.FindADObjects(FilterIsPerson)
}
// GetADActiveUsers returns all AD users
// using FilterIsPerson and FilterAccountEnabled filter query
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// const users = client.GetADActiveUsers();
// log(to_json(users));
// ```
func (c *Client) GetADActiveUsers() SearchResult {
return c.FindADObjects(JoinFilters(FilterIsPerson, FilterAccountEnabled))
}
// GetAdUserWithNeverExpiringPasswords returns all AD users
// using FilterIsPerson and FilterDontExpirePassword filter query
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// const users = client.GetADUserWithNeverExpiringPasswords();
// log(to_json(users));
// ```
func (c *Client) GetADUserWithNeverExpiringPasswords() SearchResult {
return c.FindADObjects(JoinFilters(FilterIsPerson, FilterDontExpirePassword))
}
// GetADUserTrustedForDelegation returns all AD users that are trusted for delegation
// using FilterIsPerson and FilterTrustedForDelegation filter query
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// const users = client.GetADUserTrustedForDelegation();
// log(to_json(users));
// ```
func (c *Client) GetADUserTrustedForDelegation() SearchResult {
return c.FindADObjects(JoinFilters(FilterIsPerson, FilterTrustedForDelegation))
}
// GetADUserWithPasswordNotRequired returns all AD users that do not require a password
// using FilterIsPerson and FilterPasswordNotRequired filter query
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// const users = client.GetADUserWithPasswordNotRequired();
// log(to_json(users));
// ```
func (c *Client) GetADUserWithPasswordNotRequired() SearchResult {
return c.FindADObjects(JoinFilters(FilterIsPerson, FilterPasswordNotRequired))
}
// GetADGroups returns all AD groups
// using FilterIsGroup filter query
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// const groups = client.GetADGroups();
// log(to_json(groups));
// ```
func (c *Client) GetADGroups() SearchResult {
return c.FindADObjects(FilterIsGroup)
}
// GetADDCList returns all AD domain controllers
// using FilterIsComputer, FilterAccountEnabled and FilterServerTrustAccount filter query
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// const dcs = client.GetADDCList();
// log(to_json(dcs));
// ```
func (c *Client) GetADDCList() SearchResult {
return c.FindADObjects(JoinFilters(FilterIsComputer, FilterAccountEnabled, FilterServerTrustAccount))
}
// GetADAdmins returns all AD admins
// using FilterIsPerson, FilterAccountEnabled and FilterIsAdmin filter query
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// const admins = client.GetADAdmins();
// log(to_json(admins));
// ```
func (c *Client) GetADAdmins() SearchResult {
return c.FindADObjects(JoinFilters(FilterIsPerson, FilterAccountEnabled, FilterIsAdmin))
}
// GetADUserKerberoastable returns all AD users that are kerberoastable
// using FilterIsPerson, FilterAccountEnabled and FilterHasServicePrincipalName filter query
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// const kerberoastable = client.GetADUserKerberoastable();
// log(to_json(kerberoastable));
// ```
func (c *Client) GetADUserKerberoastable() SearchResult {
return c.FindADObjects(JoinFilters(FilterIsPerson, FilterAccountEnabled, FilterHasServicePrincipalName))
}
// GetADUserAsRepRoastable returns all AD users that are AsRepRoastable
// using FilterIsPerson, and FilterDontRequirePreauth filter query
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// const AsRepRoastable = client.GetADUserAsRepRoastable();
// log(to_json(AsRepRoastable));
// ```
func (c *Client) GetADUserAsRepRoastable() SearchResult {
return c.FindADObjects(JoinFilters(FilterIsPerson, FilterDontRequirePreauth))
}
// GetADDomainSID returns the SID of the AD domain
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// const domainSID = client.GetADDomainSID();
// log(domainSID);
// ```
func (c *Client) GetADDomainSID() string {
r := c.Search(FilterServerTrustAccount, "objectSid")
c.nj.Require(len(r.Entries) > 0, "no result from GetADDomainSID query")
for _, entry := range r.Entries {
if sid, ok := entry.Attributes.Extra["objectSid"]; ok {
if sid, ok := sid.([]string); ok {
return DecodeSID(sid[0])
} else {
c.nj.HandleError(fmt.Errorf("invalid objectSid type: %T", entry.Attributes.Extra["objectSid"]), "invalid objectSid type")
}
}
}
c.nj.HandleError(fmt.Errorf("no objectSid found"), "no objectSid found")
return ""
}
================================================
FILE: pkg/js/libs/ldap/ldap.go
================================================
package ldap
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/url"
"strings"
"github.com/Mzack9999/goja"
"github.com/go-ldap/ldap/v3"
"github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
type (
// Client is a client for ldap protocol in nuclei
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// // here ldap.example.com is the ldap server and acme.com is the realm
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// ```
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const cfg = new ldap.Config();
// cfg.Timeout = 10;
// cfg.ServerName = 'ldap.internal.acme.com';
// // optional config can be passed as third argument
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com', cfg);
// ```
Client struct {
Host string // Hostname
Port int // Port
Realm string // Realm
BaseDN string // BaseDN (generated from Realm)
// unexported
nj *utils.NucleiJS // nuclei js utils
conn *ldap.Conn
cfg Config
}
)
type (
// Config is extra configuration for the ldap client
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const cfg = new ldap.Config();
// cfg.Timeout = 10;
// cfg.ServerName = 'ldap.internal.acme.com';
// cfg.Upgrade = true; // upgrade to tls
// ```
Config struct {
// Timeout is the timeout for the ldap client in seconds
Timeout int
ServerName string // default to host (when using tls)
Upgrade bool // when true first connects to non-tls and then upgrades to tls
}
)
// Constructor for creating a new ldap client
// The following schemas are supported for url: ldap://, ldaps://, ldapi://,
// and cldap:// (RFC1798, deprecated but used by Active Directory).
// ldaps uses TLS/SSL, ldapi uses a Unix domain socket, and cldap uses connectionless LDAP.
// Constructor: constructor(public ldapUrl: string, public realm: string, public config?: Config)
func NewClient(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {
// setup nucleijs utils
c := &Client{nj: utils.NewNucleiJS(runtime)}
c.nj.ObjectSig = "Client(ldapUrl,Realm,{Config})" // will be included in error messages
// get arguments (type assertion is efficient than reflection)
ldapUrl, _ := c.nj.GetArg(call.Arguments, 0).(string)
realm, _ := c.nj.GetArg(call.Arguments, 1).(string)
c.cfg = utils.GetStructTypeSafe[Config](c.nj, call.Arguments, 2, Config{})
c.Realm = realm
c.BaseDN = fmt.Sprintf("dc=%s", strings.Join(strings.Split(realm, "."), ",dc="))
// validate arguments
c.nj.Require(ldapUrl != "", "ldap url cannot be empty")
c.nj.Require(realm != "", "realm cannot be empty")
u, err := url.Parse(ldapUrl)
c.nj.HandleError(err, "invalid ldap url supported schemas are ldap://, ldaps://, ldapi://, and cldap://")
executionId := c.nj.ExecutionId()
dialers := protocolstate.GetDialersWithId(executionId)
if dialers == nil {
panic("dialers with executionId " + executionId + " not found")
}
var conn net.Conn
if u.Scheme == "ldapi" {
if u.Path == "" || u.Path == "/" {
u.Path = "/var/run/slapd/ldapi"
}
conn, err = dialers.Fastdialer.Dial(context.TODO(), "unix", u.Path)
c.nj.HandleError(err, "failed to connect to ldap server")
} else {
host, port, err := net.SplitHostPort(u.Host)
if err != nil {
// we assume that error is due to missing port
host = u.Host
port = ""
}
if u.Scheme == "" {
// default to ldap
u.Scheme = "ldap"
}
switch u.Scheme {
case "cldap":
if port == "" {
port = ldap.DefaultLdapPort
}
conn, err = dialers.Fastdialer.Dial(context.TODO(), "udp", net.JoinHostPort(host, port))
case "ldap":
if port == "" {
port = ldap.DefaultLdapPort
}
conn, err = dialers.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, port))
case "ldaps":
if port == "" {
port = ldap.DefaultLdapsPort
}
serverName := host
if c.cfg.ServerName != "" {
serverName = c.cfg.ServerName
}
conn, err = dialers.Fastdialer.DialTLSWithConfig(context.TODO(), "tcp", net.JoinHostPort(host, port),
&tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS10, ServerName: serverName})
default:
err = fmt.Errorf("unsupported ldap url schema %v", u.Scheme)
}
c.nj.HandleError(err, "failed to connect to ldap server")
}
c.conn = ldap.NewConn(conn, u.Scheme == "ldaps")
if u.Scheme != "ldaps" && c.cfg.Upgrade {
serverName := u.Hostname()
if c.cfg.ServerName != "" {
serverName = c.cfg.ServerName
}
if err := c.conn.StartTLS(&tls.Config{InsecureSkipVerify: true, ServerName: serverName}); err != nil {
c.nj.HandleError(err, "failed to upgrade to tls")
}
} else {
c.conn.Start()
}
return utils.LinkConstructor(call, runtime, c)
}
// Authenticate authenticates with the ldap server using the given username and password
// performs NTLMBind first and then Bind/UnauthenticatedBind if NTLMBind fails
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// client.Authenticate('user', 'password');
// ```
func (c *Client) Authenticate(username, password string) bool {
c.nj.Require(c.conn != nil, "no existing connection")
if c.BaseDN == "" {
c.BaseDN = fmt.Sprintf("dc=%s", strings.Join(strings.Split(c.Realm, "."), ",dc="))
}
if err := c.conn.NTLMBind(c.Realm, username, password); err == nil {
// if bind with NTLMBind(), there is nothing
// else to do, you are authenticated
return true
}
var err error
switch password {
case "":
if err = c.conn.UnauthenticatedBind(username); err != nil {
c.nj.ThrowError(err)
}
default:
if err = c.conn.Bind(username, password); err != nil {
c.nj.ThrowError(err)
}
}
return err == nil
}
// AuthenticateWithNTLMHash authenticates with the ldap server using the given username and NTLM hash
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// client.AuthenticateWithNTLMHash('pdtm', 'hash');
// ```
func (c *Client) AuthenticateWithNTLMHash(username, hash string) bool {
c.nj.Require(c.conn != nil, "no existing connection")
if c.BaseDN == "" {
c.BaseDN = fmt.Sprintf("dc=%s", strings.Join(strings.Split(c.Realm, "."), ",dc="))
}
var err error
if err = c.conn.NTLMBindWithHash(c.Realm, username, hash); err != nil {
c.nj.ThrowError(err)
}
return err == nil
}
// Search accepts whatever filter and returns a list of maps having provided attributes
// as keys and associated values mirroring the ones returned by ldap
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// const results = client.Search('(objectClass=*)', 'cn', 'mail');
// ```
func (c *Client) Search(filter string, attributes ...string) SearchResult {
c.nj.Require(c.conn != nil, "no existing connection")
c.nj.Require(c.BaseDN != "", "base dn cannot be empty")
c.nj.Require(len(attributes) > 0, "attributes cannot be empty")
res, err := c.conn.Search(
ldap.NewSearchRequest(
"",
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0, 0, false,
filter,
attributes,
nil,
),
)
c.nj.HandleError(err, "ldap search request failed")
return *getSearchResult(res)
}
// AdvancedSearch accepts all values of search request type and return Ldap Entry
// its up to user to handle the response
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// const results = client.AdvancedSearch(ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, '(objectClass=*)', ['cn', 'mail'], []);
// ```
func (c *Client) AdvancedSearch(
Scope, DerefAliases, SizeLimit, TimeLimit int,
TypesOnly bool,
Filter string,
Attributes []string,
Controls []ldap.Control) SearchResult {
c.nj.Require(c.conn != nil, "no existing connection")
if c.BaseDN == "" {
c.BaseDN = fmt.Sprintf("dc=%s", strings.Join(strings.Split(c.Realm, "."), ",dc="))
}
req := ldap.NewSearchRequest(c.BaseDN, Scope, DerefAliases, SizeLimit, TimeLimit, TypesOnly, Filter, Attributes, Controls)
res, err := c.conn.Search(req)
c.nj.HandleError(err, "ldap search request failed")
c.nj.Require(res != nil, "ldap search request failed got nil response")
return *getSearchResult(res)
}
type (
// Metadata is the metadata for ldap server.
// this is returned by CollectMetadata method
Metadata struct {
BaseDN string
Domain string
DefaultNamingContext string
DomainFunctionality string
ForestFunctionality string
DomainControllerFunctionality string
DnsHostName string
}
)
// CollectLdapMetadata collects metadata from ldap server.
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// const metadata = client.CollectMetadata();
// log(to_json(metadata));
// ```
func (c *Client) CollectMetadata() Metadata {
c.nj.Require(c.conn != nil, "no existing connection")
var metadata Metadata
metadata.Domain = c.Realm
if c.BaseDN == "" {
c.BaseDN = fmt.Sprintf("dc=%s", strings.Join(strings.Split(c.Realm, "."), ",dc="))
}
metadata.BaseDN = c.BaseDN
// Use scope as Base since Root DSE doesn't have subentries
srMetadata := ldap.NewSearchRequest(
"",
ldap.ScopeBaseObject,
ldap.NeverDerefAliases,
0, 0, false,
"(objectClass=*)",
[]string{
"defaultNamingContext",
"domainFunctionality",
"forestFunctionality",
"domainControllerFunctionality",
"dnsHostName",
},
nil)
resMetadata, err := c.conn.Search(srMetadata)
c.nj.HandleError(err, "ldap search request failed")
for _, entry := range resMetadata.Entries {
for _, attr := range entry.Attributes {
value := entry.GetAttributeValue(attr.Name)
switch attr.Name {
case "defaultNamingContext":
metadata.DefaultNamingContext = value
case "domainFunctionality":
metadata.DomainFunctionality = value
case "forestFunctionality":
metadata.ForestFunctionality = value
case "domainControllerFunctionality":
metadata.DomainControllerFunctionality = value
case "dnsHostName":
metadata.DnsHostName = value
}
}
}
return metadata
}
// GetVersion returns the LDAP versions being used by the server
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// const versions = client.GetVersion();
// log(versions);
// ```
func (c *Client) GetVersion() []string {
c.nj.Require(c.conn != nil, "no existing connection")
// Query root DSE for supported LDAP versions
sr := ldap.NewSearchRequest(
"",
ldap.ScopeBaseObject,
ldap.NeverDerefAliases,
0, 0, false,
"(objectClass=*)",
[]string{"supportedLDAPVersion"},
nil)
res, err := c.conn.Search(sr)
c.nj.HandleError(err, "failed to get LDAP version")
if len(res.Entries) > 0 {
return res.Entries[0].GetAttributeValues("supportedLDAPVersion")
}
return []string{"unknown"}
}
// close the ldap connection
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// client.Close();
// ```
func (c *Client) Close() {
_ = c.conn.Close()
}
================================================
FILE: pkg/js/libs/ldap/utils.go
================================================
package ldap
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/go-ldap/ldap/v3"
)
type (
// SearchResult contains search result of any / all ldap search request
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');
// const results = client.Search('(objectClass=*)', 'cn', 'mail');
// ```
SearchResult struct {
// Referrals contains list of referrals
Referrals []string `json:"referrals"`
// Controls contains list of controls
Controls []string `json:"controls"`
// Entries contains list of entries
Entries []LdapEntry `json:"entries"`
}
// LdapEntry represents a single LDAP entry
LdapEntry struct {
// DN contains distinguished name
DN string `json:"dn"`
// Attributes contains list of attributes
Attributes LdapAttributes `json:"attributes"`
}
// LdapAttributes represents all LDAP attributes of a particular
// ldap entry
LdapAttributes struct {
// CurrentTime contains current time
CurrentTime []string `json:"currentTime,omitempty"`
// SubschemaSubentry contains subschema subentry
SubschemaSubentry []string `json:"subschemaSubentry,omitempty"`
// DsServiceName contains ds service name
DsServiceName []string `json:"dsServiceName,omitempty"`
// NamingContexts contains naming contexts
NamingContexts []string `json:"namingContexts,omitempty"`
// DefaultNamingContext contains default naming context
DefaultNamingContext []string `json:"defaultNamingContext,omitempty"`
// SchemaNamingContext contains schema naming context
SchemaNamingContext []string `json:"schemaNamingContext,omitempty"`
// ConfigurationNamingContext contains configuration naming context
ConfigurationNamingContext []string `json:"configurationNamingContext,omitempty"`
// RootDomainNamingContext contains root domain naming context
RootDomainNamingContext []string `json:"rootDomainNamingContext,omitempty"`
// SupportedLDAPVersion contains supported LDAP version
SupportedLDAPVersion []string `json:"supportedLDAPVersion,omitempty"`
// HighestCommittedUSN contains highest committed USN
HighestCommittedUSN []string `json:"highestCommittedUSN,omitempty"`
// SupportedSASLMechanisms contains supported SASL mechanisms
SupportedSASLMechanisms []string `json:"supportedSASLMechanisms,omitempty"`
// DnsHostName contains DNS host name
DnsHostName []string `json:"dnsHostName,omitempty"`
// LdapServiceName contains LDAP service name
LdapServiceName []string `json:"ldapServiceName,omitempty"`
// ServerName contains server name
ServerName []string `json:"serverName,omitempty"`
// IsSynchronized contains is synchronized
IsSynchronized []string `json:"isSynchronized,omitempty"`
// IsGlobalCatalogReady contains is global catalog ready
IsGlobalCatalogReady []string `json:"isGlobalCatalogReady,omitempty"`
// DomainFunctionality contains domain functionality
DomainFunctionality []string `json:"domainFunctionality,omitempty"`
// ForestFunctionality contains forest functionality
ForestFunctionality []string `json:"forestFunctionality,omitempty"`
// DomainControllerFunctionality contains domain controller functionality
DomainControllerFunctionality []string `json:"domainControllerFunctionality,omitempty"`
// DistinguishedName contains the distinguished name
DistinguishedName []string `json:"distinguishedName,omitempty"`
// SAMAccountName contains the SAM account name
SAMAccountName []string `json:"sAMAccountName,omitempty"`
// PWDLastSet contains the password last set time
PWDLastSet []string `json:"pwdLastSet,omitempty"`
// LastLogon contains the last logon time
LastLogon []string `json:"lastLogon,omitempty"`
// MemberOf contains the groups the entry is a member of
MemberOf []string `json:"memberOf,omitempty"`
// ServicePrincipalName contains the service principal names
ServicePrincipalName []string `json:"servicePrincipalName,omitempty"`
// Extra contains other extra fields which might be present
Extra map[string]any `json:"extra,omitempty"`
}
)
// getSearchResult converts a ldap.SearchResult to a SearchResult
func getSearchResult(sr *ldap.SearchResult) *SearchResult {
t := &SearchResult{
Referrals: []string{},
Controls: []string{},
Entries: []LdapEntry{},
}
// add referrals
t.Referrals = append(t.Referrals, sr.Referrals...)
// add controls
for _, ctrl := range sr.Controls {
t.Controls = append(t.Controls, ctrl.String())
}
// add entries
for _, entry := range sr.Entries {
t.Entries = append(t.Entries, parseLdapEntry(entry))
}
return t
}
func parseLdapEntry(entry *ldap.Entry) LdapEntry {
e := LdapEntry{
DN: entry.DN,
}
attrs := LdapAttributes{
Extra: make(map[string]any),
}
for _, attr := range entry.Attributes {
switch attr.Name {
case "currentTime":
attrs.CurrentTime = decodeTimestamps(attr.Values)
case "subschemaSubentry":
attrs.SubschemaSubentry = attr.Values
case "dsServiceName":
attrs.DsServiceName = attr.Values
case "namingContexts":
attrs.NamingContexts = attr.Values
case "defaultNamingContext":
attrs.DefaultNamingContext = attr.Values
case "schemaNamingContext":
attrs.SchemaNamingContext = attr.Values
case "configurationNamingContext":
attrs.ConfigurationNamingContext = attr.Values
case "rootDomainNamingContext":
attrs.RootDomainNamingContext = attr.Values
case "supportedLDAPVersion":
attrs.SupportedLDAPVersion = attr.Values
case "highestCommittedUSN":
attrs.HighestCommittedUSN = attr.Values
case "supportedSASLMechanisms":
attrs.SupportedSASLMechanisms = attr.Values
case "dnsHostName":
attrs.DnsHostName = attr.Values
case "ldapServiceName":
attrs.LdapServiceName = attr.Values
case "serverName":
attrs.ServerName = attr.Values
case "isSynchronized":
attrs.IsSynchronized = attr.Values
case "isGlobalCatalogReady":
attrs.IsGlobalCatalogReady = attr.Values
case "domainFunctionality":
attrs.DomainFunctionality = attr.Values
case "forestFunctionality":
attrs.ForestFunctionality = attr.Values
case "domainControllerFunctionality":
attrs.DomainControllerFunctionality = attr.Values
case "distinguishedName":
attrs.DistinguishedName = attr.Values
case "sAMAccountName":
attrs.SAMAccountName = attr.Values
case "pwdLastSet":
attrs.PWDLastSet = decodeTimestamps(attr.Values)
case "lastLogon":
attrs.LastLogon = decodeTimestamps(attr.Values)
case "memberOf":
attrs.MemberOf = attr.Values
case "servicePrincipalName":
attrs.ServicePrincipalName = attr.Values
default:
attrs.Extra[attr.Name] = attr.Values
}
}
e.Attributes = attrs
return e
}
// decodeTimestamps decodes multiple timestamps
func decodeTimestamps(timestamps []string) []string {
res := []string{}
for _, timestamp := range timestamps {
res = append(res, DecodeADTimestamp(timestamp))
}
return res
}
// DecodeSID decodes a SID string
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const sid = ldap.DecodeSID('S-1-5-21-3623811015-3361044348-30300820-1013');
// log(sid);
// ```
func DecodeSID(s string) string {
b := []byte(s)
revisionLvl := int(b[0])
subAuthorityCount := int(b[1]) & 0xFF
var authority int
for i := 2; i <= 7; i++ {
authority = authority | int(b[i])<<(8*(5-(i-2)))
}
var size = 4
var offset = 8
var subAuthorities []int
for i := 0; i < subAuthorityCount; i++ {
var subAuthority int
for k := 0; k < size; k++ {
subAuthority = subAuthority | (int(b[offset+k])&0xFF)<<(8*k)
}
subAuthorities = append(subAuthorities, subAuthority)
offset += size
}
var builder strings.Builder
builder.WriteString("S-")
fmt.Fprintf(&builder, "%d-", revisionLvl)
fmt.Fprintf(&builder, "%d", authority)
for _, v := range subAuthorities {
fmt.Fprintf(&builder, "-%d", v)
}
return builder.String()
}
// DecodeADTimestamp decodes an Active Directory timestamp
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const timestamp = ldap.DecodeADTimestamp('132036744000000000');
// log(timestamp);
// ```
func DecodeADTimestamp(timestamp string) string {
adtime, _ := strconv.ParseInt(timestamp, 10, 64)
if (adtime == 9223372036854775807) || (adtime == 0) {
return "Not Set"
}
unixtime_int64 := adtime/(10*1000*1000) - 11644473600
unixtime := time.Unix(unixtime_int64, 0)
return unixtime.Format("2006-01-02 3:4:5 pm")
}
// DecodeZuluTimestamp decodes a Zulu timestamp
// @example
// ```javascript
// const ldap = require('nuclei/ldap');
// const timestamp = ldap.DecodeZuluTimestamp('2021-08-25T10:00:00Z');
// log(timestamp);
// ```
func DecodeZuluTimestamp(timestamp string) string {
zulu, err := time.Parse(time.RFC3339, timestamp)
if err != nil {
return ""
}
return zulu.Format("2006-01-02 3:4:5 pm")
}
================================================
FILE: pkg/js/libs/mssql/memo.mssql.go
================================================
// Warning - This is generated code
package mssql
import (
"errors"
"fmt"
_ "github.com/microsoft/go-mssqldb"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
func memoizedconnect(executionId string, host string, port int, username string, password string, dbName string) (bool, error) {
hash := "connect" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(username) + ":" + fmt.Sprint(password) + ":" + fmt.Sprint(dbName)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return connect(executionId, host, port, username, password, dbName)
})
if err != nil {
return false, err
}
if value, ok := v.(bool); ok {
return value, nil
}
return false, errors.New("could not convert cached result")
}
func memoizedisMssql(executionId string, host string, port int) (bool, error) {
hash := "isMssql" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return isMssql(executionId, host, port)
})
if err != nil {
return false, err
}
if value, ok := v.(bool); ok {
return value, nil
}
return false, errors.New("could not convert cached result")
}
================================================
FILE: pkg/js/libs/mssql/mssql.go
================================================
package mssql
import (
"context"
"database/sql"
"fmt"
"net"
"net/url"
"strings"
"time"
_ "github.com/microsoft/go-mssqldb"
"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/mssql"
"github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
type (
// Client is a client for MS SQL database.
// Internally client uses microsoft/go-mssqldb driver.
// @example
// ```javascript
// const mssql = require('nuclei/mssql');
// const client = new mssql.MSSQLClient;
// ```
MSSQLClient struct{}
)
// Connect connects to MS SQL database using given credentials.
// If connection is successful, it returns true.
// If connection is unsuccessful, it returns false and error.
// The connection is closed after the function returns.
// @example
// ```javascript
// const mssql = require('nuclei/mssql');
// const client = new mssql.MSSQLClient;
// const connected = client.Connect('acme.com', 1433, 'username', 'password');
// ```
func (c *MSSQLClient) Connect(ctx context.Context, host string, port int, username, password string) (bool, error) {
executionId := ctx.Value("executionId").(string)
return memoizedconnect(executionId, host, port, username, password, "master")
}
// ConnectWithDB connects to MS SQL database using given credentials and database name.
// If connection is successful, it returns true.
// If connection is unsuccessful, it returns false and error.
// The connection is closed after the function returns.
// @example
// ```javascript
// const mssql = require('nuclei/mssql');
// const client = new mssql.MSSQLClient;
// const connected = client.ConnectWithDB('acme.com', 1433, 'username', 'password', 'master');
// ```
func (c *MSSQLClient) ConnectWithDB(ctx context.Context, host string, port int, username, password, dbName string) (bool, error) {
executionId := ctx.Value("executionId").(string)
return memoizedconnect(executionId, host, port, username, password, dbName)
}
// @memo
func connect(executionId string, host string, port int, username string, password string, dbName string) (bool, error) {
if host == "" || port <= 0 {
return false, fmt.Errorf("invalid host or port")
}
if !protocolstate.IsHostAllowed(executionId, host) {
// host is not valid according to network policy
return false, protocolstate.ErrHostDenied.Msgf(host)
}
target := net.JoinHostPort(host, fmt.Sprintf("%d", port))
connString := fmt.Sprintf("sqlserver://%s:%s@%s?database=%s&connection+timeout=30",
url.PathEscape(username),
url.PathEscape(password),
target,
dbName)
db, err := sql.Open("sqlserver", connString)
if err != nil {
return false, err
}
defer func() {
_ = db.Close()
}()
_, err = db.Exec("select 1")
if err != nil {
switch {
case strings.Contains(err.Error(), "connect: connection refused"):
fallthrough
case strings.Contains(err.Error(), "no pg_hba.conf entry for host"):
fallthrough
case strings.Contains(err.Error(), "network unreachable"):
fallthrough
case strings.Contains(err.Error(), "reset"):
fallthrough
case strings.Contains(err.Error(), "i/o timeout"):
return false, err
}
return false, nil
}
return true, nil
}
// IsMssql checks if the given host is running MS SQL database.
// If the host is running MS SQL database, it returns true.
// If the host is not running MS SQL database, it returns false.
// @example
// ```javascript
// const mssql = require('nuclei/mssql');
// const isMssql = mssql.IsMssql('acme.com', 1433);
// ```
func (c *MSSQLClient) IsMssql(ctx context.Context, host string, port int) (bool, error) {
executionId := ctx.Value("executionId").(string)
return memoizedisMssql(executionId, host, port)
}
// @memo
func isMssql(executionId string, host string, port int) (bool, error) {
if !protocolstate.IsHostAllowed(executionId, host) {
// host is not valid according to network policy
return false, protocolstate.ErrHostDenied.Msgf(host)
}
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return false, fmt.Errorf("dialers not initialized for %s", executionId)
}
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, fmt.Sprintf("%d", port)))
if err != nil {
return false, err
}
defer func() {
_ = conn.Close()
}()
data, check, err := mssql.DetectMSSQL(conn, 5*time.Second)
if check && err != nil {
return false, nil
} else if !check && err != nil {
return false, err
}
if data.Version != "" {
return true, nil
}
return false, nil
}
// ExecuteQuery connects to MS SQL database using given credentials and executes a query.
// It returns the results of the query or an error if something goes wrong.
// @example
// ```javascript
// const mssql = require('nuclei/mssql');
// const client = new mssql.MSSQLClient;
// const result = client.ExecuteQuery('acme.com', 1433, 'username', 'password', 'master', 'SELECT @@version');
// log(to_json(result));
// ```
func (c *MSSQLClient) ExecuteQuery(ctx context.Context, host string, port int, username, password, dbName, query string) (*utils.SQLResult, error) {
executionId := ctx.Value("executionId").(string)
if host == "" || port <= 0 {
return nil, fmt.Errorf("invalid host or port")
}
if !protocolstate.IsHostAllowed(executionId, host) {
// host is not valid according to network policy
return nil, protocolstate.ErrHostDenied.Msgf(host)
}
target := net.JoinHostPort(host, fmt.Sprintf("%d", port))
ok, err := c.IsMssql(ctx, host, port)
if err != nil {
return nil, err
}
if !ok {
return nil, fmt.Errorf("not a mssql service")
}
connString := fmt.Sprintf("sqlserver://%s:%s@%s?database=%s&connection+timeout=30",
url.PathEscape(username),
url.PathEscape(password),
target,
dbName)
db, err := sql.Open("sqlserver", connString)
if err != nil {
return nil, err
}
defer func() {
_ = db.Close()
}()
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(0)
rows, err := db.Query(query)
if err != nil {
return nil, err
}
data, err := utils.UnmarshalSQLRows(rows)
if err != nil {
if data != nil && len(data.Rows) > 0 {
return data, nil
}
return nil, err
}
return data, nil
}
================================================
FILE: pkg/js/libs/mysql/memo.mysql.go
================================================
// Warning - This is generated code
package mysql
import (
"errors"
"fmt"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
func memoizedisMySQL(executionId string, host string, port int) (bool, error) {
hash := "isMySQL" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return isMySQL(executionId, host, port)
})
if err != nil {
return false, err
}
if value, ok := v.(bool); ok {
return value, nil
}
return false, errors.New("could not convert cached result")
}
func memoizedfingerprintMySQL(executionId string, host string, port int) (MySQLInfo, error) {
hash := "fingerprintMySQL" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return fingerprintMySQL(executionId, host, port)
})
if err != nil {
return MySQLInfo{}, err
}
if value, ok := v.(MySQLInfo); ok {
return value, nil
}
return MySQLInfo{}, errors.New("could not convert cached result")
}
================================================
FILE: pkg/js/libs/mysql/memo.mysql_private.go
================================================
// Warning - This is generated code
package mysql
import (
"errors"
"fmt"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
func memoizedconnectWithDSN(executionId string, dsn string) (bool, error) {
hash := "connectWithDSN" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(dsn)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return connectWithDSN(executionId, dsn)
})
if err != nil {
return false, err
}
if value, ok := v.(bool); ok {
return value, nil
}
return false, errors.New("could not convert cached result")
}
================================================
FILE: pkg/js/libs/mysql/mysql.go
================================================
package mysql
import (
"context"
"database/sql"
"fmt"
"io"
"log"
"net"
"time"
"github.com/go-sql-driver/mysql"
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
mysqlplugin "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/mysql"
"github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
type (
// MySQLClient is a client for MySQL database.
// Internally client uses go-sql-driver/mysql driver.
// @example
// ```javascript
// const mysql = require('nuclei/mysql');
// const client = new mysql.MySQLClient;
// ```
MySQLClient struct{}
)
// IsMySQL checks if the given host is running MySQL database.
// If the host is running MySQL database, it returns true.
// If the host is not running MySQL database, it returns false.
// @example
// ```javascript
// const mysql = require('nuclei/mysql');
// const isMySQL = mysql.IsMySQL('acme.com', 3306);
// ```
func (c *MySQLClient) IsMySQL(ctx context.Context, host string, port int) (bool, error) {
executionId := ctx.Value("executionId").(string)
// todo: why this is exposed? Service fingerprint should be automatic
return memoizedisMySQL(executionId, host, port)
}
// @memo
func isMySQL(executionId string, host string, port int) (bool, error) {
if !protocolstate.IsHostAllowed(executionId, host) {
// host is not valid according to network policy
return false, protocolstate.ErrHostDenied.Msgf(host)
}
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return false, fmt.Errorf("dialers not initialized for %s", executionId)
}
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, fmt.Sprintf("%d", port)))
if err != nil {
return false, err
}
defer func() {
_ = conn.Close()
}()
plugin := &mysqlplugin.MYSQLPlugin{}
service, err := plugin.Run(conn, 5*time.Second, plugins.Target{Host: host})
if err != nil {
return false, err
}
if service == nil {
return false, nil
}
return true, nil
}
// Connect connects to MySQL database using given credentials.
// If connection is successful, it returns true.
// If connection is unsuccessful, it returns false and error.
// The connection is closed after the function returns.
// @example
// ```javascript
// const mysql = require('nuclei/mysql');
// const client = new mysql.MySQLClient;
// const connected = client.Connect('acme.com', 3306, 'username', 'password');
// ```
func (c *MySQLClient) Connect(ctx context.Context, host string, port int, username, password string) (bool, error) {
executionId := ctx.Value("executionId").(string)
if !protocolstate.IsHostAllowed(executionId, host) {
// host is not valid according to network policy
return false, protocolstate.ErrHostDenied.Msgf(host)
}
// executing queries implies the remote mysql service
ok, err := c.IsMySQL(ctx, host, port)
if err != nil {
return false, err
}
if !ok {
return false, fmt.Errorf("not a mysql service")
}
dsn, err := BuildDSN(MySQLOptions{
Host: host,
Port: port,
DbName: "INFORMATION_SCHEMA",
Protocol: "tcp",
Username: username,
Password: password,
})
if err != nil {
return false, err
}
return connectWithDSN(executionId, dsn)
}
type (
// MySQLInfo contains information about MySQL server.
// this is returned when fingerprint is successful
MySQLInfo struct {
Host string `json:"host,omitempty"`
IP string `json:"ip"`
Port int `json:"port"`
Protocol string `json:"protocol"`
TLS bool `json:"tls"`
Transport string `json:"transport"`
Version string `json:"version,omitempty"`
Debug plugins.ServiceMySQL `json:"debug,omitempty"`
Raw string `json:"metadata"`
}
)
// returns MySQLInfo when fingerprint is successful
// @example
// ```javascript
// const mysql = require('nuclei/mysql');
// const info = mysql.FingerprintMySQL('acme.com', 3306);
// log(to_json(info));
// ```
func (c *MySQLClient) FingerprintMySQL(ctx context.Context, host string, port int) (MySQLInfo, error) {
executionId := ctx.Value("executionId").(string)
return memoizedfingerprintMySQL(executionId, host, port)
}
// @memo
func fingerprintMySQL(executionId string, host string, port int) (MySQLInfo, error) {
info := MySQLInfo{}
if !protocolstate.IsHostAllowed(executionId, host) {
// host is not valid according to network policy
return info, protocolstate.ErrHostDenied.Msgf(host)
}
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return MySQLInfo{}, fmt.Errorf("dialers not initialized for %s", executionId)
}
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, fmt.Sprintf("%d", port)))
if err != nil {
return info, err
}
defer func() {
_ = conn.Close()
}()
plugin := &mysqlplugin.MYSQLPlugin{}
service, err := plugin.Run(conn, 5*time.Second, plugins.Target{Host: host})
if err != nil {
return info, err
}
if service == nil {
return info, fmt.Errorf("something went wrong got null output")
}
// fill all fields
info.Host = service.Host
info.IP = service.IP
info.Port = service.Port
info.Protocol = service.Protocol
info.TLS = service.TLS
info.Transport = service.Transport
info.Version = service.Version
info.Debug = service.Metadata().(plugins.ServiceMySQL)
bin, _ := service.Raw.MarshalJSON()
info.Raw = string(bin)
return info, nil
}
// ConnectWithDSN connects to MySQL database using given DSN.
// we override mysql dialer with fastdialer so it respects network policy
// If connection is successful, it returns true.
// @example
// ```javascript
// const mysql = require('nuclei/mysql');
// const client = new mysql.MySQLClient;
// const connected = client.ConnectWithDSN('username:password@tcp(acme.com:3306)/');
// ```
func (c *MySQLClient) ConnectWithDSN(ctx context.Context, dsn string) (bool, error) {
executionId := ctx.Value("executionId").(string)
return memoizedconnectWithDSN(executionId, dsn)
}
// ExecuteQueryWithOpts connects to Mysql database using given credentials
// and executes a query on the db.
// @example
// ```javascript
// const mysql = require('nuclei/mysql');
// const options = new mysql.MySQLOptions();
// options.Host = 'acme.com';
// options.Port = 3306;
// const result = mysql.ExecuteQueryWithOpts(options, 'SELECT * FROM users');
// log(to_json(result));
// ```
func (c *MySQLClient) ExecuteQueryWithOpts(ctx context.Context, opts MySQLOptions, query string) (*utils.SQLResult, error) {
executionId := ctx.Value("executionId").(string)
if !protocolstate.IsHostAllowed(executionId, opts.Host) {
// host is not valid according to network policy
return nil, protocolstate.ErrHostDenied.Msgf(opts.Host)
}
// executing queries implies the remote mysql service
ok, err := c.IsMySQL(ctx, opts.Host, opts.Port)
if err != nil {
return nil, err
}
if !ok {
return nil, fmt.Errorf("not a mysql service")
}
dsn, err := BuildDSN(opts)
if err != nil {
return nil, err
}
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, err
}
defer func() {
_ = db.Close()
}()
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(0)
rows, err := db.Query(query)
if err != nil {
return nil, err
}
data, err := utils.UnmarshalSQLRows(rows)
if err != nil {
if len(data.Rows) > 0 {
// allow partial results
return data, nil
}
return nil, err
}
return data, nil
}
// ExecuteQuery connects to Mysql database using given credentials
// and executes a query on the db.
// @example
// ```javascript
// const mysql = require('nuclei/mysql');
// const result = mysql.ExecuteQuery('acme.com', 3306, 'username', 'password', 'SELECT * FROM users');
// log(to_json(result));
// ```
func (c *MySQLClient) ExecuteQuery(ctx context.Context, host string, port int, username, password, query string) (*utils.SQLResult, error) {
// executing queries implies the remote mysql service
ok, err := c.IsMySQL(ctx, host, port)
if err != nil {
return nil, err
}
if !ok {
return nil, fmt.Errorf("not a mysql service")
}
return c.ExecuteQueryWithOpts(ctx, MySQLOptions{
Host: host,
Port: port,
Protocol: "tcp",
Username: username,
Password: password,
}, query)
}
// ExecuteQuery connects to Mysql database using given credentials
// and executes a query on the db.
// @example
// ```javascript
// const mysql = require('nuclei/mysql');
// const result = mysql.ExecuteQueryOnDB('acme.com', 3306, 'username', 'password', 'dbname', 'SELECT * FROM users');
// log(to_json(result));
// ```
func (c *MySQLClient) ExecuteQueryOnDB(ctx context.Context, host string, port int, username, password, dbname, query string) (*utils.SQLResult, error) {
return c.ExecuteQueryWithOpts(ctx, MySQLOptions{
Host: host,
Port: port,
Protocol: "tcp",
Username: username,
Password: password,
DbName: dbname,
}, query)
}
func init() {
_ = mysql.SetLogger(log.New(io.Discard, "", 0))
}
================================================
FILE: pkg/js/libs/mysql/mysql_private.go
================================================
package mysql
import (
"context"
"database/sql"
"fmt"
"net"
"net/url"
"strings"
)
type (
// MySQLOptions defines the data source name (DSN) options required to connect to a MySQL database.
// along with other options like Timeout etc
// @example
// ```javascript
// const mysql = require('nuclei/mysql');
// const options = new mysql.MySQLOptions();
// options.Host = 'acme.com';
// options.Port = 3306;
// ```
MySQLOptions struct {
Host string // Host is the host name or IP address of the MySQL server.
Port int // Port is the port number on which the MySQL server is listening.
Protocol string // Protocol is the protocol used to connect to the MySQL server (ex: "tcp").
Username string // Username is the user name used to authenticate with the MySQL server.
Password string // Password is the password used to authenticate with the MySQL server.
DbName string // DbName is the name of the database to connect to on the MySQL server.
RawQuery string // QueryStr is the query string to append to the DSN (ex: "?tls=skip-verify").
Timeout int // Timeout is the timeout in seconds for the connection to the MySQL server.
}
)
// BuildDSN builds a MySQL data source name (DSN) from the given options.
// @example
// ```javascript
// const mysql = require('nuclei/mysql');
// const options = new mysql.MySQLOptions();
// options.Host = 'acme.com';
// options.Port = 3306;
// const dsn = mysql.BuildDSN(options);
// ```
func BuildDSN(opts MySQLOptions) (string, error) {
if opts.Host == "" || opts.Port <= 0 {
return "", fmt.Errorf("invalid host or port")
}
if opts.Protocol == "" {
opts.Protocol = "tcp"
}
// We're going to use a custom dialer when creating MySQL connections, so if we've been
// given "tcp" as the protocol, then quietly switch it to "nucleitcp", which we have
// already registered.
if opts.Protocol == "tcp" {
opts.Protocol = "nucleitcp"
}
if opts.DbName == "" {
opts.DbName = "/"
} else {
opts.DbName = "/" + opts.DbName
}
target := net.JoinHostPort(opts.Host, fmt.Sprintf("%d", opts.Port))
var dsn strings.Builder
fmt.Fprintf(&dsn, "%v:%v", url.QueryEscape(opts.Username), opts.Password)
dsn.WriteString("@")
fmt.Fprintf(&dsn, "%v(%v)", opts.Protocol, target)
if opts.DbName != "" {
dsn.WriteString(opts.DbName)
}
if opts.RawQuery != "" {
dsn.WriteString(opts.RawQuery)
}
return dsn.String(), nil
}
// @memo
func connectWithDSN(executionId string, dsn string) (bool, error) {
db, err := sql.Open("mysql", dsn)
if err != nil {
return false, err
}
defer func() {
_ = db.Close()
}()
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(0)
ctx := context.WithValue(context.Background(), "executionId", executionId) // nolint: staticcheck
err = db.PingContext(ctx)
if err != nil {
return false, err
}
return true, nil
}
================================================
FILE: pkg/js/libs/net/net.go
================================================
package net
import (
"context"
"crypto/tls"
"encoding/hex"
"fmt"
"net"
"time"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/utils/errkit"
"github.com/projectdiscovery/utils/reader"
)
var (
defaultTimeout = time.Duration(5) * time.Second
)
// Open opens a new connection to the address with a timeout.
// supported protocols: tcp, udp
// @example
// ```javascript
// const net = require('nuclei/net');
// const conn = net.Open('tcp', 'acme.com:80');
// ```
func Open(ctx context.Context, protocol, address string) (*NetConn, error) {
executionId := ctx.Value("executionId").(string)
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return nil, fmt.Errorf("dialers not initialized for %s", executionId)
}
conn, err := dialer.Fastdialer.Dial(ctx, protocol, address)
if err != nil {
return nil, err
}
return &NetConn{conn: conn, timeout: defaultTimeout}, nil
}
// Open opens a new connection to the address with a timeout.
// supported protocols: tcp, udp
// @example
// ```javascript
// const net = require('nuclei/net');
// const conn = net.OpenTLS('tcp', 'acme.com:443');
// ```
func OpenTLS(ctx context.Context, protocol, address string) (*NetConn, error) {
config := &tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS10}
host, _, _ := net.SplitHostPort(address)
if host != "" {
c := config.Clone()
c.ServerName = host
config = c
}
executionId := ctx.Value("executionId").(string)
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return nil, fmt.Errorf("dialers not initialized for %s", executionId)
}
conn, err := dialer.Fastdialer.DialTLSWithConfig(ctx, protocol, address, config)
if err != nil {
return nil, err
}
return &NetConn{conn: conn, timeout: defaultTimeout}, nil
}
type (
// NetConn is a connection to a remote host.
// this is returned/create by Open and OpenTLS functions.
// @example
// ```javascript
// const net = require('nuclei/net');
// const conn = net.Open('tcp', 'acme.com:80');
// ```
NetConn struct {
conn net.Conn
timeout time.Duration
}
)
// Close closes the connection.
// @example
// ```javascript
// const net = require('nuclei/net');
// const conn = net.Open('tcp', 'acme.com:80');
// conn.Close();
// ```
func (c *NetConn) Close() error {
err := c.conn.Close()
return err
}
// SetTimeout sets read/write timeout for the connection (in seconds).
// @example
// ```javascript
// const net = require('nuclei/net');
// const conn = net.Open('tcp', 'acme.com:80');
// conn.SetTimeout(10);
// ```
func (c *NetConn) SetTimeout(value int) {
c.timeout = time.Duration(value) * time.Second
}
// setDeadLine sets read/write deadline for the connection (in seconds).
// this is intended to be called before every read/write operation.
func (c *NetConn) setDeadLine() {
if c.timeout == 0 {
c.timeout = 5 * time.Second
}
_ = c.conn.SetDeadline(time.Now().Add(c.timeout))
}
// unsetDeadLine unsets read/write deadline for the connection.
func (c *NetConn) unsetDeadLine() {
_ = c.conn.SetDeadline(time.Time{})
}
// SendArray sends array data to connection
// @example
// ```javascript
// const net = require('nuclei/net');
// const conn = net.Open('tcp', 'acme.com:80');
// conn.SendArray(['hello', 'world']);
// ```
func (c *NetConn) SendArray(data []interface{}) error {
c.setDeadLine()
defer c.unsetDeadLine()
input := types.ToByteSlice(data)
length, err := c.conn.Write(input)
if err != nil {
return err
}
if length < len(input) {
return fmt.Errorf("failed to write all bytes (%d bytes written, %d bytes expected)", length, len(input))
}
return nil
}
// SendHex sends hex data to connection
// @example
// ```javascript
// const net = require('nuclei/net');
// const conn = net.Open('tcp', 'acme.com:80');
// conn.SendHex('68656c6c6f');
// ```
func (c *NetConn) SendHex(data string) error {
c.setDeadLine()
defer c.unsetDeadLine()
bin, err := hex.DecodeString(data)
if err != nil {
return err
}
length, err := c.conn.Write(bin)
if err != nil {
return err
}
if length < len(bin) {
return fmt.Errorf("failed to write all bytes (%d bytes written, %d bytes expected)", length, len(bin))
}
return nil
}
// Send sends data to the connection with a timeout.
// @example
// ```javascript
// const net = require('nuclei/net');
// const conn = net.Open('tcp', 'acme.com:80');
// conn.Send('hello');
// ```
func (c *NetConn) Send(data string) error {
c.setDeadLine()
defer c.unsetDeadLine()
bin := []byte(data)
length, err := c.conn.Write(bin)
if err != nil {
return err
}
if length < len(bin) {
return fmt.Errorf("failed to write all bytes (%d bytes written, %d bytes expected)", length, len(data))
}
return nil
}
// RecvFull receives data from the connection with a timeout.
// If N is 0, it will read all data sent by the server with 8MB limit.
// it tries to read until N bytes or timeout is reached.
// @example
// ```javascript
// const net = require('nuclei/net');
// const conn = net.Open('tcp', 'acme.com:80');
// const data = conn.RecvFull(1024);
// ```
func (c *NetConn) RecvFull(N int) ([]byte, error) {
c.setDeadLine()
defer c.unsetDeadLine()
if N == 0 {
// in utils we use -1 to indicate read all rather than 0
N = -1
}
bin, err := reader.ConnReadNWithTimeout(c.conn, int64(N), c.timeout)
if err != nil {
return []byte{}, errkit.Wrapf(err, "failed to read %d bytes", N)
}
return bin, nil
}
// Recv is similar to RecvFull but does not guarantee full read instead
// it creates a buffer of N bytes and returns whatever is returned by the connection
// for reading headers or initial bytes from the server this is usually used.
// for reading a fixed number of already known bytes (ex: body based on content-length) use RecvFull.
// @example
// ```javascript
// const net = require('nuclei/net');
// const conn = net.Open('tcp', 'acme.com:80');
// const data = conn.Recv(1024);
// log(`Received ${data.length} bytes from the server`)
// ```
func (c *NetConn) Recv(N int) ([]byte, error) {
c.setDeadLine()
defer c.unsetDeadLine()
if N == 0 {
N = 4096
}
b := make([]byte, N)
n, err := c.conn.Read(b)
if err != nil {
return []byte{}, errkit.Wrapf(err, "failed to read %d bytes", N)
}
return b[:n], nil
}
// RecvFullString receives data from the connection with a timeout
// output is returned as a string.
// If N is 0, it will read all data sent by the server with 8MB limit.
// @example
// ```javascript
// const net = require('nuclei/net');
// const conn = net.Open('tcp', 'acme.com:80');
// const data = conn.RecvFullString(1024);
// ```
func (c *NetConn) RecvFullString(N int) (string, error) {
bin, err := c.RecvFull(N)
if err != nil {
return "", err
}
return string(bin), nil
}
// RecvString is similar to RecvFullString but does not guarantee full read, instead
// it creates a buffer of N bytes and returns whatever is returned by the connection
// for reading headers or initial bytes from the server this is usually used.
// for reading a fixed number of already known bytes (ex: body based on content-length) use RecvFullString.
// @example
// ```javascript
// const net = require('nuclei/net');
// const conn = net.Open('tcp', 'acme.com:80');
// const data = conn.RecvString(1024);
// ```
func (c *NetConn) RecvString(N int) (string, error) {
bin, err := c.Recv(N)
if err != nil {
return "", err
}
return string(bin), nil
}
// RecvFullHex receives data from the connection with a timeout
// in hex format.
// If N is 0,it will read all data sent by the server with 8MB limit.
// until N bytes or timeout is reached.
// @example
// ```javascript
// const net = require('nuclei/net');
// const conn = net.Open('tcp', 'acme.com:80');
// const data = conn.RecvFullHex(1024);
// ```
func (c *NetConn) RecvFullHex(N int) (string, error) {
bin, err := c.RecvFull(N)
if err != nil {
return "", err
}
return hex.Dump(bin), nil
}
// RecvHex is similar to RecvFullHex but does not guarantee full read instead
// it creates a buffer of N bytes and returns whatever is returned by the connection
// for reading headers or initial bytes from the server this is usually used.
// for reading a fixed number of already known bytes (ex: body based on content-length) use RecvFull.
// @example
// ```javascript
// const net = require('nuclei/net');
// const conn = net.Open('tcp', 'acme.com:80');
// const data = conn.RecvHex(1024);
// ```
func (c *NetConn) RecvHex(N int) (string, error) {
bin, err := c.Recv(N)
if err != nil {
return "", err
}
return hex.Dump(bin), nil
}
================================================
FILE: pkg/js/libs/oracle/memo.oracle.go
================================================
// Warning - This is generated code
package oracle
import (
"errors"
"fmt"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
func memoizedisOracle(executionId string, host string, port int) (IsOracleResponse, error) {
hash := "isOracle" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return isOracle(executionId, host, port)
})
if err != nil {
return IsOracleResponse{}, err
}
if value, ok := v.(IsOracleResponse); ok {
return value, nil
}
return IsOracleResponse{}, errors.New("could not convert cached result")
}
================================================
FILE: pkg/js/libs/oracle/oracle.go
================================================
package oracle
import (
"context"
"database/sql"
"fmt"
"net"
"strconv"
"time"
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/oracledb"
"github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
goora "github.com/sijms/go-ora/v2"
)
type (
// IsOracleResponse is the response from the IsOracle function.
// this is returned by IsOracle function.
// @example
// ```javascript
// const oracle = require('nuclei/oracle');
// const isOracle = oracle.IsOracle('acme.com', 1521);
// ```
IsOracleResponse struct {
IsOracle bool
Banner string
}
// Client is a client for Oracle database.
// Internally client uses oracle/godror driver.
// @example
// ```javascript
// const oracle = require('nuclei/oracle');
// const client = new oracle.OracleClient();
// ```
OracleClient struct {
connector *goora.OracleConnector
}
)
// IsOracle checks if a host is running an Oracle server
// @example
// ```javascript
// const oracle = require('nuclei/oracle');
// const isOracle = oracle.IsOracle('acme.com', 1521);
// log(toJSON(isOracle));
// ```
func (c *OracleClient) IsOracle(ctx context.Context, host string, port int) (IsOracleResponse, error) {
executionId := ctx.Value("executionId").(string)
return memoizedisOracle(executionId, host, port)
}
// @memo
func isOracle(executionId string, host string, port int) (IsOracleResponse, error) {
resp := IsOracleResponse{}
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return IsOracleResponse{}, fmt.Errorf("dialers not initialized for %s", executionId)
}
timeout := 5 * time.Second
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
if err != nil {
return resp, err
}
defer func() {
_ = conn.Close()
}()
oracledbPlugin := oracledb.ORACLEPlugin{}
service, err := oracledbPlugin.Run(conn, timeout, plugins.Target{Host: host})
if err != nil {
return resp, err
}
if service == nil {
return resp, nil
}
resp.Banner = service.Version
resp.Banner = service.Metadata().(plugins.ServiceOracle).Info
resp.IsOracle = true
return resp, nil
}
func (c *OracleClient) oracleDbInstance(connStr string, executionId string) (*goora.OracleConnector, error) {
if c.connector != nil {
return c.connector, nil
}
connector := goora.NewConnector(connStr)
oraConnector, ok := connector.(*goora.OracleConnector)
if !ok {
return nil, fmt.Errorf("failed to cast connector to OracleConnector")
}
// Create custom dialer wrapper
customDialer := &oracleCustomDialer{
executionId: executionId,
}
oraConnector.Dialer(customDialer)
c.connector = oraConnector
return oraConnector, nil
}
// Connect connects to an Oracle database
// @example
// ```javascript
// const oracle = require('nuclei/oracle');
// const client = new oracle.OracleClient;
// client.Connect('acme.com', 1521, 'XE', 'user', 'password');
// ```
func (c *OracleClient) Connect(ctx context.Context, host string, port int, serviceName string, username string, password string) (bool, error) {
connStr := goora.BuildUrl(host, port, serviceName, username, password, nil)
return c.ConnectWithDSN(ctx, connStr)
}
func (c *OracleClient) ConnectWithDSN(ctx context.Context, dsn string) (bool, error) {
executionId := ctx.Value("executionId").(string)
connector, err := c.oracleDbInstance(dsn, executionId)
if err != nil {
return false, err
}
db := sql.OpenDB(connector)
defer func() {
_ = db.Close()
}()
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(0)
// Test the connection
err = db.Ping()
if err != nil {
return false, err
}
return true, nil
}
// ExecuteQuery connects to MS SQL database using given credentials and executes a query.
// It returns the results of the query or an error if something goes wrong.
// @example
// ```javascript
// const oracle = require('nuclei/oracle');
// const client = new oracle.OracleClient;
// const result = client.ExecuteQuery('acme.com', 1521, 'username', 'password', 'XE', 'SELECT @@version');
// log(to_json(result));
// ```
func (c *OracleClient) ExecuteQuery(ctx context.Context, host string, port int, username, password, dbName, query string) (*utils.SQLResult, error) {
if host == "" || port <= 0 {
return nil, fmt.Errorf("invalid host or port")
}
isOracleResp, err := c.IsOracle(ctx, host, port)
if err != nil {
return nil, err
}
if !isOracleResp.IsOracle {
return nil, fmt.Errorf("not a oracle service")
}
connStr := goora.BuildUrl(host, port, dbName, username, password, nil)
return c.ExecuteQueryWithDSN(ctx, connStr, query)
}
// ExecuteQueryWithDSN executes a query on an Oracle database using a DSN
// @example
// ```javascript
// const oracle = require('nuclei/oracle');
// const client = new oracle.OracleClient;
// const result = client.ExecuteQueryWithDSN('oracle://user:password@host:port/service', 'SELECT @@version');
// log(to_json(result));
// ```
func (c *OracleClient) ExecuteQueryWithDSN(ctx context.Context, dsn string, query string) (*utils.SQLResult, error) {
executionId := ctx.Value("executionId").(string)
connector, err := c.oracleDbInstance(dsn, executionId)
if err != nil {
return nil, err
}
db := sql.OpenDB(connector)
defer func() {
_ = db.Close()
}()
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(0)
rows, err := db.Query(query)
if err != nil {
return nil, err
}
data, err := utils.UnmarshalSQLRows(rows)
if err != nil {
if data != nil && len(data.Rows) > 0 {
return data, nil
}
return nil, err
}
return data, nil
}
================================================
FILE: pkg/js/libs/oracle/oracledialer.go
================================================
package oracle
import (
"context"
"fmt"
"net"
"time"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
// oracleCustomDialer implements the dialer interface expected by go-ora
type oracleCustomDialer struct {
executionId string
}
func (o *oracleCustomDialer) dialWithCtx(ctx context.Context, network, address string) (net.Conn, error) {
dialers := protocolstate.GetDialersWithId(o.executionId)
if dialers == nil {
return nil, fmt.Errorf("dialers not initialized for %s", o.executionId)
}
if !protocolstate.IsHostAllowed(o.executionId, address) {
// host is not valid according to network policy
return nil, protocolstate.ErrHostDenied.Msgf(address)
}
return dialers.Fastdialer.Dial(ctx, network, address)
}
func (o *oracleCustomDialer) Dial(network, address string) (net.Conn, error) {
return o.dialWithCtx(context.TODO(), network, address)
}
func (o *oracleCustomDialer) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return o.dialWithCtx(ctx, network, address)
}
func (o *oracleCustomDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
return o.dialWithCtx(ctx, network, address)
}
================================================
FILE: pkg/js/libs/pop3/memo.pop3.go
================================================
// Warning - This is generated code
package pop3
import (
"errors"
"fmt"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
func memoizedisPoP3(executionId string, host string, port int) (IsPOP3Response, error) {
hash := "isPoP3" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return isPoP3(executionId, host, port)
})
if err != nil {
return IsPOP3Response{}, err
}
if value, ok := v.(IsPOP3Response); ok {
return value, nil
}
return IsPOP3Response{}, errors.New("could not convert cached result")
}
================================================
FILE: pkg/js/libs/pop3/pop3.go
================================================
package pop3
import (
"context"
"fmt"
"net"
"strconv"
"time"
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/pop3"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
type (
// IsPOP3Response is the response from the IsPOP3 function.
// this is returned by IsPOP3 function.
// @example
// ```javascript
// const pop3 = require('nuclei/pop3');
// const isPOP3 = pop3.IsPOP3('acme.com', 110);
// log(toJSON(isPOP3));
// ```
IsPOP3Response struct {
IsPOP3 bool
Banner string
}
)
// IsPOP3 checks if a host is running a POP3 server.
// @example
// ```javascript
// const pop3 = require('nuclei/pop3');
// const isPOP3 = pop3.IsPOP3('acme.com', 110);
// log(toJSON(isPOP3));
// ```
func IsPOP3(ctx context.Context, host string, port int) (IsPOP3Response, error) {
executionId := ctx.Value("executionId").(string)
return memoizedisPoP3(executionId, host, port)
}
// @memo
func isPoP3(executionId string, host string, port int) (IsPOP3Response, error) {
resp := IsPOP3Response{}
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return IsPOP3Response{}, fmt.Errorf("dialers not initialized for %s", executionId)
}
timeout := 5 * time.Second
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
if err != nil {
return resp, err
}
defer func() {
_ = conn.Close()
}()
pop3Plugin := pop3.POP3Plugin{}
service, err := pop3Plugin.Run(conn, timeout, plugins.Target{Host: host})
if err != nil {
return resp, err
}
if service == nil {
return resp, nil
}
resp.Banner = service.Metadata().(plugins.ServicePOP3).Banner
resp.IsPOP3 = true
return resp, nil
}
================================================
FILE: pkg/js/libs/postgres/memo.postgres.go
================================================
// Warning - This is generated code
package postgres
import (
"errors"
"fmt"
utils "github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/utils/pgwrap"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
func memoizedisPostgres(executionId string, host string, port int) (bool, error) {
hash := "isPostgres" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return isPostgres(executionId, host, port)
})
if err != nil {
return false, err
}
if value, ok := v.(bool); ok {
return value, nil
}
return false, errors.New("could not convert cached result")
}
func memoizedexecuteQuery(executionId string, host string, port int, username string, password string, dbName string, query string) (*utils.SQLResult, error) {
hash := "executeQuery" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(username) + ":" + fmt.Sprint(password) + ":" + fmt.Sprint(dbName) + ":" + fmt.Sprint(query)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return executeQuery(executionId, host, port, username, password, dbName, query)
})
if err != nil {
return nil, err
}
if value, ok := v.(*utils.SQLResult); ok {
return value, nil
}
return nil, errors.New("could not convert cached result")
}
func memoizedconnect(executionId string, host string, port int, username string, password string, dbName string) (bool, error) {
hash := "connect" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(username) + ":" + fmt.Sprint(password) + ":" + fmt.Sprint(dbName)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return connect(executionId, host, port, username, password, dbName)
})
if err != nil {
return false, err
}
if value, ok := v.(bool); ok {
return value, nil
}
return false, errors.New("could not convert cached result")
}
================================================
FILE: pkg/js/libs/postgres/postgres.go
================================================
package postgres
import (
"context"
"database/sql"
"fmt"
"net"
"strings"
"time"
"github.com/go-pg/pg/v10"
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
postgres "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/postgresql"
utils "github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
"github.com/projectdiscovery/nuclei/v3/pkg/js/utils/pgwrap" //nolint:staticcheck // need to call init
_ "github.com/projectdiscovery/nuclei/v3/pkg/js/utils/pgwrap" //nolint:staticcheck
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
type (
// PGClient is a client for Postgres database.
// Internally client uses go-pg/pg driver.
// @example
// ```javascript
// const postgres = require('nuclei/postgres');
// const client = new postgres.PGClient;
// ```
PGClient struct{}
)
// IsPostgres checks if the given host and port are running Postgres database.
// If connection is successful, it returns true.
// If connection is unsuccessful, it returns false and error.
// @example
// ```javascript
// const postgres = require('nuclei/postgres');
// const isPostgres = postgres.IsPostgres('acme.com', 5432);
// ```
func (c *PGClient) IsPostgres(ctx context.Context, host string, port int) (bool, error) {
executionId := ctx.Value("executionId").(string)
// todo: why this is exposed? Service fingerprint should be automatic
return memoizedisPostgres(executionId, host, port)
}
// @memo
func isPostgres(executionId string, host string, port int) (bool, error) {
timeout := 10 * time.Second
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return false, fmt.Errorf("dialers not initialized for %s", executionId)
}
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port))
if err != nil {
return false, err
}
defer func() {
_ = conn.Close()
}()
_ = conn.SetDeadline(time.Now().Add(timeout))
plugin := &postgres.POSTGRESPlugin{}
service, err := plugin.Run(conn, timeout, plugins.Target{Host: host})
if err != nil {
return false, err
}
if service == nil {
return false, nil
}
return true, nil
}
// Connect connects to Postgres database using given credentials.
// If connection is successful, it returns true.
// If connection is unsuccessful, it returns false and error.
// The connection is closed after the function returns.
// @example
// ```javascript
// const postgres = require('nuclei/postgres');
// const client = new postgres.PGClient;
// const connected = client.Connect('acme.com', 5432, 'username', 'password');
// ```
func (c *PGClient) Connect(ctx context.Context, host string, port int, username string, password string) (bool, error) {
ok, err := c.IsPostgres(ctx, host, port)
if err != nil {
return false, err
}
if !ok {
return false, fmt.Errorf("not a postgres service")
}
executionId := ctx.Value("executionId").(string)
return memoizedconnect(executionId, host, port, username, password, "postgres")
}
// ExecuteQuery connects to Postgres database using given credentials and database name.
// and executes a query on the db.
// If connection is successful, it returns the result of the query.
// @example
// ```javascript
// const postgres = require('nuclei/postgres');
// const client = new postgres.PGClient;
// const result = client.ExecuteQuery('acme.com', 5432, 'username', 'password', 'dbname', 'select * from users');
// log(to_json(result));
// ```
func (c *PGClient) ExecuteQuery(ctx context.Context, host string, port int, username string, password string, dbName string, query string) (*utils.SQLResult, error) {
ok, err := c.IsPostgres(ctx, host, port)
if err != nil {
return nil, err
}
if !ok {
return nil, fmt.Errorf("not a postgres service")
}
executionId := ctx.Value("executionId").(string)
return memoizedexecuteQuery(executionId, host, port, username, password, dbName, query)
}
// @memo
func executeQuery(executionId string, host string, port int, username string, password string, dbName string, query string) (*utils.SQLResult, error) {
if !protocolstate.IsHostAllowed(executionId, host) {
// host is not valid according to network policy
return nil, protocolstate.ErrHostDenied.Msgf(host)
}
target := net.JoinHostPort(host, fmt.Sprintf("%d", port))
connStr := fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=disable&executionId=%s", username, password, target, dbName, executionId)
db, err := sql.Open(pgwrap.PGWrapDriver, connStr)
if err != nil {
return nil, err
}
defer func() {
_ = db.Close()
}()
rows, err := db.Query(query)
if err != nil {
return nil, err
}
resp, err := utils.UnmarshalSQLRows(rows)
if err != nil {
return nil, err
}
return resp, nil
}
// ConnectWithDB connects to Postgres database using given credentials and database name.
// If connection is successful, it returns true.
// If connection is unsuccessful, it returns false and error.
// The connection is closed after the function returns.
// @example
// ```javascript
// const postgres = require('nuclei/postgres');
// const client = new postgres.PGClient;
// const connected = client.ConnectWithDB('acme.com', 5432, 'username', 'password', 'dbname');
// ```
func (c *PGClient) ConnectWithDB(ctx context.Context, host string, port int, username string, password string, dbName string) (bool, error) {
ok, err := c.IsPostgres(ctx, host, port)
if err != nil {
return false, err
}
if !ok {
return false, fmt.Errorf("not a postgres service")
}
executionId := ctx.Value("executionId").(string)
return memoizedconnect(executionId, host, port, username, password, dbName)
}
// @memo
func connect(executionId string, host string, port int, username string, password string, dbName string) (bool, error) {
if host == "" || port <= 0 {
return false, fmt.Errorf("invalid host or port")
}
if !protocolstate.IsHostAllowed(executionId, host) {
// host is not valid according to network policy
return false, protocolstate.ErrHostDenied.Msgf(host)
}
target := net.JoinHostPort(host, fmt.Sprintf("%d", port))
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return false, fmt.Errorf("dialers not initialized for %s", executionId)
}
db := pg.Connect(&pg.Options{
Addr: target,
User: username,
Password: password,
Database: dbName,
Dialer: func(dialCtx context.Context, network, addr string) (net.Conn, error) {
return dialer.Fastdialer.Dial(dialCtx, network, addr)
},
IdleCheckFrequency: -1,
}).WithTimeout(10 * time.Second)
defer func() {
_ = db.Close()
}()
_, err := db.ExecContext(ctx, "select 1")
if err != nil {
switch true {
case strings.Contains(err.Error(), "connect: connection refused"):
fallthrough
case strings.Contains(err.Error(), "no pg_hba.conf entry for host"):
fallthrough
case strings.Contains(err.Error(), "network unreachable"):
fallthrough
case strings.Contains(err.Error(), "reset"):
fallthrough
case strings.Contains(err.Error(), "i/o timeout"):
return false, err
}
return false, nil
}
return true, nil
}
================================================
FILE: pkg/js/libs/rdp/memo.rdp.go
================================================
// Warning - This is generated code
package rdp
import (
"errors"
"fmt"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
func memoizedisRDP(executionId string, host string, port int) (IsRDPResponse, error) {
hash := "isRDP" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return isRDP(executionId, host, port)
})
if err != nil {
return IsRDPResponse{}, err
}
if value, ok := v.(IsRDPResponse); ok {
return value, nil
}
return IsRDPResponse{}, errors.New("could not convert cached result")
}
func memoizedcheckRDPAuth(executionId string, host string, port int) (CheckRDPAuthResponse, error) {
hash := "checkRDPAuth" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return checkRDPAuth(executionId, host, port)
})
if err != nil {
return CheckRDPAuthResponse{}, err
}
if value, ok := v.(CheckRDPAuthResponse); ok {
return value, nil
}
return CheckRDPAuthResponse{}, errors.New("could not convert cached result")
}
func memoizedcheckRDPEncryption(executionId string, host string, port int) (RDPEncryptionResponse, error) {
hash := "checkRDPEncryption" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return checkRDPEncryption(executionId, host, port)
})
if err != nil {
return RDPEncryptionResponse{}, err
}
if value, ok := v.(RDPEncryptionResponse); ok {
return value, nil
}
return RDPEncryptionResponse{}, errors.New("could not convert cached result")
}
================================================
FILE: pkg/js/libs/rdp/rdp.go
================================================
package rdp
import (
"context"
"fmt"
"net"
"strconv"
"time"
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/rdp"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
type (
// IsRDPResponse is the response from the IsRDP function.
// this is returned by IsRDP function.
// @example
// ```javascript
// const rdp = require('nuclei/rdp');
// const isRDP = rdp.IsRDP('acme.com', 3389);
// log(toJSON(isRDP));
// ```
IsRDPResponse struct {
IsRDP bool
OS string
}
)
// IsRDP checks if the given host and port are running rdp server.
// If connection is successful, it returns true.
// If connection is unsuccessful, it returns false and error.
// The Name of the OS is also returned if the connection is successful.
// @example
// ```javascript
// const rdp = require('nuclei/rdp');
// const isRDP = rdp.IsRDP('acme.com', 3389);
// log(toJSON(isRDP));
// ```
func IsRDP(ctx context.Context, host string, port int) (IsRDPResponse, error) {
executionId := ctx.Value("executionId").(string)
return memoizedisRDP(executionId, host, port)
}
// @memo
func isRDP(executionId string, host string, port int) (IsRDPResponse, error) {
resp := IsRDPResponse{}
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return IsRDPResponse{}, fmt.Errorf("dialers not initialized for %s", executionId)
}
timeout := 5 * time.Second
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port))
if err != nil {
return resp, err
}
defer func() {
_ = conn.Close()
}()
server, isRDP, err := rdp.DetectRDP(conn, timeout)
if err != nil {
return resp, err
}
if !isRDP {
return resp, nil
}
resp.IsRDP = true
resp.OS = server
return resp, nil
}
type (
// CheckRDPAuthResponse is the response from the CheckRDPAuth function.
// this is returned by CheckRDPAuth function.
// @example
// ```javascript
// const rdp = require('nuclei/rdp');
// const checkRDPAuth = rdp.CheckRDPAuth('acme.com', 3389);
// log(toJSON(checkRDPAuth));
// ```
CheckRDPAuthResponse struct {
PluginInfo *plugins.ServiceRDP
Auth bool
}
)
// CheckRDPAuth checks if the given host and port are running rdp server
// with authentication and returns their metadata.
// If connection is successful, it returns true.
// @example
// ```javascript
// const rdp = require('nuclei/rdp');
// const checkRDPAuth = rdp.CheckRDPAuth('acme.com', 3389);
// log(toJSON(checkRDPAuth));
// ```
func CheckRDPAuth(ctx context.Context, host string, port int) (CheckRDPAuthResponse, error) {
executionId := ctx.Value("executionId").(string)
return memoizedcheckRDPAuth(executionId, host, port)
}
// @memo
func checkRDPAuth(executionId string, host string, port int) (CheckRDPAuthResponse, error) {
resp := CheckRDPAuthResponse{}
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return CheckRDPAuthResponse{}, fmt.Errorf("dialers not initialized for %s", executionId)
}
timeout := 5 * time.Second
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port))
if err != nil {
return resp, err
}
defer func() {
_ = conn.Close()
}()
pluginInfo, auth, err := rdp.DetectRDPAuth(conn, timeout)
if err != nil {
return resp, err
}
if !auth {
return resp, nil
}
resp.Auth = true
resp.PluginInfo = pluginInfo
return resp, nil
}
type (
SecurityLayer string
)
const (
SecurityLayerNativeRDP = "NativeRDP"
SecurityLayerSSL = "SSL"
SecurityLayerCredSSP = "CredSSP"
SecurityLayerRDSTLS = "RDSTLS"
SecurityLayerCredSSPWithEarlyUserAuth = "CredSSPWithEarlyUserAuth"
)
type (
EncryptionLevel string
)
const (
EncryptionLevelRC4_40bit = "RC4_40bit"
EncryptionLevelRC4_56bit = "RC4_56bit"
EncryptionLevelRC4_128bit = "RC4_128bit"
EncryptionLevelFIPS140_1 = "FIPS140_1"
)
type (
// RDPEncryptionResponse is the response from the CheckRDPEncryption function.
// This is returned by CheckRDPEncryption function.
// @example
// ```javascript
// const rdp = require('nuclei/rdp');
// const encryption = rdp.CheckRDPEncryption('acme.com', 3389);
// log(toJSON(encryption));
// ```
RDPEncryptionResponse struct {
// Protocols
NativeRDP bool
SSL bool
CredSSP bool
RDSTLS bool
CredSSPWithEarlyUserAuth bool
// EncryptionLevels
RC4_40bit bool
RC4_56bit bool
RC4_128bit bool
FIPS140_1 bool
}
)
// CheckRDPEncryption checks the RDP server's supported security layers and encryption levels.
// It tests different protocols and ciphers to determine what is supported.
// @example
// ```javascript
// const rdp = require('nuclei/rdp');
// const encryption = rdp.CheckRDPEncryption('acme.com', 3389);
// log(toJSON(encryption));
// ```
func CheckRDPEncryption(ctx context.Context, host string, port int) (RDPEncryptionResponse, error) {
executionId := ctx.Value("executionId").(string)
return memoizedcheckRDPEncryption(executionId, host, port)
}
// @memo
func checkRDPEncryption(executionId string, host string, port int) (RDPEncryptionResponse, error) {
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return RDPEncryptionResponse{}, fmt.Errorf("dialers not initialized for %s", executionId)
}
resp := RDPEncryptionResponse{}
defaultTimeout := 5 * time.Second
// Test different security protocols
protocols := map[SecurityLayer]int{
SecurityLayerNativeRDP: 0,
SecurityLayerSSL: 1,
SecurityLayerCredSSP: 3,
SecurityLayerRDSTLS: 4,
SecurityLayerCredSSPWithEarlyUserAuth: 8,
}
for name, value := range protocols {
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
conn, err := dialer.Fastdialer.Dial(ctx, "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
if err != nil {
continue
}
defer func() {
_ = conn.Close()
}()
// Test protocol
isRDP, err := testRDPProtocol(conn, value)
if err == nil && isRDP {
switch SecurityLayer(name) {
case SecurityLayerNativeRDP:
resp.NativeRDP = true
case SecurityLayerSSL:
resp.SSL = true
case SecurityLayerCredSSP:
resp.CredSSP = true
case SecurityLayerRDSTLS:
resp.RDSTLS = true
case SecurityLayerCredSSPWithEarlyUserAuth:
resp.CredSSPWithEarlyUserAuth = true
}
}
}
// Test different encryption levels
ciphers := map[EncryptionLevel]int{
EncryptionLevelRC4_40bit: 1,
EncryptionLevelRC4_56bit: 8,
EncryptionLevelRC4_128bit: 2,
EncryptionLevelFIPS140_1: 16,
}
for encryptionLevel, value := range ciphers {
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
conn, err := dialer.Fastdialer.Dial(ctx, "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
if err != nil {
continue
}
defer func() {
_ = conn.Close()
}()
// Test cipher
isRDP, err := testRDPCipher(conn, value)
if err == nil && isRDP {
switch encryptionLevel {
case EncryptionLevelRC4_40bit:
resp.RC4_40bit = true
case EncryptionLevelRC4_56bit:
resp.RC4_56bit = true
case EncryptionLevelRC4_128bit:
resp.RC4_128bit = true
case EncryptionLevelFIPS140_1:
resp.FIPS140_1 = true
}
}
}
return resp, nil
}
// testRDPProtocol tests RDP with a specific security protocol
func testRDPProtocol(conn net.Conn, protocol int) (bool, error) {
// Send RDP connection request with specific protocol
// This is a simplified version - in reality you'd need to implement the full RDP protocol
// including the negotiation phase with the specified protocol
_, err := conn.Write([]byte{0x03, 0x00, 0x00, 0x13, 0x0e, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, byte(protocol), 0x00, 0x08, 0x00, 0x03, 0x00, 0x00, 0x00})
if err != nil {
return false, err
}
// Read response
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
return false, err
}
// Check if response indicates RDP
if n >= 19 && buf[0] == 0x03 && buf[1] == 0x00 && buf[2] == 0x00 {
// For CredSSP and CredSSP with Early User Auth, we need to check for NLA support
if protocol == 3 || protocol == 8 {
// Check for NLA support in the response
if n >= 19 && buf[18]&0x01 != 0 {
return true, nil
}
return false, nil
}
return true, nil
}
return false, nil
}
// testRDPCipher tests RDP with a specific encryption level
func testRDPCipher(conn net.Conn, cipher int) (bool, error) {
// Send RDP connection request with specific cipher
// This is a simplified version - in reality you'd need to implement the full RDP protocol
// including the negotiation phase with the specified cipher
_, err := conn.Write([]byte{0x03, 0x00, 0x00, 0x13, 0x0e, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, byte(cipher), 0x03, 0x00, 0x00, 0x00})
if err != nil {
return false, err
}
// Read response
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
return false, err
}
// Check if response indicates RDP
if n >= 19 && buf[0] == 0x03 && buf[1] == 0x00 && buf[2] == 0x00 {
// Check for encryption level support in the response
if n >= 19 && buf[18]&byte(cipher) != 0 {
return true, nil
}
return false, nil
}
return false, nil
}
================================================
FILE: pkg/js/libs/redis/memo.redis.go
================================================
// Warning - This is generated code
package redis
import (
"errors"
"fmt"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
func memoizedgetServerInfo(executionId string, host string, port int) (string, error) {
hash := "getServerInfo" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return getServerInfo(executionId, host, port)
})
if err != nil {
return "", err
}
if value, ok := v.(string); ok {
return value, nil
}
return "", errors.New("could not convert cached result")
}
func memoizedconnect(executionId string, host string, port int, password string) (bool, error) {
hash := "connect" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(password)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return connect(executionId, host, port, password)
})
if err != nil {
return false, err
}
if value, ok := v.(bool); ok {
return value, nil
}
return false, errors.New("could not convert cached result")
}
func memoizedgetServerInfoAuth(executionId string, host string, port int, password string) (string, error) {
hash := "getServerInfoAuth" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(password)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return getServerInfoAuth(executionId, host, port, password)
})
if err != nil {
return "", err
}
if value, ok := v.(string); ok {
return value, nil
}
return "", errors.New("could not convert cached result")
}
func memoizedisAuthenticated(executionId string, host string, port int) (bool, error) {
hash := "isAuthenticated" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return isAuthenticated(executionId, host, port)
})
if err != nil {
return false, err
}
if value, ok := v.(bool); ok {
return value, nil
}
return false, errors.New("could not convert cached result")
}
================================================
FILE: pkg/js/libs/redis/redis.go
================================================
package redis
import (
"context"
"fmt"
"time"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/redis/go-redis/v9"
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
pluginsredis "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/redis"
)
// GetServerInfo returns the server info for a redis server
// @example
// ```javascript
// const redis = require('nuclei/redis');
// const info = redis.GetServerInfo('acme.com', 6379);
// ```
func GetServerInfo(ctx context.Context, host string, port int) (string, error) {
executionId := ctx.Value("executionId").(string)
return memoizedgetServerInfo(executionId, host, port)
}
// @memo
func getServerInfo(executionId string, host string, port int) (string, error) {
if !protocolstate.IsHostAllowed(executionId, host) {
// host is not valid according to network policy
return "", protocolstate.ErrHostDenied.Msgf(host)
}
// create a new client
client := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", host, port),
Password: "", // no password set
DB: 0, // use default DB
})
defer func() {
_ = client.Close()
}()
// Ping the Redis server
_, err := client.Ping(context.TODO()).Result()
if err != nil {
return "", err
}
// Get Redis server info
infoCmd := client.Info(context.TODO())
if infoCmd.Err() != nil {
return "", infoCmd.Err()
}
return infoCmd.Val(), nil
}
// Connect tries to connect redis server with password
// @example
// ```javascript
// const redis = require('nuclei/redis');
// const connected = redis.Connect('acme.com', 6379, 'password');
// ```
func Connect(ctx context.Context, host string, port int, password string) (bool, error) {
executionId := ctx.Value("executionId").(string)
return memoizedconnect(executionId, host, port, password)
}
// @memo
func connect(executionId string, host string, port int, password string) (bool, error) {
if !protocolstate.IsHostAllowed(executionId, host) {
// host is not valid according to network policy
return false, protocolstate.ErrHostDenied.Msgf(host)
}
// create a new client
client := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", host, port),
Password: password, // no password set
DB: 0, // use default DB
})
defer func() {
_ = client.Close()
}()
_, err := client.Ping(context.TODO()).Result()
if err != nil {
return false, err
}
// Get Redis server info
infoCmd := client.Info(context.TODO())
if infoCmd.Err() != nil {
return false, infoCmd.Err()
}
return true, nil
}
// GetServerInfoAuth returns the server info for a redis server
// @example
// ```javascript
// const redis = require('nuclei/redis');
// const info = redis.GetServerInfoAuth('acme.com', 6379, 'password');
// ```
func GetServerInfoAuth(ctx context.Context, host string, port int, password string) (string, error) {
executionId := ctx.Value("executionId").(string)
return memoizedgetServerInfoAuth(executionId, host, port, password)
}
// @memo
func getServerInfoAuth(executionId string, host string, port int, password string) (string, error) {
if !protocolstate.IsHostAllowed(executionId, host) {
// host is not valid according to network policy
return "", protocolstate.ErrHostDenied.Msgf(host)
}
// create a new client
client := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", host, port),
Password: password, // no password set
DB: 0, // use default DB
})
defer func() {
_ = client.Close()
}()
// Ping the Redis server
_, err := client.Ping(context.TODO()).Result()
if err != nil {
return "", err
}
// Get Redis server info
infoCmd := client.Info(context.TODO())
if infoCmd.Err() != nil {
return "", infoCmd.Err()
}
return infoCmd.Val(), nil
}
// IsAuthenticated checks if the redis server requires authentication
// @example
// ```javascript
// const redis = require('nuclei/redis');
// const isAuthenticated = redis.IsAuthenticated('acme.com', 6379);
// ```
func IsAuthenticated(ctx context.Context, host string, port int) (bool, error) {
executionId := ctx.Value("executionId").(string)
return memoizedisAuthenticated(executionId, host, port)
}
// @memo
func isAuthenticated(executionId string, host string, port int) (bool, error) {
plugin := pluginsredis.REDISPlugin{}
timeout := 5 * time.Second
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return false, fmt.Errorf("dialers not initialized for %s", executionId)
}
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port))
if err != nil {
return false, err
}
defer func() {
_ = conn.Close()
}()
_, err = plugin.Run(conn, timeout, plugins.Target{Host: host})
if err != nil {
return false, err
}
return true, nil
}
// RunLuaScript runs a lua script on the redis server
// @example
// ```javascript
// const redis = require('nuclei/redis');
// const result = redis.RunLuaScript('acme.com', 6379, 'password', 'return redis.call("get", KEYS[1])');
// ```
func RunLuaScript(ctx context.Context, host string, port int, password string, script string) (interface{}, error) {
executionId := ctx.Value("executionId").(string)
if !protocolstate.IsHostAllowed(executionId, host) {
// host is not valid according to network policy
return false, protocolstate.ErrHostDenied.Msgf(host)
}
// create a new client
client := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", host, port),
Password: password,
DB: 0, // use default DB
})
defer func() {
_ = client.Close()
}()
// Ping the Redis server
_, err := client.Ping(context.TODO()).Result()
if err != nil {
return "", err
}
// Get Redis server info
infoCmd := client.Eval(context.Background(), script, []string{})
if infoCmd.Err() != nil {
return "", infoCmd.Err()
}
return infoCmd.Val(), nil
}
================================================
FILE: pkg/js/libs/rsync/memo.rsync.go
================================================
// Warning - This is generated code
package rsync
import (
"errors"
"fmt"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
func memoizedisRsync(executionId string, host string, port int) (IsRsyncResponse, error) {
hash := "isRsync" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return isRsync(executionId, host, port)
})
if err != nil {
return IsRsyncResponse{}, err
}
if value, ok := v.(IsRsyncResponse); ok {
return value, nil
}
return IsRsyncResponse{}, errors.New("could not convert cached result")
}
================================================
FILE: pkg/js/libs/rsync/rsync.go
================================================
package rsync
import (
"bytes"
"context"
"fmt"
"log/slog"
"net"
"strconv"
"time"
rsynclib "github.com/Mzack9999/go-rsync/rsync"
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/rsync"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
type (
// RsyncClient is a client for RSYNC servers.
// Internally client uses https://github.com/gokrazy/rsync driver.
// @example
// ```javascript
// const rsync = require('nuclei/rsync');
// const client = new rsync.RsyncClient();
// ```
RsyncClient struct{}
// IsRsyncResponse is the response from the IsRsync function.
// this is returned by IsRsync function.
// @example
// ```javascript
// const rsync = require('nuclei/rsync');
// const isRsync = rsync.IsRsync('acme.com', 873);
// log(toJSON(isRsync));
// ```
IsRsyncResponse struct {
IsRsync bool
Banner string
}
// ListSharesResponse is the response from the ListShares function.
// this is returned by ListShares function.
// @example
// ```javascript
// const rsync = require('nuclei/rsync');
// const client = new rsync.RsyncClient();
// const listShares = client.ListShares('acme.com', 873);
// log(toJSON(listShares));
RsyncListResponse struct {
Modules []string
Files []string
Output string
}
)
func connectWithFastDialer(executionId string, host string, port int) (net.Conn, error) {
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return nil, fmt.Errorf("dialers not initialized for %s", executionId)
}
return dialer.Fastdialer.Dial(context.Background(), "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
}
// IsRsync checks if a host is running a Rsync server.
// @example
// ```javascript
// const rsync = require('nuclei/rsync');
// const isRsync = rsync.IsRsync('acme.com', 873);
// log(toJSON(isRsync));
// ```
func IsRsync(ctx context.Context, host string, port int) (IsRsyncResponse, error) {
executionId := ctx.Value("executionId").(string)
return memoizedisRsync(executionId, host, port)
}
// @memo
func isRsync(executionId string, host string, port int) (IsRsyncResponse, error) {
resp := IsRsyncResponse{}
timeout := 5 * time.Second
conn, err := connectWithFastDialer(executionId, host, port)
if err != nil {
return resp, err
}
defer func() {
_ = conn.Close()
}()
rsyncPlugin := rsync.RSYNCPlugin{}
service, err := rsyncPlugin.Run(conn, timeout, plugins.Target{Host: host})
if err != nil {
return resp, nil
}
if service == nil {
return resp, nil
}
resp.Banner = service.Version
resp.IsRsync = true
return resp, nil
}
// ListModules lists the modules of a Rsync server.
// @example
// ```javascript
// const rsync = require('nuclei/rsync');
// const client = new rsync.RsyncClient();
// const listModules = client.ListModules('acme.com', 873, 'username', 'password');
// log(toJSON(listModules));
// ```
func (c *RsyncClient) ListModules(ctx context.Context, host string, port int, username string, password string) (RsyncListResponse, error) {
executionId := ctx.Value("executionId").(string)
return listModules(executionId, host, port, username, password)
}
// ListShares lists the shares of a Rsync server.
// @example
// ```javascript
// const rsync = require('nuclei/rsync');
// const client = new rsync.RsyncClient();
// const listShares = client.ListFilesInModule('acme.com', 873, 'username', 'password', '/');
// log(toJSON(listShares));
// ```
func (c *RsyncClient) ListFilesInModule(ctx context.Context, host string, port int, username string, password string, module string) (RsyncListResponse, error) {
executionId := ctx.Value("executionId").(string)
return listFilesInModule(executionId, host, port, username, password, module)
}
func listModules(executionId string, host string, port int, username string, password string) (RsyncListResponse, error) {
fastDialer := protocolstate.GetDialersWithId(executionId)
if fastDialer == nil {
return RsyncListResponse{}, fmt.Errorf("dialers not initialized for %s", executionId)
}
address := net.JoinHostPort(host, strconv.Itoa(port))
// Create a bytes buffer for logging
var logBuffer bytes.Buffer
// Create a custom slog handler that writes to the buffer
logHandler := slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{
Level: slog.LevelDebug,
})
// Create a logger that writes to our buffer
logger := slog.New(logHandler)
sr, err := rsynclib.ListModules(address,
rsynclib.WithClientAuth(username, password),
rsynclib.WithLogger(logger),
rsynclib.WithFastDialer(fastDialer.Fastdialer),
)
if err != nil {
return RsyncListResponse{}, fmt.Errorf("connect failed: %v", err)
}
result := RsyncListResponse{
Modules: make([]string, len(sr)),
Output: logBuffer.String(),
}
for i, item := range sr {
result.Modules[i] = string(item.Name)
}
return result, nil
}
func listFilesInModule(executionId string, host string, port int, username string, password string, module string) (RsyncListResponse, error) {
fastDialer := protocolstate.GetDialersWithId(executionId)
if fastDialer == nil {
return RsyncListResponse{}, fmt.Errorf("dialers not initialized for %s", executionId)
}
address := net.JoinHostPort(host, strconv.Itoa(port))
// Create a bytes buffer for logging
var logBuffer bytes.Buffer
// Create a custom slog handler that writes to the buffer
logHandler := slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{
Level: slog.LevelDebug,
})
// Create a logger that writes to our buffer
logger := slog.New(logHandler)
sr, err := rsynclib.SocketClient(nil, address, module, ".",
rsynclib.WithClientAuth(username, password),
rsynclib.WithLogger(logger),
rsynclib.WithFastDialer(fastDialer.Fastdialer),
)
if err != nil {
return RsyncListResponse{}, fmt.Errorf("connect failed: %v", err)
}
// Try to list files to test authentication
list, err := sr.List()
if err != nil {
return RsyncListResponse{}, fmt.Errorf("authentication failed: %v", err)
}
result := RsyncListResponse{
Files: make([]string, len(list)),
Output: logBuffer.String(),
}
for i, item := range list {
result.Files[i] = string(item.Path)
}
return result, nil
}
================================================
FILE: pkg/js/libs/smb/memo.smb.go
================================================
// Warning - This is generated code
package smb
import (
"errors"
"fmt"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/zmap/zgrab2/lib/smb/smb"
)
func memoizedconnectSMBInfoMode(executionId string, host string, port int) (*smb.SMBLog, error) {
hash := "connectSMBInfoMode" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return connectSMBInfoMode(executionId, host, port)
})
if err != nil {
return nil, err
}
if value, ok := v.(*smb.SMBLog); ok {
return value, nil
}
return nil, errors.New("could not convert cached result")
}
func memoizedlistShares(executionId string, host string, port int, user string, password string) ([]string, error) {
hash := "listShares" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(user) + ":" + fmt.Sprint(password)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return listShares(executionId, host, port, user, password)
})
if err != nil {
return []string{}, err
}
if value, ok := v.([]string); ok {
return value, nil
}
return []string{}, errors.New("could not convert cached result")
}
================================================
FILE: pkg/js/libs/smb/memo.smb_private.go
================================================
// Warning - This is generated code
package smb
import (
"errors"
"fmt"
"time"
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
func memoizedcollectSMBv2Metadata(executionId string, host string, port int, timeout time.Duration) (*plugins.ServiceSMB, error) {
hash := "collectSMBv2Metadata" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port) + ":" + fmt.Sprint(timeout)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return collectSMBv2Metadata(executionId, host, port, timeout)
})
if err != nil {
return nil, err
}
if value, ok := v.(*plugins.ServiceSMB); ok {
return value, nil
}
return nil, errors.New("could not convert cached result")
}
================================================
FILE: pkg/js/libs/smb/memo.smbghost.go
================================================
// Warning - This is generated code
package smb
import (
"errors"
"fmt"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
func memoizeddetectSMBGhost(executionId string, host string, port int) (bool, error) {
hash := "detectSMBGhost" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return detectSMBGhost(executionId, host, port)
})
if err != nil {
return false, err
}
if value, ok := v.(bool); ok {
return value, nil
}
return false, errors.New("could not convert cached result")
}
================================================
FILE: pkg/js/libs/smb/smb.go
================================================
package smb
import (
"context"
"fmt"
"time"
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
"github.com/projectdiscovery/go-smb2"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/zmap/zgrab2/lib/smb/smb"
)
type (
// SMBClient is a client for SMB servers.
// Internally client uses github.com/zmap/zgrab2/lib/smb/smb driver.
// github.com/projectdiscovery/go-smb2 driver
// @example
// ```javascript
// const smb = require('nuclei/smb');
// const client = new smb.SMBClient();
// ```
SMBClient struct{}
)
// ConnectSMBInfoMode tries to connect to provided host and port
// and discovery SMB information
// Returns handshake log and error. If error is not nil,
// state will be false
// @example
// ```javascript
// const smb = require('nuclei/smb');
// const client = new smb.SMBClient();
// const info = client.ConnectSMBInfoMode('acme.com', 445);
// log(to_json(info));
// ```
func (c *SMBClient) ConnectSMBInfoMode(ctx context.Context, host string, port int) (*smb.SMBLog, error) {
executionId := ctx.Value("executionId").(string)
return memoizedconnectSMBInfoMode(executionId, host, port)
}
// @memo
func connectSMBInfoMode(executionId string, host string, port int) (*smb.SMBLog, error) {
if !protocolstate.IsHostAllowed(executionId, host) {
// host is not valid according to network policy
return nil, protocolstate.ErrHostDenied.Msgf(host)
}
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return nil, fmt.Errorf("dialers not initialized for %s", executionId)
}
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port))
if err != nil {
return nil, err
}
// try to get SMBv2/v3 info
result, err := getSMBInfo(conn, true, false)
_ = conn.Close() // close regardless of error
if err == nil {
return result, nil
}
// try to negotiate SMBv1
conn, err = dialer.Fastdialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port))
if err != nil {
return nil, err
}
defer func() {
_ = conn.Close()
}()
result, err = getSMBInfo(conn, true, true)
if err != nil {
return result, nil
}
return result, nil
}
// ListSMBv2Metadata tries to connect to provided host and port
// and list SMBv2 metadata.
// Returns metadata and error. If error is not nil,
// state will be false
// @example
// ```javascript
// const smb = require('nuclei/smb');
// const client = new smb.SMBClient();
// const metadata = client.ListSMBv2Metadata('acme.com', 445);
// log(to_json(metadata));
// ```
func (c *SMBClient) ListSMBv2Metadata(ctx context.Context, host string, port int) (*plugins.ServiceSMB, error) {
executionId := ctx.Value("executionId").(string)
if !protocolstate.IsHostAllowed(executionId, host) {
// host is not valid according to network policy
return nil, protocolstate.ErrHostDenied.Msgf(host)
}
return memoizedcollectSMBv2Metadata(executionId, host, port, 5*time.Second)
}
// ListShares tries to connect to provided host and port
// and list shares by using given credentials.
// Credentials cannot be blank. guest or anonymous credentials
// can be used by providing empty password.
// @example
// ```javascript
// const smb = require('nuclei/smb');
// const client = new smb.SMBClient();
// const shares = client.ListShares('acme.com', 445, 'username', 'password');
//
// for (const share of shares) {
// log(share);
// }
//
// ```
func (c *SMBClient) ListShares(ctx context.Context, host string, port int, user, password string) ([]string, error) {
executionId := ctx.Value("executionId").(string)
return memoizedlistShares(executionId, host, port, user, password)
}
// @memo
func listShares(executionId string, host string, port int, user string, password string) ([]string, error) {
if !protocolstate.IsHostAllowed(executionId, host) {
// host is not valid according to network policy
return nil, protocolstate.ErrHostDenied.Msgf(host)
}
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return nil, fmt.Errorf("dialers not initialized for %s", executionId)
}
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", fmt.Sprintf("%s:%d", host, port))
if err != nil {
return nil, err
}
defer func() {
_ = conn.Close()
}()
d := &smb2.Dialer{
Initiator: &smb2.NTLMInitiator{
User: user,
Password: password,
},
}
s, err := d.Dial(conn)
if err != nil {
return nil, err
}
defer func() {
_ = s.Logoff()
}()
names, err := s.ListSharenames()
if err != nil {
return nil, err
}
return names, nil
}
================================================
FILE: pkg/js/libs/smb/smb_private.go
================================================
package smb
import (
"context"
"fmt"
"net"
"time"
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/smb"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
zgrabsmb "github.com/zmap/zgrab2/lib/smb/smb"
)
// ==== private helper functions/methods ====
// collectSMBv2Metadata collects metadata for SMBv2 services.
// @memo
func collectSMBv2Metadata(executionId string, host string, port int, timeout time.Duration) (*plugins.ServiceSMB, error) {
if timeout == 0 {
timeout = 5 * time.Second
}
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return nil, fmt.Errorf("dialers not initialized for %s", executionId)
}
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, fmt.Sprintf("%d", port)))
if err != nil {
return nil, err
}
defer func() {
_ = conn.Close()
}()
metadata, err := smb.DetectSMBv2(conn, timeout)
if err != nil {
return nil, err
}
return metadata, nil
}
// getSMBInfo
func getSMBInfo(conn net.Conn, setupSession, v1 bool) (*zgrabsmb.SMBLog, error) {
_ = conn.SetDeadline(time.Now().Add(10 * time.Second))
defer func() {
_ = conn.SetDeadline(time.Time{})
}()
result, err := zgrabsmb.GetSMBLog(conn, setupSession, v1, false)
if err != nil {
return nil, err
}
return result, nil
}
================================================
FILE: pkg/js/libs/smb/smbghost.go
================================================
package smb
import (
"bytes"
"context"
"errors"
"fmt"
"net"
"strconv"
"time"
"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/structs"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/utils/reader"
)
const (
pkt = "\x00\x00\x00\xc0\xfeSMB@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00\x08\x00\x01\x00\x00\x00\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00x\x00\x00\x00\x02\x00\x00\x00\x02\x02\x10\x02\"\x02$\x02\x00\x03\x02\x03\x10\x03\x11\x03\x00\x00\x00\x00\x01\x00&\x00\x00\x00\x00\x00\x01\x00 \x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\n\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00"
)
// DetectSMBGhost tries to detect SMBGhost vulnerability
// by using SMBv3 compression feature.
// If the host is vulnerable, it returns true.
// @example
// ```javascript
// const smb = require('nuclei/smb');
// const isSMBGhost = smb.DetectSMBGhost('acme.com', 445);
// ```
func (c *SMBClient) DetectSMBGhost(ctx context.Context, host string, port int) (bool, error) {
executionId := ctx.Value("executionId").(string)
return memoizeddetectSMBGhost(executionId, host, port)
}
// @memo
func detectSMBGhost(executionId string, host string, port int) (bool, error) {
if !protocolstate.IsHostAllowed(executionId, host) {
// host is not valid according to network policy
return false, protocolstate.ErrHostDenied.Msgf(host)
}
addr := net.JoinHostPort(host, strconv.Itoa(port))
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return false, fmt.Errorf("dialers not initialized for %s", executionId)
}
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", addr)
if err != nil {
return false, err
}
defer func() {
_ = conn.Close()
}()
_, err = conn.Write([]byte(pkt))
if err != nil {
return false, err
}
buff, _ := reader.ConnReadNWithTimeout(conn, 4, time.Duration(5)*time.Second)
args, err := structs.Unpack(">I", buff)
if err != nil {
return false, err
}
if len(args) != 1 {
return false, errors.New("invalid response")
}
length := args[0].(int)
_ = conn.SetReadDeadline(time.Now().Add(2 * time.Second))
data, err := reader.ConnReadNWithTimeout(conn, int64(length), time.Duration(5)*time.Second)
if err != nil {
return false, err
}
if len(data) < 72 {
return false, errors.New("invalid response expected at least 72 bytes")
}
if !bytes.Equal(data[68:70], []byte("\x11\x03")) || !bytes.Equal(data[70:72], []byte("\x02\x00")) {
return false, nil
}
return true, nil
}
================================================
FILE: pkg/js/libs/smtp/msg.go
================================================
package smtp
import (
"bufio"
"bytes"
"net/textproto"
"strings"
)
type (
// SMTPMessage is a message to be sent over SMTP
// @example
// ```javascript
// const smtp = require('nuclei/smtp');
// const message = new smtp.SMTPMessage();
// message.From('xyz@projectdiscovery.io');
// ```
SMTPMessage struct {
from string
to []string
sub string
msg []byte
user string
pass string
}
)
// From adds the from field to the message
// @example
// ```javascript
// const smtp = require('nuclei/smtp');
// const message = new smtp.SMTPMessage();
// message.From('xyz@projectdiscovery.io');
// ```
func (s *SMTPMessage) From(email string) *SMTPMessage {
s.from = email
return s
}
// To adds the to field to the message
// @example
// ```javascript
// const smtp = require('nuclei/smtp');
// const message = new smtp.SMTPMessage();
// message.To('xyz@projectdiscovery.io');
// ```
func (s *SMTPMessage) To(email string) *SMTPMessage {
s.to = append(s.to, email)
return s
}
// Subject adds the subject field to the message
// @example
// ```javascript
// const smtp = require('nuclei/smtp');
// const message = new smtp.SMTPMessage();
// message.Subject('hello');
// ```
func (s *SMTPMessage) Subject(sub string) *SMTPMessage {
s.sub = sub
return s
}
// Body adds the message body to the message
// @example
// ```javascript
// const smtp = require('nuclei/smtp');
// const message = new smtp.SMTPMessage();
// message.Body('hello');
// ```
func (s *SMTPMessage) Body(msg []byte) *SMTPMessage {
s.msg = msg
return s
}
// Auth when called authenticates using username and password before sending the message
// @example
// ```javascript
// const smtp = require('nuclei/smtp');
// const message = new smtp.SMTPMessage();
// message.Auth('username', 'password');
// ```
func (s *SMTPMessage) Auth(username, password string) *SMTPMessage {
s.user = username
s.pass = password
return s
}
// String returns the string representation of the message
// @example
// ```javascript
// const smtp = require('nuclei/smtp');
// const message = new smtp.SMTPMessage();
// message.From('xyz@projectdiscovery.io');
// message.To('xyz2@projectdiscoveyr.io');
// message.Subject('hello');
// message.Body('hello');
// log(message.String());
// ```
func (s *SMTPMessage) String() string {
var buff bytes.Buffer
tw := textproto.NewWriter(bufio.NewWriter(&buff))
_ = tw.PrintfLine("To: %s", strings.Join(s.to, ","))
if s.sub != "" {
_ = tw.PrintfLine("Subject: %s", s.sub)
}
_ = tw.PrintfLine("\r\n%s", s.msg)
return buff.String()
}
================================================
FILE: pkg/js/libs/smtp/smtp.go
================================================
package smtp
import (
"context"
"fmt"
"net"
"net/smtp"
"strconv"
"time"
"github.com/Mzack9999/goja"
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
"github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
pluginsmtp "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/smtp"
)
type (
// SMTPResponse is the response from the IsSMTP function.
// @example
// ```javascript
// const smtp = require('nuclei/smtp');
// const client = new smtp.Client('acme.com', 25);
// const isSMTP = client.IsSMTP();
// log(isSMTP)
// ```
SMTPResponse struct {
IsSMTP bool
Banner string
}
)
type (
// Client is a minimal SMTP client for nuclei scripts.
// @example
// ```javascript
// const smtp = require('nuclei/smtp');
// const client = new smtp.Client('acme.com', 25);
// ```
Client struct {
nj *utils.NucleiJS
host string
port string
}
)
// Constructor for SMTP Client
// Constructor: constructor(public host: string, public port: string)
func NewSMTPClient(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {
// setup nucleijs utils
c := &Client{nj: utils.NewNucleiJS(runtime)}
c.nj.ObjectSig = "Client(host, port)" // will be included in error messages
host, _ := c.nj.GetArg(call.Arguments, 0).(string) // host
port, _ := c.nj.GetArg(call.Arguments, 1).(string) // port
// validate arguments
c.nj.Require(host != "", "host cannot be empty")
c.nj.Require(port != "", "port cannot be empty")
// validate port
portInt, err := strconv.Atoi(port)
c.nj.Require(err == nil && portInt > 0 && portInt < 65536, "port must be a valid number")
c.host = host
c.port = port
executionId := c.nj.ExecutionId()
// check if this is allowed address
c.nj.Require(protocolstate.IsHostAllowed(executionId, host+":"+port), protocolstate.ErrHostDenied.Msgf(host+":"+port).Error())
// Link Constructor to Client and return
return utils.LinkConstructor(call, runtime, c)
}
// IsSMTP checks if a host is running a SMTP server.
// @example
// ```javascript
// const smtp = require('nuclei/smtp');
// const client = new smtp.Client('acme.com', 25);
// const isSMTP = client.IsSMTP();
// log(isSMTP)
// ```
func (c *Client) IsSMTP() (SMTPResponse, error) {
resp := SMTPResponse{}
c.nj.Require(c.host != "", "host cannot be empty")
c.nj.Require(c.port != "", "port cannot be empty")
timeout := 5 * time.Second
executionId := c.nj.ExecutionId()
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return SMTPResponse{}, fmt.Errorf("dialers not initialized for %s", executionId)
}
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(c.host, c.port))
if err != nil {
return resp, err
}
defer func() {
_ = conn.Close()
}()
smtpPlugin := pluginsmtp.SMTPPlugin{}
service, err := smtpPlugin.Run(conn, timeout, plugins.Target{Host: c.host})
if err != nil {
return resp, err
}
if service == nil {
return resp, nil
}
resp.Banner = service.Version
resp.IsSMTP = true
return resp, nil
}
// IsOpenRelay checks if a host is an open relay.
// @example
// ```javascript
// const smtp = require('nuclei/smtp');
// const message = new smtp.SMTPMessage();
// message.From('xyz@projectdiscovery.io');
// message.To('xyz2@projectdiscoveyr.io');
// message.Subject('hello');
// message.Body('hello');
// const client = new smtp.Client('acme.com', 25);
// const isRelay = client.IsOpenRelay(message);
// ```
func (c *Client) IsOpenRelay(msg *SMTPMessage) (bool, error) {
c.nj.Require(c.host != "", "host cannot be empty")
c.nj.Require(c.port != "", "port cannot be empty")
executionId := c.nj.ExecutionId()
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return false, fmt.Errorf("dialers not initialized for %s", executionId)
}
addr := net.JoinHostPort(c.host, c.port)
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", addr)
if err != nil {
return false, err
}
defer func() {
_ = conn.Close()
}()
client, err := smtp.NewClient(conn, c.host)
if err != nil {
return false, err
}
if err := client.Mail(msg.from); err != nil {
return false, err
}
if len(msg.to) == 0 || len(msg.to) > 1 {
return false, fmt.Errorf("invalid number of recipients: required 1, got %d", len(msg.to))
}
if err := client.Rcpt(msg.to[0]); err != nil {
return false, err
}
// Send the email body.
wc, err := client.Data()
if err != nil {
return false, err
}
_, err = wc.Write([]byte(msg.String()))
if err != nil {
return false, err
}
err = wc.Close()
if err != nil {
return false, err
}
// Send the QUIT command and close the connection.
err = client.Quit()
if err != nil {
return false, err
}
return true, nil
}
// SendMail sends an email using the SMTP protocol.
// @example
// ```javascript
// const smtp = require('nuclei/smtp');
// const message = new smtp.SMTPMessage();
// message.From('xyz@projectdiscovery.io');
// message.To('xyz2@projectdiscoveyr.io');
// message.Subject('hello');
// message.Body('hello');
// const client = new smtp.Client('acme.com', 25);
// const isSent = client.SendMail(message);
// log(isSent)
// ```
func (c *Client) SendMail(msg *SMTPMessage) (bool, error) {
c.nj.Require(c.host != "", "host cannot be empty")
c.nj.Require(c.port != "", "port cannot be empty")
var auth smtp.Auth
if msg.user != "" && msg.pass != "" {
auth = smtp.PlainAuth("", msg.user, msg.pass, c.host)
}
// send mail
addr := net.JoinHostPort(c.host, c.port)
if err := smtp.SendMail(addr, auth, msg.from, msg.to, []byte(msg.String())); err != nil {
c.nj.Throw("failed to send mail with message(%s) got %v", msg.String(), err)
}
return true, nil
}
================================================
FILE: pkg/js/libs/ssh/memo.ssh.go
================================================
// Warning - This is generated code
package ssh
import (
"errors"
"fmt"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/zmap/zgrab2/lib/ssh"
)
func memoizedconnectSSHInfoMode(opts *connectOptions) (*ssh.HandshakeLog, error) {
hash := "connectSSHInfoMode" + ":" + fmt.Sprint(opts)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return connectSSHInfoMode(opts)
})
if err != nil {
return nil, err
}
if value, ok := v.(*ssh.HandshakeLog); ok {
return value, nil
}
return nil, errors.New("could not convert cached result")
}
================================================
FILE: pkg/js/libs/ssh/ssh.go
================================================
package ssh
import (
"context"
"fmt"
"regexp"
"strings"
"time"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
"github.com/projectdiscovery/utils/errkit"
"github.com/zmap/zgrab2/lib/ssh"
)
type (
// SSHClient is a client for SSH servers.
// Internally client uses github.com/zmap/zgrab2/lib/ssh driver.
// @example
// ```javascript
// const ssh = require('nuclei/ssh');
// const client = new ssh.SSHClient();
// ```
SSHClient struct {
connection *ssh.Client
timeout time.Duration
}
)
// precompiled regex patterns
var (
passwordQuestionPattern = regexp.MustCompile(`(?i)(pass(word|phrase|code)?|pin)`)
usernameQuestionPattern = regexp.MustCompile(`(?i)(user(name)?|login)`)
)
// SetTimeout sets the timeout for the SSH connection in seconds
// @example
// ```javascript
// const ssh = require('nuclei/ssh');
// const client = new ssh.SSHClient();
// client.SetTimeout(10);
// ```
func (c *SSHClient) SetTimeout(sec int) {
c.timeout = time.Duration(sec) * time.Second
}
// Connect tries to connect to provided host and port
// with provided username and password with ssh.
// Returns state of connection and error. If error is not nil,
// state will be false
// @example
// ```javascript
// const ssh = require('nuclei/ssh');
// const client = new ssh.SSHClient();
// const connected = client.Connect('acme.com', 22, 'username', 'password');
// ```
func (c *SSHClient) Connect(ctx context.Context, host string, port int, username, password string) (bool, error) {
executionId := ctx.Value("executionId").(string)
conn, err := connect(&connectOptions{
Host: host,
Port: port,
User: username,
Password: password,
ExecutionId: executionId,
})
if err != nil {
return false, err
}
c.connection = conn
return true, nil
}
// ConnectWithKey tries to connect to provided host and port
// with provided username and private_key.
// Returns state of connection and error. If error is not nil,
// state will be false
// @example
// ```javascript
// const ssh = require('nuclei/ssh');
// const client = new ssh.SSHClient();
// const privateKey = `-----BEGIN RSA PRIVATE KEY----- ...`;
// const connected = client.ConnectWithKey('acme.com', 22, 'username', privateKey);
// ```
func (c *SSHClient) ConnectWithKey(ctx context.Context, host string, port int, username, key string) (bool, error) {
executionId := ctx.Value("executionId").(string)
conn, err := connect(&connectOptions{
Host: host,
Port: port,
User: username,
PrivateKey: key,
ExecutionId: executionId,
})
if err != nil {
return false, err
}
c.connection = conn
return true, nil
}
// ConnectSSHInfoMode tries to connect to provided host and port
// with provided host and port
// Returns HandshakeLog and error. If error is not nil,
// state will be false
// HandshakeLog is a struct that contains information about the
// ssh connection
// @example
// ```javascript
// const ssh = require('nuclei/ssh');
// const client = new ssh.SSHClient();
// const info = client.ConnectSSHInfoMode('acme.com', 22);
// log(to_json(info));
// ```
func (c *SSHClient) ConnectSSHInfoMode(ctx context.Context, host string, port int) (*ssh.HandshakeLog, error) {
executionId := ctx.Value("executionId").(string)
return memoizedconnectSSHInfoMode(&connectOptions{
Host: host,
Port: port,
ExecutionId: executionId,
})
}
// Run tries to open a new SSH session, then tries to execute
// the provided command in said session
// Returns string and error. If error is not nil,
// state will be false
// The string contains the command output
// @example
// ```javascript
// const ssh = require('nuclei/ssh');
// const client = new ssh.SSHClient();
// client.Connect('acme.com', 22, 'username', 'password');
// const output = client.Run('id');
// log(output);
// ```
func (c *SSHClient) Run(cmd string) (string, error) {
if c.connection == nil {
return "", errkit.New("no connection")
}
session, err := c.connection.NewSession()
if err != nil {
return "", err
}
defer func() {
_ = session.Close()
}()
data, err := session.Output(cmd)
if err != nil {
return "", err
}
return string(data), nil
}
// Close closes the SSH connection and destroys the client
// Returns the success state and error. If error is not nil,
// state will be false
// @example
// ```javascript
// const ssh = require('nuclei/ssh');
// const client = new ssh.SSHClient();
// client.Connect('acme.com', 22, 'username', 'password');
// const closed = client.Close();
// ```
func (c *SSHClient) Close() (bool, error) {
if err := c.connection.Close(); err != nil {
return false, err
}
return true, nil
}
// unexported functions
type connectOptions struct {
Host string
Port int
User string
Password string
PrivateKey string
Timeout time.Duration // default 10s
ExecutionId string
}
func (c *connectOptions) validate() error {
if c.Host == "" {
return errkit.New("host is required")
}
if c.Port <= 0 {
return errkit.New("port is required")
}
if !protocolstate.IsHostAllowed(c.ExecutionId, c.Host) {
// host is not valid according to network policy
return protocolstate.ErrHostDenied.Msgf(c.Host)
}
if c.Timeout == 0 {
c.Timeout = 10 * time.Second
}
return nil
}
// @memo
func connectSSHInfoMode(opts *connectOptions) (*ssh.HandshakeLog, error) {
if err := opts.validate(); err != nil {
return nil, err
}
data := new(ssh.HandshakeLog)
sshConfig := ssh.MakeSSHConfig()
sshConfig.Timeout = 10 * time.Second
sshConfig.ConnLog = data
sshConfig.DontAuthenticate = true
sshConfig.BannerCallback = func(banner string) error {
data.Banner = strings.TrimSpace(banner)
return nil
}
rhost := fmt.Sprintf("%s:%d", opts.Host, opts.Port)
client, err := ssh.Dial("tcp", rhost, sshConfig)
if err != nil {
return nil, err
}
defer func() {
_ = client.Close()
}()
return data, nil
}
func connect(opts *connectOptions) (*ssh.Client, error) {
if err := opts.validate(); err != nil {
return nil, err
}
conf := &ssh.ClientConfig{
User: opts.User,
Auth: []ssh.AuthMethod{},
Timeout: opts.Timeout,
}
if len(opts.Password) > 0 {
conf.Auth = append(conf.Auth, ssh.Password(opts.Password))
cb := func(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
answers = make([]string, len(questions))
filledCount := 0
for i, question := range questions {
challenge := map[string]any{"user": user, "instruction": instruction, "question": question, "echo": echos[i]}
gologger.Debug().Msgf("SSH keyboard-interactive question %d/%d: %s", i+1, len(questions), vardump.DumpVariables(challenge))
if !echos[i] && passwordQuestionPattern.MatchString(question) {
answers[i] = opts.Password
filledCount++
} else if echos[i] && usernameQuestionPattern.MatchString(question) {
answers[i] = opts.User
filledCount++
}
}
gologger.Debug().Msgf("SSH keyboard-interactive: %d/%d questions filled", filledCount, len(questions))
return answers, nil
}
conf.Auth = append(conf.Auth, ssh.KeyboardInteractiveChallenge(cb))
}
if len(opts.PrivateKey) > 0 {
signer, err := ssh.ParsePrivateKey([]byte(opts.PrivateKey))
if err != nil {
return nil, err
}
conf.Auth = append(conf.Auth, ssh.PublicKeys(signer))
}
client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", opts.Host, opts.Port), conf)
if err != nil {
return nil, err
}
return client, nil
}
================================================
FILE: pkg/js/libs/structs/smbexploit.js
================================================
const header = bytes.Buffer();
// Create the SMB header first
header.append(structs.pack("B", 254)); // magic
header.append("SMB");
header.append(structs.pack("H", 64)); // header size
header.append(structs.pack("H", 0)); // credit charge
header.append(structs.pack("H", 0)); // channel sequence
header.append(structs.pack("H", 0)); // reserved
header.append(structs.pack("H", 0)); // negotiate protocol command
header.append(structs.pack("H", 31)); // credits requested
header.append(structs.pack("I", 0)); // flags
header.append(structs.pack("I", 0)); // chain offset
header.append(structs.pack("Q", 0)); // message id
header.append(structs.pack("I", 0)); // process id
header.append(structs.pack("I", 0)); // tree id
header.append(structs.pack("Q", 0)); // session id
header.append(structs.pack("QQ", [0, 0])); // signature
// Create negotiation packet
const negotiation = bytes.Buffer();
negotiation.append(structs.pack("H", 0x24)); // struct size
negotiation.append(structs.pack("H", 8)); // amount of dialects
negotiation.append(structs.pack("H", 1)); // enable signing
negotiation.append(structs.pack("H", 0)); // reserved
negotiation.append(structs.pack("I", 0x7f)); // capabilities
negotiation.append(structs.pack("QQ", [0, 0])); // client guid
negotiation.append(structs.pack("I", 0x78)); // negotiation offset
negotiation.append(structs.pack("H", 2)); // negotiation context count
negotiation.append(structs.pack("H", 0)); // reserved
negotiation.append(structs.pack("H", 0x0202)); // smb 2.0.2 dialect
negotiation.append(structs.pack("H", 0x0210)); // smb 2.1.0 dialect
negotiation.append(structs.pack("H", 0x0222)); // smb 2.2.2 dialect
negotiation.append(structs.pack("H", 0x0224)); // smb 2.2.4 dialect
negotiation.append(structs.pack("H", 0x0300)); // smb 3.0.0 dialect
negotiation.append(structs.pack("H", 0x0302)); // smb 3.0.2 dialect
negotiation.append(structs.pack("H", 0x0310)); // smb 3.1.0 dialect
negotiation.append(structs.pack("H", 0x0311)); // smb 3.1.1 dialect
negotiation.append(structs.pack("I", 0)); // padding
negotiation.append(structs.pack("H", 1)); // negotiation context type
negotiation.append(structs.pack("H", 38)); // negotiation data length
negotiation.append(structs.pack("I", 0)); // reserved
negotiation.append(structs.pack("H", 1)); // negotiation hash algorithm count
negotiation.append(structs.pack("H", 32)); // negotiation salt length
negotiation.append(structs.pack("H", 1)); // negotiation hash algorithm SHA512
negotiation.append(structs.pack("H", 1)); // negotiation hash algorithm SHA512
negotiation.append(structs.pack("QQ", [0, 0])); // salt part 1
negotiation.append(structs.pack("QQ", [0, 0])); // salt part 2
negotiation.append(structs.pack("H", 3)); // unknown??
negotiation.append(structs.pack("H", 10)); // data length unknown??
negotiation.append(structs.pack("I", 0)); // reserved unknown??
negotiation.append("\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00"); // unknown
const packet = bytes.Buffer();
packet.append(header.bytes());
packet.append(negotiation.bytes());
const netbios = bytes.Buffer();
netbios.append(structs.pack("H", 0)); // NetBIOS sessions message (should be 1 byte but whatever)
netbios.append(structs.pack("B", 0)); // just a pad to make it 3 bytes
netbios.append(structs.pack("B", packet.len())); // NetBIOS length (should be 3 bytes but whatever, as long as the packet isn't 0xff+ bytes)
const final = bytes.Buffer();
final.append(netbios.bytes());
final.append(packet.bytes());
console.log("Netbios", netbios.hex(), netbios.len());
console.log("Header", header.hex(), header.len());
console.log("Negotiation", negotiation.hex(), negotiation.len());
console.log("Packet", final.hex(), final.len());
const c = require("nuclei/libnet");
let conn = c.Open("tcp", "118.68.186.114:445");
conn.Send(final.bytes(), 0);
let bytesRecv = conn.Recv(0, 4);
console.log("recv Bytes", bytesRecv);
let size = structs.unpack("I", bytesRecv)[0];
console.log("Size", size);
let data = conn.Recv(0, size);
console.log("Data", data);
// TODO: Add hexdump helpers
version = structs.unpack("H", data.slice(68,70))[0]
context = structs.unpack("H", data.slice(70,72))[0]
console.log("Version", version);
console.log("Context", context);
if (version != 0x0311){
console.log("SMB version ", version, "was found which is not vulnerable!");
} else if (context != 2) {
console.log("Server answered with context", context, " which indicates that the target may not have SMB compression enabled and is therefore not vulnerable!");
} else {
console.log("SMB version ", version, " with context ", context, " was found which indicates SMBv3.1.1 is being used and SMB compression is enabled, therefore being vulnerable to CVE-2020-0796!");
}
conn.Close();
================================================
FILE: pkg/js/libs/structs/structs.go
================================================
package structs
import (
_ "embed"
"github.com/projectdiscovery/gostruct"
)
// StructsUnpack the byte slice (presumably packed by Pack(format, msg)) according to the given format.
// The result is a []interface{} slice even if it contains exactly one item.
// The byte slice must contain not less the amount of data required by the format
// (len(msg) must more or equal CalcSize(format)).
// Ex: structs.Unpack(">I", buff[:nb])
// @example
// ```javascript
// const structs = require('nuclei/structs');
// const result = structs.Unpack('H', [0]);
// ```
func Unpack(format string, msg []byte) ([]interface{}, error) {
return gostruct.UnPack(buildFormatSliceFromStringFormat(format), msg)
}
// StructsPack returns a byte slice containing the values of msg slice packed according to the given format.
// The items of msg slice must match the values required by the format exactly.
// Ex: structs.pack("H", 0)
// @example
// ```javascript
// const structs = require('nuclei/structs');
// const packed = structs.Pack('H', [0]);
// ```
func Pack(formatStr string, msg interface{}) ([]byte, error) {
var args []interface{}
switch v := msg.(type) {
case []interface{}:
args = v
default:
args = []interface{}{v}
}
format := buildFormatSliceFromStringFormat(formatStr)
var idxMsg int
for _, f := range format {
switch f {
case "<", ">", "!":
case "h", "H", "i", "I", "l", "L", "q", "Q", "b", "B":
switch v := args[idxMsg].(type) {
case int64:
args[idxMsg] = int(v)
}
idxMsg++
}
}
return gostruct.Pack(format, args)
}
// StructsCalcSize returns the number of bytes needed to pack the values according to the given format.
// Ex: structs.CalcSize("H")
// @example
// ```javascript
// const structs = require('nuclei/structs');
// const size = structs.CalcSize('H');
// ```
func StructsCalcSize(format string) (int, error) {
return gostruct.CalcSize(buildFormatSliceFromStringFormat(format))
}
func buildFormatSliceFromStringFormat(format string) []string {
var formats []string
temp := ""
for _, c := range format {
if c >= '0' && c <= '9' {
temp += string(c)
} else {
if temp != "" {
formats = append(formats, temp+string(c))
temp = ""
} else {
formats = append(formats, string(c))
}
}
}
if temp != "" {
formats = append(formats, temp)
}
return formats
}
================================================
FILE: pkg/js/libs/telnet/memo.telnet.go
================================================
// Warning - This is generated code
package telnet
import (
"errors"
"fmt"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
func memoizedisTelnet(executionId string, host string, port int) (IsTelnetResponse, error) {
hash := "isTelnet" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return isTelnet(executionId, host, port)
})
if err != nil {
return IsTelnetResponse{}, err
}
if value, ok := v.(IsTelnetResponse); ok {
return value, nil
}
return IsTelnetResponse{}, errors.New("could not convert cached result")
}
================================================
FILE: pkg/js/libs/telnet/telnet.go
================================================
package telnet
import (
"context"
"fmt"
"net"
"strconv"
"time"
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/telnet"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/telnetmini"
)
// Telnet protocol constants
const (
IAC = 255 // Interpret As Command
WILL = 251 // Will
WONT = 252 // Won't
DO = 253 // Do
DONT = 254 // Don't
SB = 250 // Subnegotiation Begin
SE = 240 // Subnegotiation End
ECHO = 1 // Echo
SUPPRESS_GO_AHEAD = 3 // Suppress Go Ahead
TERMINAL_TYPE = 24 // Terminal Type
NAWS = 31 // Negotiate About Window Size
ENCRYPT = 38 // Encryption option (0x26)
)
type (
// IsTelnetResponse is the response from the IsTelnet function.
// this is returned by IsTelnet function.
// @example
// ```javascript
// const telnet = require('nuclei/telnet');
// const isTelnet = telnet.IsTelnet('acme.com', 23);
// log(toJSON(isTelnet));
// ```
IsTelnetResponse struct {
IsTelnet bool
Banner string
}
// TelnetInfoResponse is the response from the Info function.
// @example
// ```javascript
// const telnet = require('nuclei/telnet');
// const client = new telnet.TelnetClient();
// const info = client.Info('acme.com', 23);
// log(toJSON(info));
// ```
TelnetInfoResponse struct {
SupportsEncryption bool
Banner string
Options map[int][]int
}
// TelnetClient is a client for Telnet servers.
// @example
// ```javascript
// const telnet = require('nuclei/telnet');
// const client = new telnet.TelnetClient();
// ```
TelnetClient struct{}
)
// IsTelnet checks if a host is running a Telnet server.
// @example
// ```javascript
// const telnet = require('nuclei/telnet');
// const isTelnet = telnet.IsTelnet('acme.com', 23);
// log(toJSON(isTelnet));
// ```
func IsTelnet(ctx context.Context, host string, port int) (IsTelnetResponse, error) {
executionId := ctx.Value("executionId").(string)
return memoizedisTelnet(executionId, host, port)
}
// @memo
func isTelnet(executionId string, host string, port int) (IsTelnetResponse, error) {
resp := IsTelnetResponse{}
timeout := 5 * time.Second
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return IsTelnetResponse{}, fmt.Errorf("dialers not initialized for %s", executionId)
}
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
if err != nil {
return resp, err
}
defer func() {
_ = conn.Close()
}()
telnetPlugin := telnet.TELNETPlugin{}
service, err := telnetPlugin.Run(conn, timeout, plugins.Target{Host: host})
if err != nil {
return resp, err
}
if service == nil {
return resp, nil
}
resp.Banner = service.Metadata().(plugins.ServiceTelnet).ServerData
resp.IsTelnet = true
return resp, nil
}
// Connect tries to connect to provided host and port with telnet.
// Optionally provides username and password for authentication.
// Returns state of connection. If the connection is successful,
// the function will return true, otherwise false.
// @example
// ```javascript
// const telnet = require('nuclei/telnet');
// const client = new telnet.TelnetClient();
// const connected = client.Connect('acme.com', 23, 'username', 'password');
// ```
func (c *TelnetClient) Connect(ctx context.Context, host string, port int, username string, password string) (bool, error) {
executionId := ctx.Value("executionId").(string)
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return false, fmt.Errorf("dialers not initialized for %s", executionId)
}
if !protocolstate.IsHostAllowed(executionId, host) {
return false, protocolstate.ErrHostDenied.Msgf(host)
}
// Create TCP connection
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
if err != nil {
return false, err
}
// Create telnet client using the telnetmini library
client := telnetmini.New(conn)
defer func() {
_ = client.Close()
}()
// Handle authentication if credentials provided
if username != "" && password != "" {
// Set a timeout context for authentication
authCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
if err := client.Auth(authCtx, username, password); err != nil {
return false, err
}
}
return true, nil
}
// Info gathers information about the telnet server including encryption support.
// Uses the telnetmini library's DetectEncryption helper function.
// WARNING: The connection used for detection becomes unusable after this call.
// @example
// ```javascript
// const telnet = require('nuclei/telnet');
// const client = new telnet.TelnetClient();
// const info = client.Info('acme.com', 23);
// log(toJSON(info));
// ```
func (c *TelnetClient) Info(ctx context.Context, host string, port int) (TelnetInfoResponse, error) {
executionId := ctx.Value("executionId").(string)
if !protocolstate.IsHostAllowed(executionId, host) {
return TelnetInfoResponse{}, protocolstate.ErrHostDenied.Msgf(host)
}
// Create TCP connection for encryption detection
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return TelnetInfoResponse{}, fmt.Errorf("dialers not initialized for %s", executionId)
}
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
if err != nil {
return TelnetInfoResponse{}, err
}
defer func() {
_ = conn.Close()
}()
// Use the telnetmini library's DetectEncryption helper function
// Note: The connection becomes unusable after this call
encryptionInfo, err := telnetmini.DetectEncryption(conn, 7*time.Second)
if err != nil {
return TelnetInfoResponse{}, err
}
return TelnetInfoResponse{
SupportsEncryption: encryptionInfo.SupportsEncryption,
Banner: encryptionInfo.Banner,
Options: encryptionInfo.Options,
}, nil
}
// GetTelnetNTLMInfo implements the Nmap telnet-ntlm-info.nse script functionality.
// This function uses the telnetmini library and SMB packet crafting functions to send
// MS-TNAP NTLM authentication requests with null credentials. It might work only on
// Microsoft Telnet servers.
// @example
// ```javascript
// const telnet = require('nuclei/telnet');
// const client = new telnet.TelnetClient();
// const ntlmInfo = client.GetTelnetNTLMInfo('acme.com', 23);
// log(toJSON(ntlmInfo));
// ```
func (c *TelnetClient) GetTelnetNTLMInfo(ctx context.Context, host string, port int) (*telnetmini.NTLMInfoResponse, error) {
executionId := ctx.Value("executionId").(string)
if !protocolstate.IsHostAllowed(executionId, host) {
return nil, protocolstate.ErrHostDenied.Msgf(host)
}
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return nil, fmt.Errorf("dialers not initialized for %s", executionId)
}
// Create TCP connection
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
if err != nil {
return nil, err
}
defer func() {
_ = conn.Close()
}()
// Create telnet client using the telnetmini library
client := telnetmini.New(conn)
defer func() {
_ = client.Close()
}()
// Set timeout
_ = conn.SetDeadline(time.Now().Add(10 * time.Second))
// Use the MS-TNAP packet crafting functions from our telnetmini library
// Create MS-TNAP Login Packet (Option Command IS) as per Nmap script
tnapLoginPacket := telnetmini.CreateTNAPLoginPacket()
// Send the MS-TNAP login packet
_, err = conn.Write(tnapLoginPacket)
if err != nil {
return nil, fmt.Errorf("failed to send MS-TNAP login packet: %w", err)
}
// Read response data
buffer := make([]byte, 4096)
n, err := conn.Read(buffer)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
if n == 0 {
return nil, fmt.Errorf("no response received")
}
// Parse NTLM response using our telnetmini library functions
response := buffer[:n]
// Use the parsing functions from our library instead of reimplementing
// This should use the NTLM parsing functions we added to telnetmini
ntlmInfo, err := telnetmini.ParseNTLMResponse(response)
if err != nil {
return nil, fmt.Errorf("failed to parse NTLM response: %w", err)
}
return ntlmInfo, nil
}
================================================
FILE: pkg/js/libs/vnc/memo.vnc.go
================================================
// Warning - This is generated code
package vnc
import (
"errors"
"fmt"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
func memoizedisVNC(executionId string, host string, port int) (IsVNCResponse, error) {
hash := "isVNC" + ":" + fmt.Sprint(executionId) + ":" + fmt.Sprint(host) + ":" + fmt.Sprint(port)
v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {
return isVNC(executionId, host, port)
})
if err != nil {
return IsVNCResponse{}, err
}
if value, ok := v.(IsVNCResponse); ok {
return value, nil
}
return IsVNCResponse{}, errors.New("could not convert cached result")
}
================================================
FILE: pkg/js/libs/vnc/vnc.go
================================================
package vnc
import (
"context"
"fmt"
"net"
"strconv"
"time"
vnclib "github.com/alexsnet/go-vnc"
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
vncplugin "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/vnc"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
stringsutil "github.com/projectdiscovery/utils/strings"
)
type (
// IsVNCResponse is the response from the IsVNC function.
// @example
// ```javascript
// const vnc = require('nuclei/vnc');
// const isVNC = vnc.IsVNC('acme.com', 5900);
// log(toJSON(isVNC));
// ```
IsVNCResponse struct {
IsVNC bool
Banner string
}
// VNCClient is a client for VNC servers.
// @example
// ```javascript
// const vnc = require('nuclei/vnc');
// const client = new vnc.VNCClient();
// const connected = client.Connect('acme.com', 5900, 'password');
// log(toJSON(connected));
// ```
VNCClient struct{}
)
// Connect connects to VNC server using given password.
// If connection and authentication is successful, it returns true.
// If connection or authentication is unsuccessful, it returns false and error.
// The connection is closed after the function returns.
// @example
// ```javascript
// const vnc = require('nuclei/vnc');
// const client = new vnc.VNCClient();
// const connected = client.Connect('acme.com', 5900, 'password');
// ```
func (c *VNCClient) Connect(ctx context.Context, host string, port int, password string) (bool, error) {
executionId := ctx.Value("executionId").(string)
return connect(executionId, host, port, password)
}
// connect attempts to authenticate with a VNC server using the given password
func connect(executionId string, host string, port int, password string) (bool, error) {
if host == "" || port <= 0 {
return false, fmt.Errorf("invalid host or port")
}
if !protocolstate.IsHostAllowed(executionId, host) {
// host is not valid according to network policy
return false, protocolstate.ErrHostDenied.Msgf(host)
}
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return false, fmt.Errorf("dialers not initialized for %s", executionId)
}
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
if err != nil {
return false, err
}
defer func() {
_ = conn.Close()
}()
// Set connection timeout
_ = conn.SetDeadline(time.Now().Add(10 * time.Second))
// Create VNC client config with password
vncConfig := vnclib.NewClientConfig(password)
// Attempt to connect and authenticate
c, err := vnclib.Connect(context.TODO(), conn, vncConfig)
if err != nil {
// Check for specific authentication errors
if isAuthError(err) {
return false, nil // Authentication failed, but connection succeeded
}
return false, err // Connection or other error
}
if c != nil {
_ = c.Close()
}
return true, nil
}
// isAuthError checks if the error is an authentication failure
func isAuthError(err error) bool {
if err == nil {
return false
}
// Check for common VNC authentication error messages
errStr := err.Error()
return stringsutil.ContainsAnyI(errStr, "authentication", "auth", "password", "invalid", "failed")
}
// IsVNC checks if a host is running a VNC server.
// It returns a boolean indicating if the host is running a VNC server
// and the banner of the VNC server.
// @example
// ```javascript
// const vnc = require('nuclei/vnc');
// const isVNC = vnc.IsVNC('acme.com', 5900);
// log(toJSON(isVNC));
// ```
func IsVNC(ctx context.Context, host string, port int) (IsVNCResponse, error) {
executionId := ctx.Value("executionId").(string)
return memoizedisVNC(executionId, host, port)
}
// @memo
func isVNC(executionId string, host string, port int) (IsVNCResponse, error) {
resp := IsVNCResponse{}
timeout := 5 * time.Second
dialer := protocolstate.GetDialersWithId(executionId)
if dialer == nil {
return IsVNCResponse{}, fmt.Errorf("dialers not initialized for %s", executionId)
}
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
if err != nil {
return resp, err
}
defer func() {
_ = conn.Close()
}()
vncPlugin := vncplugin.VNCPlugin{}
service, err := vncPlugin.Run(conn, timeout, plugins.Target{Host: host})
if err != nil {
return resp, err
}
if service == nil {
return resp, nil
}
resp.Banner = service.Version
resp.IsVNC = true
return resp, nil
}
================================================
FILE: pkg/js/utils/nucleijs.go
================================================
package utils
import (
"fmt"
"reflect"
"strings"
"sync"
"github.com/Mzack9999/goja"
)
// temporary on demand runtime to throw errors when vm is not available
var (
tmpRuntime *goja.Runtime
runtimeInit func() = sync.OnceFunc(func() {
tmpRuntime = goja.New()
})
)
func getRuntime() *goja.Runtime {
runtimeInit()
return tmpRuntime
}
// NucleiJS is js bindings that handles goja runtime related issue
// and allows setting a defer statements to close resources
type NucleiJS struct {
vm *goja.Runtime
ObjectSig string
}
// NewNucleiJS creates a new nucleijs instance
func NewNucleiJS(vm *goja.Runtime) *NucleiJS {
return &NucleiJS{vm: vm}
}
// internal runtime getter
func (j *NucleiJS) runtime() *goja.Runtime {
if j == nil {
return getRuntime()
}
return j.vm
}
func (j *NucleiJS) ExecutionId() string {
executionId, ok := j.vm.GetContextValue("executionId")
if !ok {
return ""
}
return executionId.(string)
}
// see: https://arc.net/l/quote/wpenftpc for throwing docs
// ThrowError throws an error in goja runtime if is not nil
func (j *NucleiJS) ThrowError(err error) {
if err == nil {
return
}
panic(j.runtime().ToValue(err.Error()))
}
// HandleError handles error and throws a
func (j *NucleiJS) HandleError(err error, msg ...string) {
if err == nil {
return
}
if len(msg) == 0 {
j.ThrowError(err)
}
j.Throw("%s: %s", strings.Join(msg, ":"), err.Error())
}
// Throw throws an error in goja runtime
func (j *NucleiJS) Throw(format string, args ...interface{}) {
if len(args) > 0 {
panic(j.runtime().ToValue(fmt.Sprintf(format, args...)))
}
panic(j.runtime().ToValue(format))
}
// GetArg returns argument at index from goja runtime if not found throws error
func (j *NucleiJS) GetArg(args []goja.Value, index int) any {
if index >= len(args) {
j.Throw("Missing argument at index %v: %v", index, j.ObjectSig)
}
val := args[index]
if goja.IsUndefined(val) {
j.Throw("Missing argument at index %v: %v", index, j.ObjectSig)
}
return val.Export()
}
// GetArgSafe returns argument at index from goja runtime if not found returns default value
func (j *NucleiJS) GetArgSafe(args []goja.Value, index int, defaultValue any) any {
if index >= len(args) {
return defaultValue
}
val := args[index]
if goja.IsUndefined(val) {
return defaultValue
}
return val.Export()
}
// Require throws an error if expression is false
func (j *NucleiJS) Require(expr bool, msg string) {
if !expr {
j.Throw("%s", msg)
}
}
// LinkConstructor links a type with invocation doing this allows
// usage of instance of type in js
func LinkConstructor[T any](call goja.ConstructorCall, vm *goja.Runtime, obj T) *goja.Object {
instance := vm.ToValue(obj).(*goja.Object)
_ = instance.SetPrototype(call.This.Prototype())
return instance
}
// GetStructType gets a type defined in go and passed as argument from goja runtime if not found throws error
// Donot use this unless you are accepting a struct type from constructor
func GetStructType[T any](nj *NucleiJS, args []goja.Value, index int, FuncSig string) T {
if nj == nil {
nj = &NucleiJS{}
}
if index >= len(args) {
if FuncSig == "" {
nj.Throw("Missing argument at index %v", index)
}
nj.Throw("Missing arguments expected : %v", FuncSig)
}
value := args[index]
// validate type
var ptr T
expected := reflect.ValueOf(ptr).Type()
argType := expected.Name()
valueType := value.ExportType().Name()
if argType != valueType {
nj.Throw("Type Mismatch expected %v got %v", argType, valueType)
}
ptrValue := reflect.New(expected).Elem()
ptrValue.Set(reflect.ValueOf(value.Export()))
return ptrValue.Interface().(T)
}
// GetStructTypeSafe gets an type defined in go and passed as argument from goja runtime if not found returns default value
// Donot use this unless you are accepting a struct type from constructor
func GetStructTypeSafe[T any](nj *NucleiJS, args []goja.Value, index int, defaultValue T) T {
if nj == nil {
nj = &NucleiJS{}
}
if index >= len(args) {
return defaultValue
}
value := args[index]
// validate type
var ptr T
argType := reflect.ValueOf(ptr).Type().Name()
valueType := value.ExportType().Name()
if argType != valueType {
return defaultValue
}
return value.ToObject(nj.runtime()).Export().(T)
}
================================================
FILE: pkg/js/utils/pgwrap/pgwrap.go
================================================
package pgwrap
import (
"context"
"database/sql"
"database/sql/driver"
"fmt"
"net"
"net/url"
"time"
"github.com/lib/pq"
"github.com/projectdiscovery/fastdialer/fastdialer"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
const (
PGWrapDriver = "pgwrap"
)
type pgDial struct {
executionId string
}
func (p *pgDial) Dial(network, address string) (net.Conn, error) {
dialers := protocolstate.GetDialersWithId(p.executionId)
if dialers == nil {
return nil, fmt.Errorf("dialers not initialized for %s", p.executionId)
}
return dialers.Fastdialer.Dial(context.TODO(), network, address)
}
func (p *pgDial) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
dialers := protocolstate.GetDialersWithId(p.executionId)
if dialers == nil {
return nil, fmt.Errorf("dialers not initialized for %s", p.executionId)
}
ctx, cancel := context.WithTimeoutCause(context.Background(), timeout, fastdialer.ErrDialTimeout)
defer cancel()
return dialers.Fastdialer.Dial(ctx, network, address)
}
func (p *pgDial) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
dialers := protocolstate.GetDialersWithId(p.executionId)
if dialers == nil {
return nil, fmt.Errorf("dialers not initialized for %s", p.executionId)
}
return dialers.Fastdialer.Dial(ctx, network, address)
}
// Unfortunately lib/pq does not provide easy to customize or
// replace dialer so we need to hijack it by wrapping it in our own
// driver and register it as postgres driver
// PgDriver is the Postgres database driver.
type PgDriver struct{}
// Open opens a new connection to the database. name is a connection string.
// Most users should only use it through database/sql package from the standard
// library.
func (d PgDriver) Open(name string) (driver.Conn, error) {
// Parse the connection string to get executionId
u, err := url.Parse(name)
if err != nil {
return nil, fmt.Errorf("invalid connection string: %v", err)
}
values := u.Query()
executionId := values.Get("executionId")
// Remove executionId from the connection string
values.Del("executionId")
u.RawQuery = values.Encode()
return pq.DialOpen(&pgDial{executionId: executionId}, u.String())
}
func init() {
sql.Register(PGWrapDriver, &PgDriver{})
}
================================================
FILE: pkg/js/utils/util.go
================================================
package utils
import (
"database/sql"
)
// SQLResult holds the result of a SQL query.
//
// It contains the count of rows, the columns present, and the actual row data.
type SQLResult struct {
Count int // Count is the number of rows returned.
Columns []string // Columns is the slice of column names.
Rows []interface{} // Rows is a slice of row data, where each row is a map of column name to value.
}
// UnmarshalSQLRows converts sql.Rows into a more structured SQLResult.
//
// This function takes *sql.Rows as input and attempts to unmarshal the data into
// a SQLResult struct. It handles different SQL data types by using the appropriate
// sql.Null* types during scanning. It returns a pointer to a SQLResult or an error.
//
// The function closes the sql.Rows when finished.
func UnmarshalSQLRows(rows *sql.Rows) (*SQLResult, error) {
defer func() {
_ = rows.Close()
}()
columnTypes, err := rows.ColumnTypes()
if err != nil {
return nil, err
}
result := &SQLResult{}
result.Columns, err = rows.Columns()
if err != nil {
return nil, err
}
count := len(columnTypes)
for rows.Next() {
result.Count++
scanArgs := make([]interface{}, count)
for i, v := range columnTypes {
switch v.DatabaseTypeName() {
case "VARCHAR", "TEXT", "UUID", "TIMESTAMP":
scanArgs[i] = new(sql.NullString)
case "BOOL":
scanArgs[i] = new(sql.NullBool)
case "INT4":
scanArgs[i] = new(sql.NullInt64)
default:
scanArgs[i] = new(sql.NullString)
}
}
err := rows.Scan(scanArgs...)
if err != nil {
// Return the result accumulated so far along with the error.
return result, err
}
masterData := make(map[string]interface{})
for i, v := range columnTypes {
if z, ok := (scanArgs[i]).(*sql.NullBool); ok {
masterData[v.Name()] = z.Bool
continue
}
if z, ok := (scanArgs[i]).(*sql.NullString); ok {
masterData[v.Name()] = z.String
continue
}
if z, ok := (scanArgs[i]).(*sql.NullInt64); ok {
masterData[v.Name()] = z.Int64
continue
}
if z, ok := (scanArgs[i]).(*sql.NullFloat64); ok {
masterData[v.Name()] = z.Float64
continue
}
if z, ok := (scanArgs[i]).(*sql.NullInt32); ok {
masterData[v.Name()] = z.Int32
continue
}
masterData[v.Name()] = scanArgs[i]
}
result.Rows = append(result.Rows, masterData)
}
return result, nil
}
================================================
FILE: pkg/keys/key.go
================================================
// keys package contains the public key for verifying digital signature of templates
package keys
import _ "embed"
const PDVerifier = "projectdiscovery/nuclei-templates"
//go:embed nuclei.crt
var NucleiCert []byte // public key for verifying digital signature of templates
================================================
FILE: pkg/keys/nuclei.crt
================================================
-----BEGIN PD NUCLEI USER CERTIFICATE-----
MIIBgDCCASWgAwIBAgIEZSUZ3jAKBggqhkjOPQQDAjAsMSowKAYDVQQDEyFwcm9q
ZWN0ZGlzY292ZXJ5L251Y2xlaS10ZW1wbGF0ZXMwHhcNMjMxMDEwMDkzMTEwWhcN
MjcxMDA5MDkzMTEwWjAsMSowKAYDVQQDEyFwcm9qZWN0ZGlzY292ZXJ5L251Y2xl
aS10ZW1wbGF0ZXMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASTaiE41H7LWudF
SMCfnqguQMwEte7dz/FRfK2lmezE02w+I2VwcS3j5cPwNaqYRAJkQhk6+7li0GpG
9fb11Fs2ozUwMzAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwEw
DAYDVR0TAQH/BAIwADAKBggqhkjOPQQDAgNJADBGAiEAhFsWwLDcWks3RUv3ujCs
4V1reu6KL+kELrCCQWu5FiUCIQDZbtqL30GPGYaPSpVmd6BKrZDBOfUVBsoCS7pS
q3JLHQ==
-----END PD NUCLEI USER CERTIFICATE-----
================================================
FILE: pkg/loader/parser/parser.go
================================================
package parser
import (
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
)
type Parser interface {
LoadTemplate(templatePath string, tagFilter any, extraTags []string, catalog catalog.Catalog) (bool, error)
ParseTemplate(templatePath string, catalog catalog.Catalog) (any, error)
LoadWorkflow(templatePath string, catalog catalog.Catalog) (bool, error)
}
================================================
FILE: pkg/loader/workflow/workflow_loader.go
================================================
package workflow
import (
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader/filter"
"github.com/projectdiscovery/nuclei/v3/pkg/model"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
)
type workflowLoader struct {
pathFilter *filter.PathFilter
tagFilter *templates.TagFilter
options *protocols.ExecutorOptions
}
// NewLoader returns a new workflow loader structure
func NewLoader(options *protocols.ExecutorOptions) (model.WorkflowLoader, error) {
tagFilter, err := templates.NewTagFilter(&templates.TagFilterConfig{
Authors: options.Options.Authors,
Tags: options.Options.Tags,
ExcludeTags: options.Options.ExcludeTags,
IncludeTags: options.Options.IncludeTags,
IncludeIds: options.Options.IncludeIds,
ExcludeIds: options.Options.ExcludeIds,
Severities: options.Options.Severities,
ExcludeSeverities: options.Options.ExcludeSeverities,
Protocols: options.Options.Protocols,
ExcludeProtocols: options.Options.ExcludeProtocols,
IncludeConditions: options.Options.IncludeConditions,
})
if err != nil {
return nil, err
}
pathFilter := filter.NewPathFilter(&filter.PathFilterConfig{
IncludedTemplates: options.Options.IncludeTemplates,
ExcludedTemplates: options.Options.ExcludedTemplates,
}, options.Catalog)
return &workflowLoader{pathFilter: pathFilter, tagFilter: tagFilter, options: options}, nil
}
func (w *workflowLoader) GetTemplatePathsByTags(templateTags []string) []string {
includedTemplates, errs := w.options.Catalog.GetTemplatesPath([]string{config.DefaultConfig.TemplatesDirectory})
for template, err := range errs {
gologger.Error().Msgf("Could not find template '%s': %s", template, err)
}
templatePathMap := w.pathFilter.Match(includedTemplates)
loadedTemplates := make([]string, 0, len(templatePathMap))
for templatePath := range templatePathMap {
loaded, _ := w.options.Parser.LoadTemplate(templatePath, w.tagFilter, templateTags, w.options.Catalog)
if loaded {
loadedTemplates = append(loadedTemplates, templatePath)
}
}
return loadedTemplates
}
func (w *workflowLoader) GetTemplatePaths(templatesList []string, noValidate bool) []string {
includedTemplates, errs := w.options.Catalog.GetTemplatesPath(templatesList)
for template, err := range errs {
gologger.Error().Msgf("Could not find template '%s': %s", template, err)
}
templatesPathMap := w.pathFilter.Match(includedTemplates)
loadedTemplates := make([]string, 0, len(templatesPathMap))
for templatePath := range templatesPathMap {
matched, err := w.options.Parser.LoadTemplate(templatePath, w.tagFilter, nil, w.options.Catalog)
if err != nil && !matched {
gologger.Warning().Msg(err.Error())
} else if matched || noValidate {
loadedTemplates = append(loadedTemplates, templatePath)
}
}
return loadedTemplates
}
================================================
FILE: pkg/model/model.go
================================================
package model
import (
"github.com/invopop/jsonschema"
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice"
)
type schemaMetadata struct {
PropName string
PropType string
Example []interface{}
OneOf []*schemaMetadata
}
var infoSchemaMetadata = []schemaMetadata{
{PropName: "author", OneOf: []*schemaMetadata{{PropType: "string", Example: []interface{}{`pdteam`}}, {PropType: "array", Example: []interface{}{`pdteam,mr.robot`}}}},
}
// Info contains metadata information about a template
type Info struct {
// description: |
// Name should be good short summary that identifies what the template does.
//
// examples:
// - value: "\"bower.json file disclosure\""
// - value: "\"Nagios Default Credentials Check\""
Name string `json:"name,omitempty" yaml:"name,omitempty" jsonschema:"title=name of the template,description=Name is a short summary of what the template does,type=string,required,example=Nagios Default Credentials Check"`
// description: |
// Author of the template.
//
// Multiple values can also be specified separated by commas.
// examples:
// - value: "\"\""
Authors stringslice.StringSlice `json:"author,omitempty" yaml:"author,omitempty" jsonschema:"title=author of the template,description=Author is the author of the template,required,example=username"`
// description: |
// Any tags for the template.
//
// Multiple values can also be specified separated by commas.
//
// examples:
// - name: Example tags
// value: "\"cve,cve2019,grafana,auth-bypass,dos\""
Tags stringslice.StringSlice `json:"tags,omitempty" yaml:"tags,omitempty" jsonschema:"title=tags of the template,description=Any tags for the template"`
// description: |
// Description of the template.
//
// You can go in-depth here on what the template actually does.
//
// examples:
// - value: "\"Bower is a package manager which stores package information in the bower.json file\""
// - value: "\"Subversion ALM for the enterprise before 8.8.2 allows reflected XSS at multiple locations\""
Description string `json:"description,omitempty" yaml:"description,omitempty" jsonschema:"title=description of the template,description=In-depth explanation on what the template does,type=string,example=Bower is a package manager which stores package information in the bower.json file"`
// description: |
// Impact of the template.
//
// You can go in-depth here on impact of the template.
//
// examples:
// - value: "\"Successful exploitation of this vulnerability could allow an attacker to execute arbitrary SQL queries, potentially leading to unauthorized access, data leakage, or data manipulation.\""
// - value: "\"Successful exploitation of this vulnerability could allow an attacker to execute arbitrary script code in the context of the victim's browser, potentially leading to session hijacking, defacement, or theft of sensitive information.\""
Impact string `json:"impact,omitempty" yaml:"impact,omitempty" jsonschema:"title=impact of the template,description=In-depth explanation on the impact of the issue found by the template,example=Successful exploitation of this vulnerability could allow an attacker to execute arbitrary SQL queries, potentially leading to unauthorized access, data leakage, or data manipulation.,type=string"`
// description: |
// References for the template.
//
// This should contain links relevant to the template.
//
// examples:
// - value: >
// []string{"https://github.com/strapi/strapi", "https://github.com/getgrav/grav"}
Reference *stringslice.RawStringSlice `json:"reference,omitempty" yaml:"reference,omitempty" jsonschema:"title=references for the template,description=Links relevant to the template"`
// description: |
// Severity of the template.
SeverityHolder severity.Holder `json:"severity,omitempty" yaml:"severity,omitempty"`
// description: |
// Metadata of the template.
//
// examples:
// - value: >
// map[string]string{"customField1":"customValue1"}
Metadata map[string]interface{} `json:"metadata,omitempty" yaml:"metadata,omitempty" jsonschema:"title=additional metadata for the template,description=Additional metadata fields for the template,type=object"`
// description: |
// Classification contains classification information about the template.
Classification *Classification `json:"classification,omitempty" yaml:"classification,omitempty" jsonschema:"title=classification info for the template,description=Classification information for the template,type=object"`
// description: |
// Remediation steps for the template.
//
// You can go in-depth here on how to mitigate the problem found by this template.
//
// examples:
// - value: "\"Change the default administrative username and password of Apache ActiveMQ by editing the file jetty-realm.properties\""
Remediation string `json:"remediation,omitempty" yaml:"remediation,omitempty" jsonschema:"title=remediation steps for the template,description=In-depth explanation on how to fix the issues found by the template,example=Change the default administrative username and password of Apache ActiveMQ by editing the file jetty-realm.properties,type=string"`
}
// JSONSchemaProperty returns the JSON schema property for the Info object.
func (i Info) JSONSchemaExtend(base *jsonschema.Schema) {
// since we are re-using a stringslice and rawStringSlice everywhere, we can extend/edit the schema here
// thus allowing us to add examples, descriptions, etc. to the properties
for _, metadata := range infoSchemaMetadata {
if prop, ok := base.Properties.Get(metadata.PropName); ok {
if len(metadata.OneOf) > 0 {
for _, oneOf := range metadata.OneOf {
prop.OneOf = append(prop.OneOf, &jsonschema.Schema{
Type: oneOf.PropType,
Examples: oneOf.Example,
})
}
} else {
if metadata.PropType != "" {
prop.Type = metadata.PropType
}
prop.Examples = []interface{}{metadata.Example}
}
}
}
}
// Classification contains the vulnerability classification data for a template.
type Classification struct {
// description: |
// CVE ID for the template
// examples:
// - value: "\"CVE-2020-14420\""
CVEID stringslice.StringSlice `json:"cve-id,omitempty" yaml:"cve-id,omitempty" jsonschema:"title=cve ids for the template,description=CVE IDs for the template,example=CVE-2020-14420"`
// description: |
// CWE ID for the template.
// examples:
// - value: "\"CWE-22\""
CWEID stringslice.StringSlice `json:"cwe-id,omitempty" yaml:"cwe-id,omitempty" jsonschema:"title=cwe ids for the template,description=CWE IDs for the template,example=CWE-22"`
// description: |
// CVSS Metrics for the template.
// examples:
// - value: "\"3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H\""
CVSSMetrics string `json:"cvss-metrics,omitempty" yaml:"cvss-metrics,omitempty" jsonschema:"title=cvss metrics for the template,description=CVSS Metrics for the template,example=3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"`
// description: |
// CVSS Score for the template.
// examples:
// - value: "\"9.8\""
CVSSScore float64 `json:"cvss-score,omitempty" yaml:"cvss-score,omitempty" jsonschema:"title=cvss score for the template,description=CVSS Score for the template,example=9.8"`
// description: |
// EPSS Score for the template.
// examples:
// - value: "\"0.42509\""
EPSSScore float64 `json:"epss-score,omitempty" yaml:"epss-score,omitempty" jsonschema:"title=epss score for the template,description=EPSS Score for the template,example=0.42509"`
// description: |
// EPSS Percentile for the template.
// examples:
// - value: "\"0.42509\""
EPSSPercentile float64 `json:"epss-percentile,omitempty" yaml:"epss-percentile,omitempty" jsonschema:"title=epss percentile for the template,description=EPSS Percentile for the template,example=0.42509"`
// description: |
// CPE for the template.
// examples:
// - value: "\"cpe:/a:vendor:product:version\""
CPE string `json:"cpe,omitempty" yaml:"cpe,omitempty" jsonschema:"title=cpe for the template,description=CPE for the template,example=cpe:/a:vendor:product:version"`
}
================================================
FILE: pkg/model/model_test.go
================================================
package model
import (
"strings"
"testing"
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"
)
func TestInfoJsonMarshal(t *testing.T) {
info := Info{
Name: "Test Template Name",
Authors: stringslice.StringSlice{Value: []string{"forgedhallpass", "ice3man"}},
Description: "Test description",
SeverityHolder: severity.Holder{Severity: severity.High},
Tags: stringslice.StringSlice{Value: []string{"cve", "misc"}},
Reference: stringslice.NewRawStringSlice("Reference1"),
Metadata: map[string]interface{}{
"string_key": "string_value",
"array_key": []string{"array_value1", "array_value2"},
"map_key": map[string]string{
"key1": "val1",
},
},
}
result, err := json.Marshal(&info)
require.Nil(t, err)
expected := `{"name":"Test Template Name","author":["forgedhallpass","ice3man"],"tags":["cve","misc"],"description":"Test description","reference":"Reference1","severity":"high","metadata":{"array_key":["array_value1","array_value2"],"map_key":{"key1":"val1"},"string_key":"string_value"}}`
require.Equal(t, expected, string(result))
}
func TestInfoYamlMarshal(t *testing.T) {
info := Info{
Name: "Test Template Name",
Authors: stringslice.StringSlice{Value: []string{"forgedhallpass", "ice3man"}},
Description: "Test description",
SeverityHolder: severity.Holder{Severity: severity.High},
Tags: stringslice.StringSlice{Value: []string{"cve", "misc"}},
Reference: stringslice.NewRawStringSlice("Reference1"),
Metadata: map[string]interface{}{
"string_key": "string_value",
"array_key": []string{"array_value1", "array_value2"},
"map_key": map[string]string{
"key1": "val1",
},
},
}
result, err := yaml.Marshal(&info)
require.Nil(t, err)
expected := `name: Test Template Name
author:
- forgedhallpass
- ice3man
tags:
- cve
- misc
description: Test description
reference: Reference1
severity: high
metadata:
array_key:
- array_value1
- array_value2
map_key:
key1: val1
string_key: string_value
`
require.Equal(t, expected, string(result))
}
func TestUnmarshal(t *testing.T) {
templateName := "Test Template"
authors := []string{"forgedhallpass", "ice3man"}
tags := []string{"cve", "misc"}
references := []string{"http://test.com", "http://Domain.com"}
dynamicKey1 := "customDynamicKey1"
dynamicKey2 := "customDynamicKey2"
dynamicKeysMap := map[string]interface{}{
dynamicKey1: "customDynamicValue1",
dynamicKey2: "customDynamicValue2",
}
assertUnmarshalledTemplateInfo := func(t *testing.T, yamlPayload string) Info {
t.Helper()
info := Info{}
err := yaml.Unmarshal([]byte(yamlPayload), &info)
require.Nil(t, err)
require.Equal(t, info.Name, templateName)
require.Equal(t, info.Authors.ToSlice(), authors)
require.Equal(t, info.Tags.ToSlice(), tags)
require.Equal(t, info.SeverityHolder.Severity, severity.Critical)
require.Equal(t, info.Reference.ToSlice(), references)
require.Equal(t, info.Metadata, dynamicKeysMap)
return info
}
yamlPayload1 := `
name: ` + templateName + `
author: ` + strings.Join(authors, ", ") + `
tags: ` + strings.Join(tags, ", ") + `
severity: critical
reference: ` + strings.Join(references, ",") + `
metadata:
` + dynamicKey1 + `: ` + dynamicKeysMap[dynamicKey1].(string) + `
` + dynamicKey2 + `: ` + dynamicKeysMap[dynamicKey2].(string) + `
`
yamlPayload2 := `
name: ` + templateName + `
author:
- ` + authors[0] + `
- ` + authors[1] + `
tags:
- ` + tags[0] + `
- ` + tags[1] + `
severity: critical
reference:
- ` + references[0] + ` # comments are not unmarshalled
- ` + references[1] + `
metadata:
` + dynamicKey1 + `: ` + dynamicKeysMap[dynamicKey1].(string) + `
` + dynamicKey2 + `: ` + dynamicKeysMap[dynamicKey2].(string) + `
`
info1 := assertUnmarshalledTemplateInfo(t, yamlPayload1)
info2 := assertUnmarshalledTemplateInfo(t, yamlPayload2)
require.Equal(t, info1, info2)
}
================================================
FILE: pkg/model/types/severity/severities.go
================================================
package severity
import (
"fmt"
"strings"
"github.com/projectdiscovery/goflags"
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
)
// Severities used by the goflags library for parsing an array of Severity types, passed as CLI arguments from the user
type Severities []Severity
func (severities *Severities) Set(values string) error {
inputSeverities, err := goflags.ToStringSlice(values, goflags.FileNormalizedStringSliceOptions)
if err != nil {
return err
}
for _, inputSeverity := range inputSeverities {
if err := setSeverity(severities, inputSeverity); err != nil {
return err
}
}
return nil
}
func (severities Severities) MarshalYAML() (interface{}, error) {
var stringSeverities = make([]string, 0, len(severities))
for _, severity := range severities {
stringSeverities = append(stringSeverities, severity.String())
}
return stringSeverities, nil
}
func (severities *Severities) UnmarshalYAML(unmarshal func(interface{}) error) error {
var stringSliceValue stringslice.StringSlice
if err := unmarshal(&stringSliceValue); err != nil {
return err
}
stringSLice := stringSliceValue.ToSlice()
var result = make(Severities, 0, len(stringSLice))
for _, severityString := range stringSLice {
if err := setSeverity(&result, severityString); err != nil {
return err
}
}
*severities = result
return nil
}
func (severities *Severities) UnmarshalJSON(data []byte) error {
var stringSliceValue stringslice.StringSlice
if err := json.Unmarshal(data, &stringSliceValue); err != nil {
return err
}
stringSLice := stringSliceValue.ToSlice()
var result = make(Severities, 0, len(stringSLice))
for _, severityString := range stringSLice {
if err := setSeverity(&result, severityString); err != nil {
return err
}
}
*severities = result
return nil
}
func (severities Severities) String() string {
var stringSeverities = make([]string, 0, len(severities))
for _, severity := range severities {
stringSeverities = append(stringSeverities, severity.String())
}
return strings.Join(stringSeverities, ", ")
}
func (severities Severities) MarshalJSON() ([]byte, error) {
var stringSeverities = make([]string, 0, len(severities))
for _, severity := range severities {
stringSeverities = append(stringSeverities, severity.String())
}
return json.Marshal(stringSeverities)
}
func setSeverity(severities *Severities, value string) error {
computedSeverity, err := toSeverity(value)
if err != nil {
return fmt.Errorf("'%s' is not a valid severity", value)
}
// TODO change the Severities type to map[Severity]interface{}, where the values are struct{}{}, to "simulates" a "set" data structure
*severities = append(*severities, computedSeverity)
return nil
}
================================================
FILE: pkg/model/types/severity/severity.go
================================================
package severity
import (
"strings"
"github.com/invopop/jsonschema"
"github.com/pkg/errors"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
)
type Severity int
// name:Severity
const (
// name:undefined
Undefined Severity = iota
// name:info
Info
// name:low
Low
// name:medium
Medium
// name:high
High
// name:critical
Critical
// name:unknown
Unknown
limit
)
var severityMappings = map[Severity]string{
Info: "info",
Low: "low",
Medium: "medium",
High: "high",
Critical: "critical",
Unknown: "unknown",
}
func GetSupportedSeverities() Severities {
var result []Severity
for index := Severity(1); index < limit; index++ {
result = append(result, index)
}
return result
}
func toSeverity(valueToMap string) (Severity, error) {
normalizedValue := normalizeValue(valueToMap)
for key, currentValue := range severityMappings {
if normalizedValue == currentValue {
return key, nil
}
}
return -1, errors.New("Invalid severity: " + valueToMap)
}
func normalizeValue(value string) string {
return strings.TrimSpace(strings.ToLower(value))
}
func (severity Severity) String() string {
return severityMappings[severity]
}
// Holder holds a Severity type. Required for un/marshalling purposes
//
//nolint:exported,revive //prefer to be explicit about the name, and make it refactor-safe
type Holder struct {
Severity Severity `mapping:"true"`
}
// Implement a jsonschema for the severity holder
func (severityHolder Holder) JSONSchema() *jsonschema.Schema {
enums := []interface{}{}
for _, severity := range GetSupportedSeverities() {
enums = append(enums, severity.String())
}
return &jsonschema.Schema{
Type: "string",
Title: "severity of the template",
Description: "Seriousness of the implications of the template",
Enum: enums,
}
}
func (severityHolder *Holder) UnmarshalYAML(unmarshal func(interface{}) error) error {
var marshalledSeverity string
if err := unmarshal(&marshalledSeverity); err != nil {
return err
}
computedSeverity, err := toSeverity(marshalledSeverity)
if err != nil {
return err
}
severityHolder.Severity = computedSeverity
return nil
}
func (severityHolder *Holder) UnmarshalJSON(data []byte) error {
var marshalledSeverity string
if err := json.Unmarshal(data, &marshalledSeverity); err != nil {
return err
}
computedSeverity, err := toSeverity(marshalledSeverity)
if err != nil {
return err
}
severityHolder.Severity = computedSeverity
return nil
}
func (severityHolder Holder) MarshalJSON() ([]byte, error) {
return json.Marshal(severityHolder.Severity.String())
}
func (severityHolder Holder) MarshalYAML() (interface{}, error) {
return severityHolder.Severity.String(), nil
}
================================================
FILE: pkg/model/types/severity/severity_test.go
================================================
package severity
import (
"testing"
"gopkg.in/yaml.v2"
"github.com/stretchr/testify/require"
)
func TestYamlUnmarshal(t *testing.T) {
testUnmarshal(t, yaml.Unmarshal, func(value string) string { return value })
}
func TestYamlMarshal(t *testing.T) {
severity := Holder{Severity: High}
marshalled, err := severity.MarshalYAML()
require.Nil(t, err, "could not marshal yaml")
require.Equal(t, "high", marshalled, "could not marshal severity correctly")
}
func TestYamlUnmarshalFail(t *testing.T) {
testUnmarshalFail(t, yaml.Unmarshal, createYAML)
}
func TestGetSupportedSeverities(t *testing.T) {
severities := GetSupportedSeverities()
require.Equal(t, severities, Severities{Info, Low, Medium, High, Critical, Unknown})
}
func testUnmarshal(t *testing.T, unmarshaller func(data []byte, v interface{}) error, payloadCreator func(value string) string) {
t.Helper()
payloads := [...]string{
payloadCreator("Info"),
payloadCreator("info"),
payloadCreator("inFo "),
payloadCreator("infO "),
payloadCreator(" INFO "),
}
for _, payload := range payloads { // nolint:scopelint // false-positive
t.Run(payload, func(t *testing.T) {
result := unmarshal(payload, unmarshaller)
require.Equal(t, result.Severity, Info)
require.Equal(t, result.Severity.String(), "info")
})
}
}
func testUnmarshalFail(t *testing.T, unmarshaller func(data []byte, v interface{}) error, payloadCreator func(value string) string) {
t.Helper()
require.Panics(t, func() { unmarshal(payloadCreator("invalid"), unmarshaller) })
}
func unmarshal(value string, unmarshaller func(data []byte, v interface{}) error) Holder {
severityStruct := Holder{}
var err = unmarshaller([]byte(value), &severityStruct)
if err != nil {
panic(err)
}
return severityStruct
}
func createYAML(value string) string {
return "severity: " + value + "\n"
}
func TestMarshalJSON(t *testing.T) {
unmarshalled := Severities{Low, Medium}
data, err := unmarshalled.MarshalJSON()
if err != nil {
panic(err)
}
require.Equal(t, "[\"low\",\"medium\"]", string(data), "could not marshal json")
}
func TestSeveritiesMarshalYaml(t *testing.T) {
unmarshalled := Severities{Low, Medium}
marshalled, err := yaml.Marshal(unmarshalled)
if err != nil {
panic(err)
}
require.Equal(t, "- low\n- medium\n", string(marshalled), "could not marshal yaml")
}
================================================
FILE: pkg/model/types/stringslice/stringslice.go
================================================
package stringslice
import (
"fmt"
"strings"
"github.com/invopop/jsonschema"
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
)
type StringOrSlice string
func (StringOrSlice) JSONSchema() *jsonschema.Schema {
return &jsonschema.Schema{
OneOf: []*jsonschema.Schema{
{
Type: "string",
},
{
Type: "array",
},
},
}
}
// StringSlice represents a single (in-lined) or multiple string value(s).
// The unmarshaller does not automatically convert in-lined strings to []string, hence the interface{} type is required.
type StringSlice struct {
Value interface{}
}
// Implement alias for stringslice and reuse it everywhere
func (stringSlice StringSlice) JSONSchemaAlias() any {
return StringOrSlice("")
}
func New(value interface{}) StringSlice {
return StringSlice{Value: value}
}
func (stringSlice *StringSlice) IsEmpty() bool {
return len(stringSlice.ToSlice()) == 0
}
func (stringSlice StringSlice) ToSlice() []string {
switch value := stringSlice.Value.(type) {
case string:
return []string{value}
case []string:
return value
case nil:
return []string{}
default:
panic(fmt.Sprintf("Unexpected StringSlice type: '%T'", value))
}
}
func (stringSlice StringSlice) String() string {
return strings.Join(stringSlice.ToSlice(), ", ")
}
func (stringSlice *StringSlice) UnmarshalYAML(unmarshal func(interface{}) error) error {
marshalledSlice, err := marshalStringToSlice(unmarshal)
if err != nil {
return err
}
result := make([]string, 0, len(marshalledSlice))
for _, value := range marshalledSlice {
result = append(result, stringSlice.Normalize(value))
}
stringSlice.Value = result
return nil
}
func (stringSlice StringSlice) Normalize(value string) string {
return strings.ToLower(strings.TrimSpace(value))
}
func (stringSlice StringSlice) MarshalYAML() (interface{}, error) {
return stringSlice.Value, nil
}
func (stringSlice StringSlice) MarshalJSON() ([]byte, error) {
return json.Marshal(stringSlice.Value)
}
func (stringSlice *StringSlice) UnmarshalJSON(data []byte) error {
var marshalledValueAsString string
var marshalledValuesAsSlice []string
sliceMarshalError := json.Unmarshal(data, &marshalledValuesAsSlice)
if sliceMarshalError != nil {
stringMarshalError := json.Unmarshal(data, &marshalledValueAsString)
if stringMarshalError != nil {
return stringMarshalError
}
}
var result []string
switch {
case len(marshalledValuesAsSlice) > 0:
result = marshalledValuesAsSlice
case !utils.IsBlank(marshalledValueAsString):
result = strings.Split(marshalledValueAsString, ",")
default:
result = []string{}
}
values := make([]string, 0, len(result))
for _, value := range result {
values = append(values, stringSlice.Normalize(value))
}
stringSlice.Value = values
return nil
}
func marshalStringToSlice(unmarshal func(interface{}) error) ([]string, error) {
var marshalledValueAsString string
var marshalledValuesAsSlice []string
sliceMarshalError := unmarshal(&marshalledValuesAsSlice)
if sliceMarshalError != nil {
stringMarshalError := unmarshal(&marshalledValueAsString)
if stringMarshalError != nil {
return nil, stringMarshalError
}
}
var result []string
switch {
case len(marshalledValuesAsSlice) > 0:
result = marshalledValuesAsSlice
case !utils.IsBlank(marshalledValueAsString):
result = strings.Split(marshalledValueAsString, ",")
default:
result = []string{}
}
return result, nil
}
================================================
FILE: pkg/model/types/stringslice/stringslice_raw.go
================================================
package stringslice
type RawStringSlice struct {
StringSlice
}
func NewRawStringSlice(value interface{}) *RawStringSlice {
return &RawStringSlice{StringSlice: StringSlice{Value: value}}
}
func (rawStringSlice *RawStringSlice) Normalize(value string) string {
return value
}
func (rawStringSlice *RawStringSlice) UnmarshalYAML(unmarshal func(interface{}) error) error {
marshalledSlice, err := marshalStringToSlice(unmarshal)
if err != nil {
return err
}
rawStringSlice.Value = marshalledSlice
return nil
}
func (rawStringSlice RawStringSlice) JSONSchemaAlias() any {
return StringOrSlice("")
}
================================================
FILE: pkg/model/types/userAgent/user_agent.go
================================================
package userAgent
import (
"strings"
"github.com/invopop/jsonschema"
"github.com/pkg/errors"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
)
type UserAgent int
// name:UserAgent
const (
// name:random
Random UserAgent = iota
// name:off
Off
// name:default
Default
// name:custom
Custom
limit
)
var userAgentMappings = map[UserAgent]string{
Random: "random",
Off: "off",
Default: "default",
Custom: "custom",
}
func GetSupportedUserAgentOptions() []UserAgent {
var result []UserAgent
for index := UserAgent(1); index < limit; index++ {
result = append(result, index)
}
return result
}
func toUserAgent(valueToMap string) (UserAgent, error) {
normalizedValue := normalizeValue(valueToMap)
for key, currentValue := range userAgentMappings {
if normalizedValue == currentValue {
return key, nil
}
}
return -1, errors.New("Invalid userAgent: " + valueToMap)
}
func normalizeValue(value string) string {
return strings.TrimSpace(strings.ToLower(value))
}
func (userAgent UserAgent) String() string {
return userAgentMappings[userAgent]
}
// UserAgentHolder holds a UserAgent type. Required for un/marshalling purposes
type UserAgentHolder struct {
Value UserAgent `mapping:"true"`
}
func (userAgentHolder UserAgentHolder) JSONSchema() *jsonschema.Schema {
gotType := &jsonschema.Schema{
Type: "string",
Title: "userAgent for the headless",
Description: "userAgent for the headless http request",
}
for _, userAgent := range GetSupportedUserAgentOptions() {
gotType.Enum = append(gotType.Enum, userAgent.String())
}
return gotType
}
func (userAgentHolder *UserAgentHolder) UnmarshalYAML(unmarshal func(interface{}) error) error {
var marshalledUserAgent string
if err := unmarshal(&marshalledUserAgent); err != nil {
return err
}
computedUserAgent, err := toUserAgent(marshalledUserAgent)
if err != nil {
return err
}
userAgentHolder.Value = computedUserAgent
return nil
}
func (userAgentHolder *UserAgentHolder) UnmarshalJSON(data []byte) error {
s := strings.Trim(string(data), `"`)
if s == "" {
return nil
}
computedUserAgent, err := toUserAgent(s)
if err != nil {
return err
}
userAgentHolder.Value = computedUserAgent
return nil
}
func (userAgentHolder *UserAgentHolder) MarshalJSON() ([]byte, error) {
return json.Marshal(userAgentHolder.Value.String())
}
func (userAgentHolder UserAgentHolder) MarshalYAML() (interface{}, error) {
return userAgentHolder.Value.String(), nil
}
================================================
FILE: pkg/model/workflow_loader.go
================================================
package model
// TODO shouldn't this rather be TemplateLoader?
// WorkflowLoader is a loader interface required for workflow initialization.
type WorkflowLoader interface {
// GetTemplatePathsByTags returns a list of template paths based on the provided tags from the templates directory
GetTemplatePathsByTags(tags []string) []string
// GetTemplatePaths takes a list of templates and returns paths for them
GetTemplatePaths(templatesList []string, noValidate bool) []string
}
================================================
FILE: pkg/operators/cache/cache.go
================================================
package cache
import (
"regexp"
"sync"
"github.com/Knetic/govaluate"
"github.com/projectdiscovery/gcache"
)
var (
initOnce sync.Once
mu sync.RWMutex
regexCap = 4096
dslCap = 4096
regexCache gcache.Cache[string, *regexp.Regexp]
dslCache gcache.Cache[string, *govaluate.EvaluableExpression]
)
func initCaches() {
initOnce.Do(func() {
regexCache = gcache.New[string, *regexp.Regexp](regexCap).LRU().Build()
dslCache = gcache.New[string, *govaluate.EvaluableExpression](dslCap).LRU().Build()
})
}
func SetCapacities(regexCapacity, dslCapacity int) {
// ensure caches are initialized under initOnce, so later Regex()/DSL() won't re-init
initCaches()
mu.Lock()
defer mu.Unlock()
if regexCapacity > 0 {
regexCap = regexCapacity
}
if dslCapacity > 0 {
dslCap = dslCapacity
}
if regexCapacity <= 0 && dslCapacity <= 0 {
return
}
// rebuild caches with new capacities
regexCache = gcache.New[string, *regexp.Regexp](regexCap).LRU().Build()
dslCache = gcache.New[string, *govaluate.EvaluableExpression](dslCap).LRU().Build()
}
func Regex() gcache.Cache[string, *regexp.Regexp] {
initCaches()
mu.RLock()
defer mu.RUnlock()
return regexCache
}
func DSL() gcache.Cache[string, *govaluate.EvaluableExpression] {
initCaches()
mu.RLock()
defer mu.RUnlock()
return dslCache
}
================================================
FILE: pkg/operators/cache/cache_test.go
================================================
package cache
import (
"regexp"
"testing"
"github.com/Knetic/govaluate"
)
func TestRegexCache_SetGet(t *testing.T) {
// ensure init
c := Regex()
pattern := "abc(\n)?123"
re, err := regexp.Compile(pattern)
if err != nil {
t.Fatalf("compile: %v", err)
}
if err := c.Set(pattern, re); err != nil {
t.Fatalf("set: %v", err)
}
got, err := c.GetIFPresent(pattern)
if err != nil || got == nil {
t.Fatalf("get: %v got=%v", err, got)
}
if got.String() != re.String() {
t.Fatalf("mismatch: %s != %s", got.String(), re.String())
}
}
func TestDSLCache_SetGet(t *testing.T) {
c := DSL()
expr := "1 + 2 == 3"
ast, err := govaluate.NewEvaluableExpression(expr)
if err != nil {
t.Fatalf("dsl compile: %v", err)
}
if err := c.Set(expr, ast); err != nil {
t.Fatalf("set: %v", err)
}
got, err := c.GetIFPresent(expr)
if err != nil || got == nil {
t.Fatalf("get: %v got=%v", err, got)
}
if got.String() != ast.String() {
t.Fatalf("mismatch: %s != %s", got.String(), ast.String())
}
}
func TestRegexCache_EvictionByCapacity(t *testing.T) {
SetCapacities(3, 3)
c := Regex()
for i := 0; i < 5; i++ {
k := string(rune('a' + i))
re := regexp.MustCompile(k)
_ = c.Set(k, re)
}
// last 3 keys expected to remain under LRU: 'c','d','e'
if _, err := c.GetIFPresent("a"); err == nil {
t.Fatalf("expected 'a' to be evicted")
}
if _, err := c.GetIFPresent("b"); err == nil {
t.Fatalf("expected 'b' to be evicted")
}
if _, err := c.GetIFPresent("c"); err != nil {
t.Fatalf("expected 'c' present")
}
}
func TestSetCapacities_NoRebuildOnZero(t *testing.T) {
// init
SetCapacities(4, 4)
c1 := Regex()
_ = c1.Set("k", regexp.MustCompile("k"))
if _, err := c1.GetIFPresent("k"); err != nil {
t.Fatalf("expected key present: %v", err)
}
// zero changes should not rebuild/clear caches
SetCapacities(0, 0)
c2 := Regex()
if _, err := c2.GetIFPresent("k"); err != nil {
t.Fatalf("key lost after zero-capacity SetCapacities: %v", err)
}
}
func TestSetCapacities_BeforeFirstUse(t *testing.T) {
// This should not be overridden by later initCaches
SetCapacities(2, 0)
c := Regex()
_ = c.Set("a", regexp.MustCompile("a"))
_ = c.Set("b", regexp.MustCompile("b"))
_ = c.Set("c", regexp.MustCompile("c"))
if _, err := c.GetIFPresent("a"); err == nil {
t.Fatalf("expected 'a' to be evicted under cap=2")
}
}
func TestSetCapacities_ConcurrentAccess(t *testing.T) {
SetCapacities(64, 64)
stop := make(chan struct{})
go func() {
for i := 0; i < 5000; i++ {
_ = Regex().Set("k"+string(rune('a'+(i%26))), regexp.MustCompile("a"))
_, _ = Regex().GetIFPresent("k" + string(rune('a'+(i%26))))
_, _ = DSL().GetIFPresent("1+2==3")
}
close(stop)
}()
for i := 0; i < 200; i++ {
SetCapacities(64+(i%5), 64+((i+1)%5))
}
<-stop
}
================================================
FILE: pkg/operators/common/dsl/dsl.go
================================================
package dsl
import (
"fmt"
"strings"
"github.com/Knetic/govaluate"
"github.com/miekg/dns"
"github.com/projectdiscovery/dsl"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/dns/dnsclientpool"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
sliceutil "github.com/projectdiscovery/utils/slice"
stringsutil "github.com/projectdiscovery/utils/strings"
)
var (
HelperFunctions map[string]govaluate.ExpressionFunction
FunctionNames []string
// knownPorts is a list of known ports for protocols implemented in nuclei
knowPorts = []string{"80", "443", "8080", "8081", "8443", "53"}
)
func init() {
_ = dsl.AddFunction(dsl.NewWithMultipleSignatures("resolve", []string{
"(host string) string",
"(format string) string",
}, false, func(args ...interface{}) (interface{}, error) {
argCount := len(args)
if argCount == 0 || argCount > 2 {
return nil, dsl.ErrInvalidDslFunction
}
format := "4"
var dnsType uint16
if len(args) > 1 {
format = strings.ToLower(types.ToString(args[1]))
}
switch format {
case "4", "a":
dnsType = dns.TypeA
case "6", "aaaa":
dnsType = dns.TypeAAAA
case "cname":
dnsType = dns.TypeCNAME
case "ns":
dnsType = dns.TypeNS
case "txt":
dnsType = dns.TypeTXT
case "srv":
dnsType = dns.TypeSRV
case "ptr":
dnsType = dns.TypePTR
case "mx":
dnsType = dns.TypeMX
case "soa":
dnsType = dns.TypeSOA
case "caa":
dnsType = dns.TypeCAA
default:
return nil, fmt.Errorf("invalid dns type")
}
options := &types.Options{}
err := dnsclientpool.Init(options)
if err != nil {
return nil, err
}
dnsClient, err := dnsclientpool.Get(options, &dnsclientpool.Configuration{})
if err != nil {
return nil, err
}
// query
rawResp, err := dnsClient.Query(types.ToString(args[0]), dnsType)
if err != nil {
return nil, err
}
dnsValues := map[uint16][]string{
dns.TypeA: rawResp.A,
dns.TypeAAAA: rawResp.AAAA,
dns.TypeCNAME: rawResp.CNAME,
dns.TypeNS: rawResp.NS,
dns.TypeTXT: rawResp.TXT,
dns.TypeSRV: rawResp.SRV,
dns.TypePTR: rawResp.PTR,
dns.TypeMX: rawResp.MX,
dns.TypeCAA: rawResp.CAA,
dns.TypeSOA: rawResp.GetSOARecords(),
}
if values, ok := dnsValues[dnsType]; ok {
firstFound, found := sliceutil.FirstNonZero(values)
if found {
return firstFound, nil
}
}
return "", fmt.Errorf("no records found")
}))
_ = dsl.AddFunction(dsl.NewWithMultipleSignatures("getNetworkPort", []string{
"(Port string,defaultPort string) string)",
"(Port int,defaultPort int) int",
}, false, func(args ...interface{}) (interface{}, error) {
if len(args) != 2 {
return nil, dsl.ErrInvalidDslFunction
}
port := types.ToString(args[0])
defaultPort := types.ToString(args[1])
if port == "" || stringsutil.EqualFoldAny(port, knowPorts...) {
return defaultPort, nil
}
return port, nil
}))
dsl.PrintDebugCallback = func(args ...interface{}) error {
gologger.Debug().Msgf("print_debug value: %s", fmt.Sprint(args...))
return nil
}
HelperFunctions = dsl.HelperFunctions()
FunctionNames = dsl.GetFunctionNames(HelperFunctions)
}
type CompilationError struct {
DslSignature string
WrappedError error
}
func (e *CompilationError) Error() string {
return fmt.Sprintf("could not compile DSL expression %q: %v", e.DslSignature, e.WrappedError)
}
func (e *CompilationError) Unwrap() error {
return e.WrappedError
}
func GetPrintableDslFunctionSignatures(noColor bool) string {
return dsl.GetPrintableDslFunctionSignatures(noColor)
}
================================================
FILE: pkg/operators/common/dsl/dsl_test.go
================================================
package dsl
import (
"fmt"
"testing"
"github.com/Knetic/govaluate"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/dns/dnsclientpool"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/stretchr/testify/require"
)
func TestDslExpressions(t *testing.T) {
// Use Google DNS for more reliable testing
googleDNS := []string{"8.8.8.8:53", "8.8.4.4:53"}
dslExpressions := map[string]interface{}{
`resolve("scanme.sh")`: "128.199.158.128",
`resolve("scanme.sh","a")`: "128.199.158.128",
`resolve("scanme.sh","6")`: "2400:6180:0:d0::91:1001",
`resolve("scanme.sh","aaaa")`: "2400:6180:0:d0::91:1001",
`resolve("scanme.sh","soa")`: "ns69.domaincontrol.com",
}
testDslExpressionScenariosWithDNS(t, dslExpressions, googleDNS)
}
func evaluateExpression(t *testing.T, dslExpression string) interface{} {
compiledExpression, err := govaluate.NewEvaluableExpressionWithFunctions(dslExpression, HelperFunctions)
require.NoError(t, err, "Error while compiling the %q expression", dslExpression)
actualResult, err := compiledExpression.Evaluate(make(map[string]interface{}))
require.NoError(t, err, "Error while evaluating the compiled %q expression", dslExpression)
for _, negativeTestWord := range []string{"panic", "invalid", "error"} {
require.NotContains(t, fmt.Sprintf("%v", actualResult), negativeTestWord)
}
return actualResult
}
func testDslExpressionScenariosWithDNS(t *testing.T, dslExpressions map[string]interface{}, resolvers []string) {
// Initialize DNS client pool with custom resolvers for testing
err := dnsclientpool.Init(&types.Options{
InternalResolversList: resolvers,
})
require.NoError(t, err, "Failed to initialize DNS client pool with custom resolvers")
for dslExpression, expectedResult := range dslExpressions {
t.Run(dslExpression, func(t *testing.T) {
actualResult := evaluateExpression(t, dslExpression)
if expectedResult != nil {
require.Equal(t, expectedResult, actualResult)
}
fmt.Printf("%s: \t %v\n", dslExpression, actualResult)
})
}
}
================================================
FILE: pkg/operators/extractors/compile.go
================================================
package extractors
import (
"fmt"
"regexp"
"strings"
"github.com/Knetic/govaluate"
"github.com/itchyny/gojq"
"github.com/projectdiscovery/nuclei/v3/pkg/operators/cache"
"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl"
)
// CompileExtractors performs the initial setup operation on an extractor
func (e *Extractor) CompileExtractors() error {
// Set up the extractor type
computedType, err := toExtractorTypes(e.GetType().String())
if err != nil {
return fmt.Errorf("unknown extractor type specified: %s", e.Type)
}
e.extractorType = computedType
// Compile the regexes
for _, regex := range e.Regex {
if cached, err := cache.Regex().GetIFPresent(regex); err == nil && cached != nil {
e.regexCompiled = append(e.regexCompiled, cached)
continue
}
compiled, err := regexp.Compile(regex)
if err != nil {
return fmt.Errorf("could not compile regex: %s", regex)
}
_ = cache.Regex().Set(regex, compiled)
e.regexCompiled = append(e.regexCompiled, compiled)
}
for i, kval := range e.KVal {
e.KVal[i] = strings.ToLower(kval)
}
for _, query := range e.JSON {
query, err := gojq.Parse(query)
if err != nil {
return fmt.Errorf("could not parse json: %s", query)
}
compiled, err := gojq.Compile(query)
if err != nil {
return fmt.Errorf("could not compile json: %s", query)
}
e.jsonCompiled = append(e.jsonCompiled, compiled)
}
for _, dslExp := range e.DSL {
if cached, err := cache.DSL().GetIFPresent(dslExp); err == nil && cached != nil {
e.dslCompiled = append(e.dslCompiled, cached)
continue
}
compiled, err := govaluate.NewEvaluableExpressionWithFunctions(dslExp, dsl.HelperFunctions)
if err != nil {
return &dsl.CompilationError{DslSignature: dslExp, WrappedError: err}
}
_ = cache.DSL().Set(dslExp, compiled)
e.dslCompiled = append(e.dslCompiled, compiled)
}
if e.CaseInsensitive {
if e.GetType() != KValExtractor {
return fmt.Errorf("case-insensitive flag is supported only for 'kval' extractors (not '%s')", e.Type)
}
for i := range e.KVal {
e.KVal[i] = strings.ToLower(e.KVal[i])
}
}
return nil
}
================================================
FILE: pkg/operators/extractors/doc.go
================================================
// Package extractors implements extractors for http response
// data retrieval.
package extractors
================================================
FILE: pkg/operators/extractors/extract.go
================================================
package extractors
import (
"fmt"
"strings"
"github.com/antchfx/htmlquery"
"github.com/antchfx/xmlquery"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
)
// ExtractRegex extracts text from a corpus and returns it
func (e *Extractor) ExtractRegex(corpus string) map[string]struct{} {
results := make(map[string]struct{})
groupPlusOne := e.RegexGroup + 1
for _, regex := range e.regexCompiled {
// skip prefix short-circuit for case-insensitive patterns
rstr := regex.String()
if !strings.Contains(rstr, "(?i") {
if prefix, ok := regex.LiteralPrefix(); ok && prefix != "" {
if !strings.Contains(corpus, prefix) {
continue
}
}
}
submatches := regex.FindAllStringSubmatch(corpus, -1)
for _, match := range submatches {
if len(match) < groupPlusOne {
continue
}
matchString := match[e.RegexGroup]
if _, ok := results[matchString]; !ok {
results[matchString] = struct{}{}
}
}
}
return results
}
// ExtractKval extracts key value pairs from a data map
func (e *Extractor) ExtractKval(data map[string]interface{}) map[string]struct{} {
if e.CaseInsensitive {
inputData := data
data = make(map[string]interface{}, len(inputData))
for k, v := range inputData {
if s, ok := v.(string); ok {
v = strings.ToLower(s)
}
data[strings.ToLower(k)] = v
}
}
results := make(map[string]struct{})
for _, k := range e.KVal {
item, ok := data[k]
if !ok {
continue
}
itemString := types.ToString(item)
if _, ok := results[itemString]; !ok {
results[itemString] = struct{}{}
}
}
return results
}
// ExtractXPath extracts items from text using XPath selectors
func (e *Extractor) ExtractXPath(corpus string) map[string]struct{} {
if strings.HasPrefix(corpus, "
Example Domain
Example Domain
This domain is for use in illustrative examples in documents. You may use this
domain in literature without prior coordination or asking for permission.
`
e := &Extractor{Type: ExtractorTypeHolder{ExtractorType: XPathExtractor}, XPath: []string{"/html/body/div/p[2]/a"}}
err := e.CompileExtractors()
require.Nil(t, err)
got := e.ExtractXPath(body)
require.Equal(t, map[string]struct{}{"More information...": {}}, got)
e = &Extractor{Type: ExtractorTypeHolder{ExtractorType: XPathExtractor}, XPath: []string{"/html/body/div/p[3]/a"}}
got = e.ExtractXPath(body)
require.Equal(t, map[string]struct{}{}, got)
}
func TestExtractor_ExtractJSON(t *testing.T) {
e := &Extractor{Type: ExtractorTypeHolder{ExtractorType: JSONExtractor}, JSON: []string{".[] | .id"}}
err := e.CompileExtractors()
require.Nil(t, err)
got := e.ExtractJSON(`[{"id": 1}]`)
require.Equal(t, map[string]struct{}{"1": {}}, got)
got = e.ExtractJSON(`{"id": 1}`)
require.Equal(t, map[string]struct{}{}, got)
}
func TestExtractor_ExtractDSL(t *testing.T) {
e := &Extractor{Type: ExtractorTypeHolder{ExtractorType: DSLExtractor}, DSL: []string{"to_upper(hello)"}}
err := e.CompileExtractors()
require.Nil(t, err)
got := e.ExtractDSL(map[string]interface{}{"hello": "hi"})
require.Equal(t, map[string]struct{}{"HI": {}}, got)
got = e.ExtractDSL(map[string]interface{}{"hi": "hello"})
require.Equal(t, map[string]struct{}{}, got)
}
================================================
FILE: pkg/operators/extractors/extractor_types.go
================================================
package extractors
import (
"errors"
"strings"
"github.com/invopop/jsonschema"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
)
// ExtractorType is the type of the extractor specified
type ExtractorType int
// name:ExtractorType
const (
// name:regex
RegexExtractor ExtractorType = iota + 1
// name:kval
KValExtractor
// name:xpath
XPathExtractor
// name:json
JSONExtractor
// name:dsl
DSLExtractor
limit
)
// extractorMappings is a table for conversion of extractor type from string.
var extractorMappings = map[ExtractorType]string{
RegexExtractor: "regex",
KValExtractor: "kval",
XPathExtractor: "xpath",
JSONExtractor: "json",
DSLExtractor: "dsl",
}
// GetType returns the type of the matcher
func (e *Extractor) GetType() ExtractorType {
return e.Type.ExtractorType
}
// GetSupportedExtractorTypes returns list of supported types
func GetSupportedExtractorTypes() []ExtractorType {
var result []ExtractorType
for index := ExtractorType(1); index < limit; index++ {
result = append(result, index)
}
return result
}
func toExtractorTypes(valueToMap string) (ExtractorType, error) {
normalizedValue := normalizeValue(valueToMap)
for key, currentValue := range extractorMappings {
if normalizedValue == currentValue {
return key, nil
}
}
return -1, errors.New("Invalid extractor type: " + valueToMap)
}
func normalizeValue(value string) string {
return strings.TrimSpace(strings.ToLower(value))
}
func (t ExtractorType) String() string {
return extractorMappings[t]
}
// ExtractorTypeHolder is used to hold internal type of the extractor
type ExtractorTypeHolder struct {
ExtractorType ExtractorType `mapping:"true"`
}
func (holder ExtractorTypeHolder) JSONSchema() *jsonschema.Schema {
gotType := &jsonschema.Schema{
Type: "string",
Title: "type of the extractor",
Description: "Type of the extractor",
}
for _, types := range GetSupportedExtractorTypes() {
gotType.Enum = append(gotType.Enum, types.String())
}
return gotType
}
func (holder *ExtractorTypeHolder) UnmarshalYAML(unmarshal func(interface{}) error) error {
var marshalledTypes string
if err := unmarshal(&marshalledTypes); err != nil {
return err
}
computedType, err := toExtractorTypes(marshalledTypes)
if err != nil {
return err
}
holder.ExtractorType = computedType
return nil
}
func (holder *ExtractorTypeHolder) UnmarshalJSON(data []byte) error {
s := strings.Trim(string(data), `"`)
if s == "" {
return nil
}
computedType, err := toExtractorTypes(s)
if err != nil {
return err
}
holder.ExtractorType = computedType
return nil
}
func (holder *ExtractorTypeHolder) MarshalJSON() ([]byte, error) {
return json.Marshal(holder.ExtractorType.String())
}
func (holder ExtractorTypeHolder) MarshalYAML() (interface{}, error) {
return holder.ExtractorType.String(), nil
}
================================================
FILE: pkg/operators/extractors/extractors.go
================================================
package extractors
import (
"regexp"
"github.com/Knetic/govaluate"
"github.com/itchyny/gojq"
)
// Extractor is used to extract part of response using a regex.
type Extractor struct {
// description: |
// Name of the extractor. Name should be lowercase and must not contain
// spaces or underscores (_).
// examples:
// - value: "\"cookie-extractor\""
Name string `yaml:"name,omitempty" json:"name,omitempty" jsonschema:"title=name of the extractor,description=Name of the extractor"`
// description: |
// Type is the type of the extractor.
Type ExtractorTypeHolder `json:"type" yaml:"type"`
// extractorType is the internal type of the extractor
extractorType ExtractorType
// description: |
// Regex contains the regular expression patterns to extract from a part.
//
// Go regex engine does not support lookaheads or lookbehinds, so as a result
// they are also not supported in nuclei.
// examples:
// - name: Braintree Access Token Regex
// value: >
// []string{"access_token\\$production\\$[0-9a-z]{16}\\$[0-9a-f]{32}"}
// - name: Wordpress Author Extraction regex
// value: >
// []string{"Author:(?:[A-Za-z0-9 -\\_=\"]+)?([A-Za-z0-9]+)<\\/span>"}
Regex []string `yaml:"regex,omitempty" json:"regex,omitempty" jsonschema:"title=regex to extract from part,description=Regex to extract from part"`
// description: |
// Group specifies a numbered group to extract from the regex.
// examples:
// - name: Example Regex Group
// value: "1"
RegexGroup int `yaml:"group,omitempty" json:"group,omitempty" jsonschema:"title=group to extract from regex,description=Group to extract from regex"`
// regexCompiled is the compiled variant
regexCompiled []*regexp.Regexp
// description: |
// kval contains the key-value pairs present in the HTTP response header.
// kval extractor can be used to extract HTTP response header and cookie key-value pairs.
// kval extractor inputs are case-insensitive, and does not support dash (-) in input which can replaced with underscores (_)
// For example, Content-Type should be replaced with content_type
//
// A list of supported parts is available in docs for request types.
// examples:
// - name: Extract Server Header From HTTP Response
// value: >
// []string{"server"}
// - name: Extracting value of PHPSESSID Cookie
// value: >
// []string{"phpsessid"}
// - name: Extracting value of Content-Type Cookie
// value: >
// []string{"content_type"}
KVal []string `yaml:"kval,omitempty" json:"kval,omitempty" jsonschema:"title=kval pairs to extract from response,description=Kval pairs to extract from response"`
// description: |
// JSON allows using jq-style syntax to extract items from json response
//
// examples:
// - value: >
// []string{".[] | .id"}
// - value: >
// []string{".batters | .batter | .[] | .id"}
JSON []string `yaml:"json,omitempty" json:"json,omitempty" jsonschema:"title=json jq expressions to extract data,description=JSON JQ expressions to evaluate from response part"`
// description: |
// XPath allows using xpath expressions to extract items from html response
//
// examples:
// - value: >
// []string{"/html/body/div/p[2]/a"}
XPath []string `yaml:"xpath,omitempty" json:"xpath,omitempty" jsonschema:"title=html xpath expressions to extract data,description=XPath allows using xpath expressions to extract items from html response"`
// description: |
// Attribute is an optional attribute to extract from response XPath.
//
// examples:
// - value: "\"href\""
Attribute string `yaml:"attribute,omitempty" json:"attribute,omitempty" jsonschema:"title=optional attribute to extract from xpath,description=Optional attribute to extract from response XPath"`
// jsonCompiled is the compiled variant
jsonCompiled []*gojq.Code
// description: |
// Extracts using DSL expressions.
DSL []string `yaml:"dsl,omitempty" json:"dsl,omitempty" jsonschema:"title=dsl expressions to extract,description=Optional attribute to extract from response dsl"`
dslCompiled []*govaluate.EvaluableExpression
// description: |
// Part is the part of the request response to extract data from.
//
// Each protocol exposes a lot of different parts which are well
// documented in docs for each request type.
// examples:
// - value: "\"body\""
// - value: "\"raw\""
Part string `yaml:"part,omitempty" json:"part,omitempty" jsonschema:"title=part of response to extract data from,description=Part of the request response to extract data from"`
// description: |
// Internal, when set to true will allow using the value extracted
// in the next request for some protocols (like HTTP).
Internal bool `yaml:"internal,omitempty" json:"internal,omitempty" jsonschema:"title=mark extracted value for internal variable use,description=Internal when set to true will allow using the value extracted in the next request for some protocols"`
// description: |
// CaseInsensitive enables case-insensitive extractions. Default is false.
// values:
// - false
// - true
CaseInsensitive bool `yaml:"case-insensitive,omitempty" json:"case-insensitive,omitempty" jsonschema:"title=use case insensitive extract,description=use case insensitive extract"`
}
================================================
FILE: pkg/operators/extractors/util.go
================================================
package extractors
// SupportsMap determines if the extractor type requires a map
func SupportsMap(extractor *Extractor) bool {
return extractor.Type.ExtractorType == KValExtractor || extractor.Type.ExtractorType == DSLExtractor
}
================================================
FILE: pkg/operators/matchers/compile.go
================================================
package matchers
import (
"encoding/hex"
"fmt"
"regexp"
"strings"
"github.com/Knetic/govaluate"
"github.com/projectdiscovery/nuclei/v3/pkg/operators/cache"
"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl"
)
// CompileMatchers performs the initial setup operation on a matcher
func (matcher *Matcher) CompileMatchers() error {
var ok bool
// Support hexadecimal encoding for matchers too.
if matcher.Encoding == "hex" {
for i, word := range matcher.Words {
if decoded, err := hex.DecodeString(word); err == nil && len(decoded) > 0 {
matcher.Words[i] = string(decoded)
}
}
}
// Set up the matcher type
computedType, err := toMatcherTypes(matcher.GetType().String())
if err != nil {
return fmt.Errorf("unknown matcher type specified: %s", matcher.Type)
}
matcher.matcherType = computedType
// Validate the matcher structure
if err := matcher.Validate(); err != nil {
return err
}
// By default, match on body if user hasn't provided any specific items
if matcher.Part == "" && matcher.GetType() != DSLMatcher {
matcher.Part = "body"
}
// Compile the regexes (with shared cache)
for _, regex := range matcher.Regex {
if cached, err := cache.Regex().GetIFPresent(regex); err == nil && cached != nil {
matcher.regexCompiled = append(matcher.regexCompiled, cached)
continue
}
compiled, err := regexp.Compile(regex)
if err != nil {
return fmt.Errorf("could not compile regex: %s", regex)
}
_ = cache.Regex().Set(regex, compiled)
matcher.regexCompiled = append(matcher.regexCompiled, compiled)
}
// Compile and validate binary Values in matcher
for _, value := range matcher.Binary {
if decoded, err := hex.DecodeString(value); err != nil {
return fmt.Errorf("could not hex decode binary: %s", value)
} else {
matcher.binaryDecoded = append(matcher.binaryDecoded, string(decoded))
}
}
// Compile the dsl expressions (with shared cache)
for _, dslExpression := range matcher.DSL {
if cached, err := cache.DSL().GetIFPresent(dslExpression); err == nil && cached != nil {
matcher.dslCompiled = append(matcher.dslCompiled, cached)
continue
}
compiledExpression, err := govaluate.NewEvaluableExpressionWithFunctions(dslExpression, dsl.HelperFunctions)
if err != nil {
return &dsl.CompilationError{DslSignature: dslExpression, WrappedError: err}
}
_ = cache.DSL().Set(dslExpression, compiledExpression)
matcher.dslCompiled = append(matcher.dslCompiled, compiledExpression)
}
// Set up the condition type, if any.
if matcher.Condition != "" {
matcher.condition, ok = ConditionTypes[matcher.Condition]
if !ok {
return fmt.Errorf("unknown condition specified: %s", matcher.Condition)
}
} else {
matcher.condition = ORCondition
}
if matcher.CaseInsensitive {
if matcher.GetType() != WordsMatcher {
return fmt.Errorf("case-insensitive flag is supported only for 'word' matchers (not '%s')", matcher.Type)
}
for i := range matcher.Words {
matcher.Words[i] = strings.ToLower(matcher.Words[i])
}
}
return nil
}
// GetType returns the condition type of the matcher
// todo: the field should be exposed natively
func (matcher *Matcher) GetCondition() ConditionType {
return matcher.condition
}
================================================
FILE: pkg/operators/matchers/doc.go
================================================
// Package matchers implements matchers for http response
// matching with templates.
package matchers
================================================
FILE: pkg/operators/matchers/match.go
================================================
package matchers
import (
"os"
"strings"
"github.com/Knetic/govaluate"
"github.com/antchfx/htmlquery"
"github.com/antchfx/xmlquery"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
stringsutil "github.com/projectdiscovery/utils/strings"
)
var (
// showDSLErr controls whether to show hidden DSL errors or not
showDSLErr = strings.EqualFold(os.Getenv("SHOW_DSL_ERRORS"), "true")
)
// MatchStatusCode matches a status code check against a corpus
func (matcher *Matcher) MatchStatusCode(statusCode int) bool {
// Iterate over all the status codes accepted as valid
//
// Status codes don't support AND conditions.
for _, status := range matcher.Status {
// Continue if the status codes don't match
if statusCode != status {
continue
}
// Return on the first match.
return true
}
return false
}
// MatchSize matches a size check against a corpus
func (matcher *Matcher) MatchSize(length int) bool {
// Iterate over all the sizes accepted as valid
//
// Sizes codes don't support AND conditions.
for _, size := range matcher.Size {
// Continue if the size doesn't match
if length != size {
continue
}
// Return on the first match.
return true
}
return false
}
// MatchWords matches a word check against a corpus.
func (matcher *Matcher) MatchWords(corpus string, data map[string]interface{}) (bool, []string) {
if matcher.CaseInsensitive {
corpus = strings.ToLower(corpus)
}
var matchedWords []string
// Iterate over all the words accepted as valid
for i, word := range matcher.Words {
if data == nil {
data = make(map[string]interface{})
}
var err error
word, err = expressions.Evaluate(word, data)
if err != nil {
gologger.Warning().Msgf("Error while evaluating word matcher: %q", word)
if matcher.condition == ANDCondition {
return false, []string{}
}
}
// Continue if the word doesn't match
if !strings.Contains(corpus, word) {
// If we are in an AND request and a match failed,
// return false as the AND condition fails on any single mismatch.
switch matcher.condition {
case ANDCondition:
return false, []string{}
case ORCondition:
continue
}
}
// If the condition was an OR, return on the first match.
if matcher.condition == ORCondition && !matcher.MatchAll {
return true, []string{word}
}
matchedWords = append(matchedWords, word)
// If we are at the end of the words, return with true
if len(matcher.Words)-1 == i && !matcher.MatchAll {
return true, matchedWords
}
}
if len(matchedWords) > 0 && matcher.MatchAll {
return true, matchedWords
}
return false, []string{}
}
// MatchRegex matches a regex check against a corpus
func (matcher *Matcher) MatchRegex(corpus string) (bool, []string) {
var matchedRegexes []string
// Iterate over all the regexes accepted as valid
for i, regex := range matcher.regexCompiled {
// Literal prefix short-circuit
rstr := regex.String()
if !strings.Contains(rstr, "(?i") { // covers (?i) and (?i:
if prefix, ok := regex.LiteralPrefix(); ok && prefix != "" {
if !strings.Contains(corpus, prefix) {
switch matcher.condition {
case ANDCondition:
return false, []string{}
case ORCondition:
continue
}
}
}
}
// Fast OR-path: return first match without full scan
if matcher.condition == ORCondition && !matcher.MatchAll {
m := regex.FindAllString(corpus, 1)
if len(m) == 0 {
continue
}
return true, m
}
// Single scan: get all matches directly
currentMatches := regex.FindAllString(corpus, -1)
if len(currentMatches) == 0 {
switch matcher.condition {
case ANDCondition:
return false, []string{}
case ORCondition:
continue
}
}
// If the condition was an OR (and MatchAll true), we still need to gather all
matchedRegexes = append(matchedRegexes, currentMatches...)
// If we are at the end of the regex, return with true
if len(matcher.regexCompiled)-1 == i && !matcher.MatchAll {
return true, matchedRegexes
}
}
if len(matchedRegexes) > 0 && matcher.MatchAll {
return true, matchedRegexes
}
return false, []string{}
}
// MatchBinary matches a binary check against a corpus
func (matcher *Matcher) MatchBinary(corpus string) (bool, []string) {
var matchedBinary []string
// Iterate over all the words accepted as valid
for i, binary := range matcher.binaryDecoded {
if !strings.Contains(corpus, binary) {
// If we are in an AND request and a match failed,
// return false as the AND condition fails on any single mismatch.
switch matcher.condition {
case ANDCondition:
return false, []string{}
case ORCondition:
continue
}
}
// If the condition was an OR, return on the first match.
if matcher.condition == ORCondition {
return true, []string{binary}
}
matchedBinary = append(matchedBinary, binary)
// If we are at the end of the words, return with true
if len(matcher.Binary)-1 == i {
return true, matchedBinary
}
}
return false, []string{}
}
// MatchDSL matches on a generic map result
func (matcher *Matcher) MatchDSL(data map[string]interface{}) bool {
logExpressionEvaluationFailure := func(matcherName string, err error) {
gologger.Warning().Msgf("Could not evaluate expression: %s, error: %s", matcherName, err.Error())
}
// Iterate over all the expressions accepted as valid
for i, expression := range matcher.dslCompiled {
if varErr := expressions.ContainsUnresolvedVariables(expression.String()); varErr != nil {
resolvedExpression, err := expressions.Evaluate(expression.String(), data)
if err != nil {
logExpressionEvaluationFailure(matcher.Name, err)
return false
}
expression, err = govaluate.NewEvaluableExpressionWithFunctions(resolvedExpression, dsl.HelperFunctions)
if err != nil {
logExpressionEvaluationFailure(matcher.Name, err)
return false
}
}
result, err := expression.Evaluate(data)
if err != nil {
if matcher.condition == ANDCondition {
return false
}
if !matcher.ignoreErr(err) {
gologger.Warning().Msgf("[%s] %s", data["template-id"], err.Error())
}
continue
}
if boolResult, ok := result.(bool); !ok {
gologger.Error().Label("WRN").Msgf("[%s] The return value of a DSL statement must return a boolean value.", data["template-id"])
continue
} else if !boolResult {
// If we are in an AND request and a match failed,
// return false as the AND condition fails on any single mismatch.
switch matcher.condition {
case ANDCondition:
return false
case ORCondition:
continue
}
}
// If the condition was an OR, return on the first match.
if matcher.condition == ORCondition {
return true
}
// If we are at the end of the dsl, return with true
if len(matcher.dslCompiled)-1 == i {
return true
}
}
return false
}
// MatchXPath matches on a generic map result
func (matcher *Matcher) MatchXPath(corpus string) bool {
if strings.HasPrefix(corpus, " 0
}
// MatchXML matches items from XML using XPath selectors
func (matcher *Matcher) MatchXML(corpus string) bool {
doc, err := xmlquery.Parse(strings.NewReader(corpus))
if err != nil {
return false
}
matches := 0
for _, k := range matcher.XPath {
nodes, err := xmlquery.QueryAll(doc, k)
if err != nil {
continue
}
// Continue if the xpath doesn't return any nodes
if len(nodes) == 0 {
// If we are in an AND request and a match failed,
// return false as the AND condition fails on any single mismatch.
switch matcher.condition {
case ANDCondition:
return false
case ORCondition:
continue
}
}
// If the condition was an OR, return on the first match.
if matcher.condition == ORCondition && !matcher.MatchAll {
return true
}
matches = matches + len(nodes)
}
return matches > 0
}
// ignoreErr checks if the error is to be ignored or not
// Reference: https://github.com/projectdiscovery/nuclei/issues/3950
func (m *Matcher) ignoreErr(err error) bool {
if showDSLErr {
return false
}
if stringsutil.ContainsAny(err.Error(), "No parameter", "error parsing argument value") {
return true
}
return false
}
================================================
FILE: pkg/operators/matchers/match_test.go
================================================
package matchers
import (
"testing"
"github.com/Knetic/govaluate"
"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl"
"github.com/stretchr/testify/require"
)
func TestWordANDCondition(t *testing.T) {
m := &Matcher{condition: ANDCondition, Words: []string{"a", "b"}}
isMatched, matched := m.MatchWords("a b", nil)
require.True(t, isMatched, "Could not match words with valid AND condition")
require.Equal(t, m.Words, matched)
isMatched, matched = m.MatchWords("b", nil)
require.False(t, isMatched, "Could match words with invalid AND condition")
require.Equal(t, []string{}, matched)
}
func TestRegexANDCondition(t *testing.T) {
m := &Matcher{Type: MatcherTypeHolder{MatcherType: RegexMatcher}, Condition: "and", Regex: []string{"[a-z]{3}", "\\d{2}"}}
err := m.CompileMatchers()
require.Nil(t, err)
isMatched, matched := m.MatchRegex("abc abcd 123")
require.True(t, isMatched, "Could not match regex with valid AND condition")
require.Equal(t, []string{"abc", "abc", "12"}, matched)
isMatched, matched = m.MatchRegex("bc 1")
require.False(t, isMatched, "Could match regex with invalid AND condition")
require.Equal(t, []string{}, matched)
}
func TestORCondition(t *testing.T) {
m := &Matcher{condition: ORCondition, Words: []string{"a", "b"}}
isMatched, matched := m.MatchWords("a b", nil)
require.True(t, isMatched, "Could not match valid word OR condition")
require.Equal(t, []string{"a"}, matched)
isMatched, matched = m.MatchWords("b", nil)
require.True(t, isMatched, "Could not match valid word OR condition")
require.Equal(t, []string{"b"}, matched)
isMatched, matched = m.MatchWords("c", nil)
require.False(t, isMatched, "Could match invalid word OR condition")
require.Equal(t, []string{}, matched)
}
func TestRegexOrCondition(t *testing.T) {
m := &Matcher{Type: MatcherTypeHolder{MatcherType: RegexMatcher}, Condition: "or", Regex: []string{"[a-z]{3}", "\\d{2}"}}
err := m.CompileMatchers()
require.Nil(t, err)
isMatched, matched := m.MatchRegex("ab 123")
require.True(t, isMatched, "Could not match valid regex OR condition")
require.Equal(t, []string{"12"}, matched)
isMatched, matched = m.MatchRegex("bc 1")
require.False(t, isMatched, "Could match invalid regex OR condition")
require.Equal(t, []string{}, matched)
}
func TestHexEncoding(t *testing.T) {
m := &Matcher{Encoding: "hex", Type: MatcherTypeHolder{MatcherType: WordsMatcher}, Part: "body", Words: []string{"50494e47"}}
err := m.CompileMatchers()
require.Nil(t, err, "could not compile matcher")
isMatched, matched := m.MatchWords("PING", nil)
require.True(t, isMatched, "Could not match valid Hex condition")
require.Equal(t, m.Words, matched)
}
func TestMatcher_MatchDSL(t *testing.T) {
compiled, err := govaluate.NewEvaluableExpressionWithFunctions("contains(body, \"{{VARIABLE}}\")", dsl.HelperFunctions)
require.Nil(t, err, "couldn't compile expression")
m := &Matcher{Type: MatcherTypeHolder{MatcherType: DSLMatcher}, dslCompiled: []*govaluate.EvaluableExpression{compiled}}
err = m.CompileMatchers()
require.Nil(t, err, "could not compile matcher")
values := []string{"PING", "pong"}
for _, value := range values {
isMatched := m.MatchDSL(map[string]interface{}{"body": value, "VARIABLE": value})
require.True(t, isMatched)
}
}
func TestMatcher_MatchXPath_HTML(t *testing.T) {
body := `
Example Domain
Example Domain
This domain is for use in illustrative examples in documents. You may use this
domain in literature without prior coordination or asking for permission.
`
// single match
m := &Matcher{Type: MatcherTypeHolder{MatcherType: XPathMatcher}, XPath: []string{"/html/body/div/p[2]/a"}}
err := m.CompileMatchers()
require.Nil(t, err)
isMatched := m.MatchXPath(body)
require.True(t, isMatched, "Could not match valid XPath")
isMatched = m.MatchXPath("
aaaaaaaaa")
require.False(t, isMatched, "Could match invalid XPath")
// OR match
m = &Matcher{Type: MatcherTypeHolder{MatcherType: XPathMatcher}, Condition: "or", XPath: []string{"/html/head/title[contains(text(), 'PATRICAAA')]", "/html/body/div/p[2]/a"}}
err = m.CompileMatchers()
require.Nil(t, err)
isMatched = m.MatchXPath(body)
require.True(t, isMatched, "Could not match valid multi-XPath with OR condition")
isMatched = m.MatchXPath(body2)
require.False(t, isMatched, "Could match invalid multi-XPath with OR condition")
// AND match
m = &Matcher{Type: MatcherTypeHolder{MatcherType: XPathMatcher}, Condition: "and", XPath: []string{"/html/head/title[contains(text(), 'Example Domain')]", "/html/body/div/p[2]/a"}}
err = m.CompileMatchers()
require.Nil(t, err)
isMatched = m.MatchXPath(body)
require.True(t, isMatched, "Could not match valid multi-XPath with AND condition")
isMatched = m.MatchXPath(body2)
require.False(t, isMatched, "Could match invalid multi-XPath with AND condition")
// invalid xpath
m = &Matcher{Type: MatcherTypeHolder{MatcherType: XPathMatcher}, XPath: []string{"//a[@a==1]"}}
_ = m.CompileMatchers()
isMatched = m.MatchXPath(body)
require.False(t, isMatched, "Invalid xpath did not return false")
}
func TestMatcher_MatchXPath_XML(t *testing.T) {
body := `barbaz`
body2 := `baralo`
// single match
m := &Matcher{Type: MatcherTypeHolder{MatcherType: XPathMatcher}, XPath: []string{"//foo[contains(text(), 'bar')]"}}
err := m.CompileMatchers()
require.Nil(t, err)
isMatched := m.MatchXPath(body)
require.True(t, isMatched, "Could not match valid XPath")
isMatched = m.MatchXPath("
aaaaaaaaa
")
require.False(t, isMatched, "Could match invalid XPath")
// OR match
m = &Matcher{Type: MatcherTypeHolder{MatcherType: XPathMatcher}, Condition: "or", XPath: []string{"/foo[contains(text(), 'PATRICAAA')]", "/parent/child"}}
err = m.CompileMatchers()
require.Nil(t, err)
isMatched = m.MatchXPath(body)
require.True(t, isMatched, "Could not match valid multi-XPath with OR condition")
isMatched = m.MatchXPath(body2)
require.False(t, isMatched, "Could match invalid multi-XPath with OR condition")
// AND match
m = &Matcher{Type: MatcherTypeHolder{MatcherType: XPathMatcher}, Condition: "and", XPath: []string{"/foo[contains(text(), 'bar')]", "/parent/child"}}
err = m.CompileMatchers()
require.Nil(t, err)
isMatched = m.MatchXPath(body)
require.True(t, isMatched, "Could not match valid multi-XPath with AND condition")
isMatched = m.MatchXPath(body2)
require.False(t, isMatched, "Could match invalid multi-XPath with AND condition")
// invalid xpath
m = &Matcher{Type: MatcherTypeHolder{MatcherType: XPathMatcher}, XPath: []string{"//a[@a==1]"}}
_ = m.CompileMatchers()
isMatched = m.MatchXPath(body)
require.False(t, isMatched, "Invalid xpath did not return false")
// invalid xml
isMatched = m.MatchXPath("
not right notvalid")
require.False(t, isMatched, "Invalid xpath did not return false")
}
func TestMatchRegex_CaseInsensitivePrefixSkip(t *testing.T) {
m := &Matcher{Type: MatcherTypeHolder{MatcherType: RegexMatcher}, Condition: "or", Regex: []string{"(?i)abc"}}
err := m.CompileMatchers()
require.NoError(t, err)
ok, got := m.MatchRegex("zzz AbC yyy")
require.True(t, ok)
require.NotEmpty(t, got)
}
func TestMatchStatusCodeAndSize(t *testing.T) {
mStatus := &Matcher{Status: []int{200, 302}}
require.True(t, mStatus.MatchStatusCode(200))
require.True(t, mStatus.MatchStatusCode(302))
require.False(t, mStatus.MatchStatusCode(404))
mSize := &Matcher{Size: []int{5, 10}}
require.True(t, mSize.MatchSize(5))
require.False(t, mSize.MatchSize(7))
}
func TestMatchBinary_AND_OR(t *testing.T) {
// AND should fail if any binary not present
mAnd := &Matcher{Type: MatcherTypeHolder{MatcherType: BinaryMatcher}, Condition: "and", Binary: []string{"50494e47", "414141"}} // "PING", "AAA"
require.NoError(t, mAnd.CompileMatchers())
ok, _ := mAnd.MatchBinary("PING")
require.False(t, ok)
// OR should succeed if any present
mOr := &Matcher{Type: MatcherTypeHolder{MatcherType: BinaryMatcher}, Condition: "or", Binary: []string{"414141", "50494e47"}} // "AAA", "PING"
require.NoError(t, mOr.CompileMatchers())
ok, got := mOr.MatchBinary("xxPINGyy")
require.True(t, ok)
require.NotEmpty(t, got)
}
func TestMatchRegex_LiteralPrefixShortCircuit(t *testing.T) {
// AND: first regex has literal prefix "abc"; corpus lacks it => early false
mAnd := &Matcher{Type: MatcherTypeHolder{MatcherType: RegexMatcher}, Condition: "and", Regex: []string{"abc[0-9]*", "[0-9]{2}"}}
require.NoError(t, mAnd.CompileMatchers())
ok, matches := mAnd.MatchRegex("zzz 12 yyy")
require.False(t, ok)
require.Empty(t, matches)
// OR: first regex skipped due to missing prefix, second matches => true
mOr := &Matcher{Type: MatcherTypeHolder{MatcherType: RegexMatcher}, Condition: "or", Regex: []string{"abc[0-9]*", "[0-9]{2}"}}
require.NoError(t, mOr.CompileMatchers())
ok, matches = mOr.MatchRegex("zzz 12 yyy")
require.True(t, ok)
require.Equal(t, []string{"12"}, matches)
}
func TestMatcher_MatchDSL_ErrorHandling(t *testing.T) {
// First expression errors (division by zero), second is true
bad, err := govaluate.NewEvaluableExpression("1 / 0")
require.NoError(t, err)
good, err := govaluate.NewEvaluableExpression("1 == 1")
require.NoError(t, err)
m := &Matcher{Type: MatcherTypeHolder{MatcherType: DSLMatcher}, Condition: "or", dslCompiled: []*govaluate.EvaluableExpression{bad, good}}
require.NoError(t, m.CompileMatchers())
ok := m.MatchDSL(map[string]interface{}{})
require.True(t, ok)
}
================================================
FILE: pkg/operators/matchers/matchers.go
================================================
package matchers
import (
"regexp"
"github.com/Knetic/govaluate"
)
// Matcher is used to match a part in the output from a protocol.
type Matcher struct {
// description: |
// Type is the type of the matcher.
Type MatcherTypeHolder `yaml:"type" json:"type" jsonschema:"title=type of matcher,description=Type of the matcher,enum=status,enum=size,enum=word,enum=regex,enum=binary,enum=dsl"`
// description: |
// Condition is the optional condition between two matcher variables. By default,
// the condition is assumed to be OR.
// values:
// - "and"
// - "or"
Condition string `yaml:"condition,omitempty" json:"condition,omitempty" jsonschema:"title=condition between matcher variables,description=Condition between the matcher variables,enum=and,enum=or"`
// description: |
// Part is the part of the request response to match data from.
//
// Each protocol exposes a lot of different parts which are well
// documented in docs for each request type.
// examples:
// - value: "\"body\""
// - value: "\"raw\""
Part string `yaml:"part,omitempty" json:"part,omitempty" jsonschema:"title=part of response to match,description=Part of response to match data from"`
// description: |
// Negative specifies if the match should be reversed
// It will only match if the condition is not true.
Negative bool `yaml:"negative,omitempty" json:"negative,omitempty" jsonschema:"title=negative specifies if match reversed,description=Negative specifies if the match should be reversed. It will only match if the condition is not true"`
// description: |
// Name of the matcher. Name should be lowercase and must not contain
// spaces or underscores (_).
// examples:
// - value: "\"cookie-matcher\""
Name string `yaml:"name,omitempty" json:"name,omitempty" jsonschema:"title=name of the matcher,description=Name of the matcher"`
// description: |
// Status are the acceptable status codes for the response.
// examples:
// - value: >
// []int{200, 302}
Status []int `yaml:"status,omitempty" json:"status,omitempty" jsonschema:"title=status to match,description=Status to match for the response"`
// description: |
// Size is the acceptable size for the response
// examples:
// - value: >
// []int{3029, 2042}
Size []int `yaml:"size,omitempty" json:"size,omitempty" jsonschema:"title=acceptable size for response,description=Size is the acceptable size for the response"`
// description: |
// Words contains word patterns required to be present in the response part.
// examples:
// - name: Match for Outlook mail protection domain
// value: >
// []string{"mail.protection.outlook.com"}
// - name: Match for application/json in response headers
// value: >
// []string{"application/json"}
Words []string `yaml:"words,omitempty" json:"words,omitempty" jsonschema:"title=words to match in response,description= Words contains word patterns required to be present in the response part"`
// description: |
// Regex contains Regular Expression patterns required to be present in the response part.
// examples:
// - name: Match for Linkerd Service via Regex
// value: >
// []string{`(?mi)^Via\\s*?:.*?linkerd.*$`}
// - name: Match for Open Redirect via Location header
// value: >
// []string{`(?m)^(?:Location\\s*?:\\s*?)(?:https?://|//)?(?:[a-zA-Z0-9\\-_\\.@]*)example\\.com.*$`}
Regex []string `yaml:"regex,omitempty" json:"regex,omitempty" jsonschema:"title=regex to match in response,description=Regex contains regex patterns required to be present in the response part"`
// description: |
// Binary are the binary patterns required to be present in the response part.
// examples:
// - name: Match for Springboot Heapdump Actuator "JAVA PROFILE", "HPROF", "Gunzip magic byte"
// value: >
// []string{"4a4156412050524f46494c45", "4850524f46", "1f8b080000000000"}
// - name: Match for 7zip files
// value: >
// []string{"377ABCAF271C"}
Binary []string `yaml:"binary,omitempty" json:"binary,omitempty" jsonschema:"title=binary patterns to match in response,description=Binary are the binary patterns required to be present in the response part"`
// description: |
// DSL are the dsl expressions that will be evaluated as part of nuclei matching rules.
// A list of these helper functions are available [here](https://nuclei.projectdiscovery.io/templating-guide/helper-functions/).
// examples:
// - name: DSL Matcher for package.json file
// value: >
// []string{"contains(body, 'packages') && contains(tolower(all_headers), 'application/octet-stream') && status_code == 200"}
// - name: DSL Matcher for missing strict transport security header
// value: >
// []string{"!contains(tolower(all_headers), ''strict-transport-security'')"}
DSL []string `yaml:"dsl,omitempty" json:"dsl,omitempty" jsonschema:"title=dsl expressions to match in response,description=DSL are the dsl expressions that will be evaluated as part of nuclei matching rules"`
// description: |
// XPath are the xpath queries expressions that will be evaluated against the response part.
// examples:
// - name: XPath Matcher to check a title
// value: >
// []string{"/html/head/title[contains(text(), 'How to Find XPath')]"}
// - name: XPath Matcher for finding links with target="_blank"
// value: >
// []string{"//a[@target=\"_blank\"]"}
XPath []string `yaml:"xpath,omitempty" json:"xpath,omitempty" jsonschema:"title=xpath queries to match in response,description=xpath are the XPath queries that will be evaluated against the response part of nuclei matching rules"`
// description: |
// Encoding specifies the encoding for the words field if any.
// values:
// - "hex"
Encoding string `yaml:"encoding,omitempty" json:"encoding,omitempty" jsonschema:"title=encoding for word field,description=Optional encoding for the word fields,enum=hex"`
// description: |
// CaseInsensitive enables case-insensitive matches. Default is false.
// values:
// - false
// - true
CaseInsensitive bool `yaml:"case-insensitive,omitempty" json:"case-insensitive,omitempty" jsonschema:"title=use case insensitive match,description=use case insensitive match"`
// description: |
// MatchAll enables matching for all matcher values. Default is false.
// values:
// - false
// - true
MatchAll bool `yaml:"match-all,omitempty" json:"match-all,omitempty" jsonschema:"title=match all values,description=match all matcher values ignoring condition"`
// description: |
// Internal when true hides the matcher from output. Default is false.
// It is meant to be used in multiprotocol / flow templates to create internal matcher condition without printing it in output.
// or other similar use cases.
// values:
// - false
// - true
Internal bool `yaml:"internal,omitempty" json:"internal,omitempty" jsonschema:"title=hide matcher from output,description=hide matcher from output"`
// cached data for the compiled matcher
condition ConditionType // todo: this field should be the one used for overridden marshal ops
matcherType MatcherType
binaryDecoded []string
regexCompiled []*regexp.Regexp
dslCompiled []*govaluate.EvaluableExpression
}
// ConditionType is the type of condition for matcher
type ConditionType int
const (
// ANDCondition matches responses with AND condition in arguments.
ANDCondition ConditionType = iota + 1
// ORCondition matches responses with AND condition in arguments.
ORCondition
)
// ConditionTypes is a table for conversion of condition type from string.
var ConditionTypes = map[string]ConditionType{
"and": ANDCondition,
"or": ORCondition,
}
// Result reverts the results of the match if the matcher is of type negative.
func (matcher *Matcher) Result(data bool) bool {
if matcher.Negative {
return !data
}
return data
}
// ResultWithMatchedSnippet returns true and the matched snippet, or false and an empty string
func (matcher *Matcher) ResultWithMatchedSnippet(data bool, matchedSnippet []string) (bool, []string) {
if matcher.Negative {
return !data, []string{}
}
return data, matchedSnippet
}
================================================
FILE: pkg/operators/matchers/matchers_types.go
================================================
package matchers
import (
"errors"
"strings"
"github.com/invopop/jsonschema"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
)
// MatcherType is the type of the matcher specified
type MatcherType int
// name:MatcherType
const (
// name:word
WordsMatcher MatcherType = iota + 1
// name:regex
RegexMatcher
// name:binary
BinaryMatcher
// name:status
StatusMatcher
// name:size
SizeMatcher
// name:dsl
DSLMatcher
// name:xpath
XPathMatcher
limit
)
// MatcherTypes is a table for conversion of matcher type from string.
var MatcherTypes = map[MatcherType]string{
StatusMatcher: "status",
SizeMatcher: "size",
WordsMatcher: "word",
RegexMatcher: "regex",
BinaryMatcher: "binary",
DSLMatcher: "dsl",
XPathMatcher: "xpath",
}
// GetType returns the type of the matcher
func (matcher *Matcher) GetType() MatcherType {
return matcher.Type.MatcherType
}
// GetSupportedMatcherTypes returns list of supported types
func GetSupportedMatcherTypes() []MatcherType {
var result []MatcherType
for index := MatcherType(1); index < limit; index++ {
result = append(result, index)
}
return result
}
func toMatcherTypes(valueToMap string) (MatcherType, error) {
normalizedValue := normalizeValue(valueToMap)
for key, currentValue := range MatcherTypes {
if normalizedValue == currentValue {
return key, nil
}
}
return -1, errors.New("Invalid matcher type: " + valueToMap)
}
func normalizeValue(value string) string {
return strings.TrimSpace(strings.ToLower(value))
}
func (t MatcherType) String() string {
return MatcherTypes[t]
}
// MatcherTypeHolder is used to hold internal type of the matcher
type MatcherTypeHolder struct {
MatcherType MatcherType `mapping:"true"`
}
func (t MatcherTypeHolder) String() string {
return t.MatcherType.String()
}
func (holder MatcherTypeHolder) JSONSchema() *jsonschema.Schema {
gotType := &jsonschema.Schema{
Type: "string",
Title: "type of the matcher",
Description: "Type of the matcher",
}
for _, types := range GetSupportedMatcherTypes() {
gotType.Enum = append(gotType.Enum, types.String())
}
return gotType
}
func (holder *MatcherTypeHolder) UnmarshalYAML(unmarshal func(interface{}) error) error {
var marshalledTypes string
if err := unmarshal(&marshalledTypes); err != nil {
return err
}
computedType, err := toMatcherTypes(marshalledTypes)
if err != nil {
return err
}
holder.MatcherType = computedType
return nil
}
func (holder *MatcherTypeHolder) UnmarshalJSON(data []byte) error {
s := strings.Trim(string(data), `"`)
if s == "" {
return nil
}
computedType, err := toMatcherTypes(s)
if err != nil {
return err
}
holder.MatcherType = computedType
return nil
}
func (holder MatcherTypeHolder) MarshalJSON() ([]byte, error) {
return json.Marshal(holder.MatcherType.String())
}
func (holder MatcherTypeHolder) MarshalYAML() (interface{}, error) {
return holder.MatcherType.String(), nil
}
================================================
FILE: pkg/operators/matchers/validate.go
================================================
package matchers
import (
"errors"
"fmt"
"reflect"
"strings"
"github.com/antchfx/xpath"
sliceutil "github.com/projectdiscovery/utils/slice"
)
var commonExpectedFields = []string{"Type", "Condition", "Name", "MatchAll", "Negative", "Internal"}
// Validate perform initial validation on the matcher structure
func (matcher *Matcher) Validate() error {
// Build a map of YAML‐tag names that are actually set (non-zero) in the matcher.
matcherMap := make(map[string]interface{})
val := reflect.ValueOf(*matcher)
typ := reflect.TypeOf(*matcher)
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
// skip internal / unexported or opt-out fields
yamlTag := strings.Split(field.Tag.Get("yaml"), ",")[0]
if yamlTag == "" || yamlTag == "-" {
continue
}
if val.Field(i).IsZero() {
continue
}
matcherMap[yamlTag] = struct{}{}
}
var err error
var expectedFields []string
switch matcher.matcherType {
case DSLMatcher:
expectedFields = append(commonExpectedFields, "DSL")
case StatusMatcher:
expectedFields = append(commonExpectedFields, "Status", "Part")
case SizeMatcher:
expectedFields = append(commonExpectedFields, "Size", "Part")
case WordsMatcher:
expectedFields = append(commonExpectedFields, "Words", "Part", "Encoding", "CaseInsensitive")
case BinaryMatcher:
expectedFields = append(commonExpectedFields, "Binary", "Part", "Encoding", "CaseInsensitive")
case RegexMatcher:
expectedFields = append(commonExpectedFields, "Regex", "Part", "Encoding", "CaseInsensitive")
case XPathMatcher:
expectedFields = append(commonExpectedFields, "XPath", "Part")
}
if err = checkFields(matcher, matcherMap, expectedFields...); err != nil {
return err
}
// validate the XPath query
if matcher.matcherType == XPathMatcher {
for _, query := range matcher.XPath {
if _, err = xpath.Compile(query); err != nil {
return err
}
}
}
return nil
}
func checkFields(m *Matcher, matcherMap map[string]interface{}, expectedFields ...string) error {
var foundUnexpectedFields []string
for marshaledFieldName := range matcherMap {
// revert back the marshaled name to the original field
structFieldName, err := getFieldNameFromYamlTag(marshaledFieldName, *m)
if err != nil {
return err
}
if !sliceutil.Contains(expectedFields, structFieldName) {
foundUnexpectedFields = append(foundUnexpectedFields, structFieldName)
}
}
if len(foundUnexpectedFields) > 0 {
return fmt.Errorf("matcher %s has unexpected fields: %s", m.matcherType, strings.Join(foundUnexpectedFields, ","))
}
return nil
}
func getFieldNameFromYamlTag(tagName string, object interface{}) (string, error) {
reflectType := reflect.TypeOf(object)
if reflectType.Kind() != reflect.Struct {
return "", errors.New("the object must be a struct")
}
for idx := 0; idx < reflectType.NumField(); idx++ {
field := reflectType.Field(idx)
tagParts := strings.Split(field.Tag.Get("yaml"), ",")
if len(tagParts) > 0 && tagParts[0] == tagName {
return field.Name, nil
}
}
return "", fmt.Errorf("field %s not found", tagName)
}
================================================
FILE: pkg/operators/matchers/validate_test.go
================================================
package matchers
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestValidate(t *testing.T) {
m := &Matcher{matcherType: DSLMatcher, DSL: []string{"anything"}}
err := m.Validate()
require.Nil(t, err, "Could not validate correct template")
m = &Matcher{matcherType: DSLMatcher, Part: "test"}
err = m.Validate()
require.NotNil(t, err, "Invalid template was correctly validated")
m = &Matcher{matcherType: XPathMatcher, XPath: []string{"//q[@id=\"foo\"]"}}
err = m.Validate()
require.Nil(t, err, "Could not validate correct XPath template")
m = &Matcher{matcherType: XPathMatcher, Status: []int{123}}
err = m.Validate()
require.NotNil(t, err, "Invalid XPath template was correctly validated")
m = &Matcher{matcherType: XPathMatcher, XPath: []string{"//a[@a==1]"}}
err = m.Validate()
require.NotNil(t, err, "Invalid XPath query was correctly validated")
}
================================================
FILE: pkg/operators/operators.go
================================================
package operators
import (
"fmt"
"maps"
"strconv"
"strings"
"github.com/pkg/errors"
"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors"
"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/excludematchers"
sliceutil "github.com/projectdiscovery/utils/slice"
)
// Operators contains the operators that can be applied on protocols
type Operators struct {
// description: |
// Matchers contains the detection mechanism for the request to identify
// whether the request was successful by doing pattern matching
// on request/responses.
//
// Multiple matchers can be combined with `matcher-condition` flag
// which accepts either `and` or `or` as argument.
Matchers []*matchers.Matcher `yaml:"matchers,omitempty" json:"matchers,omitempty" jsonschema:"title=matchers to run on response,description=Detection mechanism to identify whether the request was successful by doing pattern matching"`
// description: |
// Extractors contains the extraction mechanism for the request to identify
// and extract parts of the response.
Extractors []*extractors.Extractor `yaml:"extractors,omitempty" json:"extractors,omitempty" jsonschema:"title=extractors to run on response,description=Extractors contains the extraction mechanism for the request to identify and extract parts of the response"`
// description: |
// MatchersCondition is the condition between the matchers. Default is OR.
// values:
// - "and"
// - "or"
MatchersCondition string `yaml:"matchers-condition,omitempty" json:"matchers-condition,omitempty" jsonschema:"title=condition between the matchers,description=Conditions between the matchers,enum=and,enum=or"`
// cached variables that may be used along with request.
matchersCondition matchers.ConditionType
// TemplateID is the ID of the template for matcher
TemplateID string `json:"-" yaml:"-" jsonschema:"-"`
// ExcludeMatchers is a list of excludeMatchers items
ExcludeMatchers *excludematchers.ExcludeMatchers `json:"-" yaml:"-" jsonschema:"-"`
}
// Compile compiles the operators as well as their corresponding matchers and extractors
func (operators *Operators) Compile() error {
if operators.MatchersCondition != "" {
operators.matchersCondition = matchers.ConditionTypes[operators.MatchersCondition]
} else {
operators.matchersCondition = matchers.ORCondition
}
for _, matcher := range operators.Matchers {
if err := matcher.CompileMatchers(); err != nil {
return errors.Wrap(err, "could not compile matcher")
}
}
for _, extractor := range operators.Extractors {
if err := extractor.CompileExtractors(); err != nil {
return errors.Wrap(err, "could not compile extractor")
}
}
return nil
}
func (operators *Operators) HasDSL() bool {
for _, matcher := range operators.Matchers {
if len(matcher.DSL) > 0 {
return true
}
}
for _, extractor := range operators.Extractors {
if len(extractor.DSL) > 0 {
return true
}
}
return false
}
// GetMatchersCondition returns the condition for the matchers
func (operators *Operators) GetMatchersCondition() matchers.ConditionType {
return operators.matchersCondition
}
// Result is a result structure created from operators running on data.
type Result struct {
// Matched is true if any matchers matched
Matched bool
// Extracted is true if any result type values were extracted
Extracted bool
// Matches is a map of matcher names that we matched
Matches map[string][]string
// Extracts contains all the data extracted from inputs
Extracts map[string][]string
// OutputExtracts is the list of extracts to be displayed on screen.
OutputExtracts []string
outputUnique map[string]struct{}
// DynamicValues contains any dynamic values to be templated
DynamicValues map[string][]string
// PayloadValues contains payload values provided by user. (Optional)
PayloadValues map[string]interface{}
// Optional lineCounts for file protocol
LineCount string
// Operators is reference to operators that generated this result (Read-Only)
Operators *Operators
}
func (result *Result) HasMatch(name string) bool {
return result.hasItem(name, result.Matches)
}
func (result *Result) HasExtract(name string) bool {
return result.hasItem(name, result.Extracts)
}
func (result *Result) hasItem(name string, m map[string][]string) bool {
for matchName := range m {
if strings.EqualFold(name, matchName) {
return true
}
}
return false
}
// MakeDynamicValuesCallback takes an input dynamic values map and calls
// the callback function with all variations of the data in input in form
// of map[string]string (interface{}).
func MakeDynamicValuesCallback(input map[string][]string, iterateAllValues bool, callback func(map[string]interface{}) bool) {
output := make(map[string]interface{}, len(input))
if !iterateAllValues {
for k, v := range input {
if len(v) > 0 {
output[k] = v[0]
}
}
callback(output)
return
}
inputIndex := make(map[string]int, len(input))
var maxValue int
for _, v := range input {
if len(v) > maxValue {
maxValue = len(v)
}
}
for i := 0; i < maxValue; i++ {
for k, v := range input {
if len(v) == 0 {
continue
}
if len(v) == 1 {
output[k] = v[0]
continue
}
if gotIndex, ok := inputIndex[k]; !ok {
inputIndex[k] = 0
output[k] = v[0]
} else {
newIndex := gotIndex + 1
if newIndex >= len(v) {
output[k] = v[len(v)-1]
continue
}
output[k] = v[newIndex]
inputIndex[k] = newIndex
}
}
// skip if the callback says so
if callback(output) {
return
}
}
}
// Merge merges a result structure into the other.
func (r *Result) Merge(result *Result) {
if !r.Matched && result.Matched {
r.Matched = result.Matched
}
if !r.Extracted && result.Extracted {
r.Extracted = result.Extracted
}
for k, v := range result.Matches {
r.Matches[k] = sliceutil.Dedupe(append(r.Matches[k], v...))
}
for k, v := range result.Extracts {
r.Extracts[k] = sliceutil.Dedupe(append(r.Extracts[k], v...))
}
r.outputUnique = make(map[string]struct{})
output := r.OutputExtracts
r.OutputExtracts = make([]string, 0, len(output))
for _, v := range output {
if _, ok := r.outputUnique[v]; !ok {
r.outputUnique[v] = struct{}{}
r.OutputExtracts = append(r.OutputExtracts, v)
}
}
for _, v := range result.OutputExtracts {
if _, ok := r.outputUnique[v]; !ok {
r.outputUnique[v] = struct{}{}
r.OutputExtracts = append(r.OutputExtracts, v)
}
}
for k, v := range result.DynamicValues {
if _, ok := r.DynamicValues[k]; !ok {
r.DynamicValues[k] = v
} else {
r.DynamicValues[k] = sliceutil.Dedupe(append(r.DynamicValues[k], v...))
}
}
maps.Copy(r.PayloadValues, result.PayloadValues)
}
// MatchFunc performs matching operation for a matcher on model and returns true or false.
type MatchFunc func(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string)
// ExtractFunc performs extracting operation for an extractor on model and returns true or false.
type ExtractFunc func(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{}
// Execute executes the operators on data and returns a result structure
func (operators *Operators) Execute(data map[string]interface{}, match MatchFunc, extract ExtractFunc, isDebug bool) (*Result, bool) {
matcherCondition := operators.GetMatchersCondition()
var matches bool
result := &Result{
Matches: make(map[string][]string),
Extracts: make(map[string][]string),
DynamicValues: make(map[string][]string),
outputUnique: make(map[string]struct{}),
Operators: operators,
}
// state variable to check if all extractors are internal
var allInternalExtractors = true
// Start with the extractors first and evaluate them.
for _, extractor := range operators.Extractors {
if !extractor.Internal && allInternalExtractors {
allInternalExtractors = false
}
var extractorResults []string
for match := range extract(data, extractor) {
extractorResults = append(extractorResults, match)
if extractor.Internal {
if data, ok := result.DynamicValues[extractor.Name]; !ok {
result.DynamicValues[extractor.Name] = []string{match}
} else {
result.DynamicValues[extractor.Name] = append(data, match)
}
} else {
if _, ok := result.outputUnique[match]; !ok {
result.OutputExtracts = append(result.OutputExtracts, match)
result.outputUnique[match] = struct{}{}
}
}
}
if len(extractorResults) > 0 && !extractor.Internal && extractor.Name != "" {
result.Extracts[extractor.Name] = extractorResults
}
// update data with whatever was extracted doesn't matter if it is internal or not (skip unless it empty)
if len(extractorResults) > 0 {
data[extractor.Name] = getExtractedValue(extractorResults)
}
}
// expose dynamic values to same request matchers
if len(result.DynamicValues) > 0 {
dataDynamicValues := make(map[string]interface{})
for dynName, dynValues := range result.DynamicValues {
if len(dynValues) > 1 {
for dynIndex, dynValue := range dynValues {
dynKeyName := fmt.Sprintf("%s%d", dynName, dynIndex)
dataDynamicValues[dynKeyName] = dynValue
}
dataDynamicValues[dynName] = dynValues
} else {
dataDynamicValues[dynName] = dynValues[0]
}
}
data = generators.MergeMaps(data, dataDynamicValues)
}
for matcherIndex, matcher := range operators.Matchers {
// Skip matchers that are in the blocklist
if operators.ExcludeMatchers != nil {
if operators.ExcludeMatchers.Match(operators.TemplateID, matcher.Name) {
continue
}
}
if isMatch, matched := match(data, matcher); isMatch {
if isDebug { // matchers without an explicit name or with AND condition should only be made visible if debug is enabled
matcherName := GetMatcherName(matcher, matcherIndex)
result.Matches[matcherName] = matched
} else { // if it's a "named" matcher with OR condition, then display it
if matcherCondition == matchers.ORCondition && matcher.Name != "" {
result.Matches[matcher.Name] = matched
}
}
matches = true
} else if matcherCondition == matchers.ANDCondition {
if len(result.DynamicValues) > 0 {
return result, true
}
return result, false
}
}
result.Matched = matches
result.Extracted = len(result.OutputExtracts) > 0
if len(result.DynamicValues) > 0 && allInternalExtractors {
// only return early if all extractors are internal
// if some are internal and some are not then followthrough
return result, true
}
// Don't print if we have matchers, and they have not matched, regardless of extractor
if len(operators.Matchers) > 0 && !matches {
// if dynamic values are present then it is not a failure
if len(result.DynamicValues) > 0 {
return result, true
}
return nil, false
}
// Write a final string of output if matcher type is
// AND or if we have extractors for the mechanism too.
if len(result.Extracts) > 0 || len(result.OutputExtracts) > 0 || matches {
return result, true
}
// if dynamic values are present then it is not a failure
if len(result.DynamicValues) > 0 {
return result, true
}
return nil, false
}
// GetMatcherName returns matchername of given matcher
func GetMatcherName(matcher *matchers.Matcher, matcherIndex int) string {
if matcher.Name != "" {
return matcher.Name
} else {
return matcher.Type.String() + "-" + strconv.Itoa(matcherIndex+1) // making the index start from 1 to be more readable
}
}
// ExecuteInternalExtractors executes internal dynamic extractors
func (operators *Operators) ExecuteInternalExtractors(data map[string]interface{}, extract ExtractFunc) map[string]interface{} {
dynamicValues := make(map[string]interface{})
// Start with the extractors first and evaluate them.
for _, extractor := range operators.Extractors {
if !extractor.Internal {
continue
}
for match := range extract(data, extractor) {
if _, ok := dynamicValues[extractor.Name]; !ok {
dynamicValues[extractor.Name] = match
}
}
}
return dynamicValues
}
// IsEmpty determines if the operator has matchers or extractors
func (operators *Operators) IsEmpty() bool {
return operators.Len() == 0
}
// Len calculates the sum of the number of matchers and extractors
func (operators *Operators) Len() int {
return len(operators.Matchers) + len(operators.Extractors)
}
// getExtractedValue takes array of extracted values if it only has one value
// then it is flattened and returned as a string else original type is returned
func getExtractedValue(values []string) any {
if len(values) == 1 {
return values[0]
} else {
return values
}
}
// EvalBoolSlice evaluates a slice of bools using a logical AND
func EvalBoolSlice(slice []bool, isAnd bool) bool {
if len(slice) == 0 {
return false
}
result := slice[0]
for _, b := range slice[1:] {
if isAnd {
result = result && b
} else {
result = result || b
}
}
return result
}
================================================
FILE: pkg/operators/operators_test.go
================================================
package operators
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestMakeDynamicValuesCallback(t *testing.T) {
input := map[string][]string{
"a": {"1", "2"},
"b": {"3"},
"c": {},
"d": {"A", "B", "C"},
}
count := 0
MakeDynamicValuesCallback(input, true, func(data map[string]interface{}) bool {
count++
require.Len(t, data, 3, "could not get correct output length")
return false
})
require.Equal(t, 3, count, "could not get correct result count")
t.Run("all", func(t *testing.T) {
input := map[string][]string{
"a": {"1"},
"b": {"2"},
"c": {"3"},
}
count := 0
MakeDynamicValuesCallback(input, true, func(data map[string]interface{}) bool {
count++
require.Len(t, data, 3, "could not get correct output length")
return false
})
require.Equal(t, 1, count, "could not get correct result count")
})
t.Run("first", func(t *testing.T) {
input := map[string][]string{
"a": {"1", "2"},
"b": {"3"},
"c": {},
"d": {"A", "B", "C"},
}
count := 0
MakeDynamicValuesCallback(input, false, func(data map[string]interface{}) bool {
count++
require.Len(t, data, 3, "could not get correct output length")
return false
})
require.Equal(t, 1, count, "could not get correct result count")
})
}
================================================
FILE: pkg/output/doc.go
================================================
// Package output implements output writing interfaces for nuclei.
package output
================================================
FILE: pkg/output/file_output_writer.go
================================================
package output
import (
"os"
"sync"
)
// fileWriter is a concurrent file based output writer.
type fileWriter struct {
file *os.File
mu sync.Mutex
}
// NewFileOutputWriter creates a new buffered writer for a file
func newFileOutputWriter(file string, resume bool) (*fileWriter, error) {
var output *os.File
var err error
if resume {
output, err = os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
} else {
output, err = os.Create(file)
}
if err != nil {
return nil, err
}
return &fileWriter{file: output}, nil
}
// WriteString writes an output to the underlying file
func (w *fileWriter) Write(data []byte) (int, error) {
w.mu.Lock()
defer w.mu.Unlock()
if _, err := w.file.Write(data); err != nil {
return 0, err
}
if _, err := w.file.Write([]byte("\n")); err != nil {
return 0, err
}
return len(data) + 1, nil
}
// Close closes the underlying writer flushing everything to disk
func (w *fileWriter) Close() error {
w.mu.Lock()
defer w.mu.Unlock()
//nolint:errcheck // we don't care whether sync failed or succeeded.
w.file.Sync()
return w.file.Close()
}
================================================
FILE: pkg/output/format_json.go
================================================
package output
import (
jsoniter "github.com/json-iterator/go"
)
// formatJSON formats the output for json based formatting
func (w *StandardWriter) formatJSON(output *ResultEvent) ([]byte, error) {
if !w.jsonReqResp { // don't show request-response in json if not asked
output.Request = ""
output.Response = ""
}
return jsoniter.Marshal(output)
}
================================================
FILE: pkg/output/format_screen.go
================================================
package output
import (
"bytes"
"strconv"
"strings"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
mapsutil "github.com/projectdiscovery/utils/maps"
)
// formatScreen formats the output for showing on screen.
func (w *StandardWriter) formatScreen(output *ResultEvent) []byte {
builder := &bytes.Buffer{}
if !w.noMetadata {
if w.timestamp {
builder.WriteRune('[')
builder.WriteString(w.aurora.Cyan(output.Timestamp.Format("2006-01-02 15:04:05")).String())
builder.WriteString("] ")
}
builder.WriteRune('[')
builder.WriteString(w.aurora.BrightGreen(output.TemplateID).String())
if output.MatcherName != "" {
builder.WriteString(":")
builder.WriteString(w.aurora.BrightGreen(output.MatcherName).Bold().String())
} else if output.ExtractorName != "" {
builder.WriteString(":")
builder.WriteString(w.aurora.BrightGreen(output.ExtractorName).Bold().String())
}
if w.matcherStatus {
builder.WriteString("] [")
if !output.MatcherStatus {
builder.WriteString(w.aurora.Red("failed").String())
} else {
builder.WriteString(w.aurora.Green("matched").String())
}
}
if output.GlobalMatchers {
builder.WriteString("] [")
builder.WriteString(w.aurora.BrightMagenta("global").String())
}
builder.WriteString("] [")
builder.WriteString(w.aurora.BrightBlue(output.Type).String())
builder.WriteString("] ")
builder.WriteString("[")
builder.WriteString(w.severityColors(output.Info.SeverityHolder.Severity))
builder.WriteString("] ")
}
if output.Matched != "" {
builder.WriteString(output.Matched)
} else {
builder.WriteString(output.Host)
}
// If any extractors, write the results
if len(output.ExtractedResults) > 0 {
builder.WriteString(" [")
for i, item := range output.ExtractedResults {
// trim trailing space
// quote non-ascii and non printable characters and then
// unquote quotes (`"`) for readability
item = strings.TrimSpace(item)
item = strconv.QuoteToASCII(item)
item = strings.ReplaceAll(item, `\"`, `"`)
builder.WriteString(w.aurora.BrightCyan(item).String())
if i != len(output.ExtractedResults)-1 {
builder.WriteRune(',')
}
}
builder.WriteString("]")
}
if len(output.Lines) > 0 {
builder.WriteString(" [LN: ")
for i, line := range output.Lines {
builder.WriteString(strconv.Itoa(line))
if i != len(output.Lines)-1 {
builder.WriteString(",")
}
}
builder.WriteString("]")
}
// Write meta if any
if len(output.Metadata) > 0 {
builder.WriteString(" [")
first := true
// sort to get predictable output
for _, name := range mapsutil.GetSortedKeys(output.Metadata) {
value := output.Metadata[name]
if !first {
builder.WriteRune(',')
}
first = false
builder.WriteString(w.aurora.BrightYellow(name).String())
builder.WriteRune('=')
builder.WriteString(w.aurora.BrightYellow(strconv.QuoteToASCII(types.ToString(value))).String())
}
builder.WriteString("]")
}
// If it is a fuzzing output, enrich with additional
// metadata for the match.
if output.IsFuzzingResult {
if output.FuzzingParameter != "" {
builder.WriteString(" [")
builder.WriteString(output.FuzzingPosition)
builder.WriteRune(':')
builder.WriteString(w.aurora.BrightMagenta(output.FuzzingParameter).String())
builder.WriteString("]")
}
builder.WriteString(" [")
builder.WriteString(output.FuzzingMethod)
builder.WriteString("]")
}
return builder.Bytes()
}
================================================
FILE: pkg/output/multi_writer.go
================================================
package output
import (
"github.com/logrusorgru/aurora"
)
type MultiWriter struct {
writers []Writer
}
var _ Writer = &MultiWriter{}
// NewMultiWriter creates a new MultiWriter instance
func NewMultiWriter(writers ...Writer) *MultiWriter {
return &MultiWriter{writers: writers}
}
func (mw *MultiWriter) Close() {
for _, writer := range mw.writers {
writer.Close()
}
}
func (mw *MultiWriter) Colorizer() aurora.Aurora {
// Return the colorizer of the first writer
if len(mw.writers) > 0 {
return mw.writers[0].Colorizer()
}
// Default to a no-op colorizer
return aurora.NewAurora(false)
}
func (mw *MultiWriter) Write(event *ResultEvent) error {
for _, writer := range mw.writers {
if err := writer.Write(event); err != nil {
return err
}
}
return nil
}
func (mw *MultiWriter) WriteFailure(event *InternalWrappedEvent) error {
for _, writer := range mw.writers {
if err := writer.WriteFailure(event); err != nil {
return err
}
}
return nil
}
func (mw *MultiWriter) Request(templateID, url, requestType string, err error) {
for _, writer := range mw.writers {
writer.Request(templateID, url, requestType, err)
}
}
func (mw *MultiWriter) WriteStoreDebugData(host, templateID, eventType string, data string) {
for _, writer := range mw.writers {
writer.WriteStoreDebugData(host, templateID, eventType, data)
}
}
func (mw *MultiWriter) RequestStatsLog(statusCode, response string) {
for _, writer := range mw.writers {
writer.RequestStatsLog(statusCode, response)
}
}
func (mw *MultiWriter) ResultCount() int {
count := 0
for _, writer := range mw.writers {
if count := writer.ResultCount(); count > 0 {
return count
}
}
return count
}
================================================
FILE: pkg/output/output.go
================================================
package output
import (
"encoding/base64"
"fmt"
"io"
"log/slog"
"maps"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/pkg/errors"
"go.uber.org/multierr"
jsoniter "github.com/json-iterator/go"
"github.com/logrusorgru/aurora"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/interactsh/pkg/server"
"github.com/projectdiscovery/nuclei/v3/internal/colorizer"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/model"
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
protocolUtils "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/nuclei/v3/pkg/types/nucleierr"
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
"github.com/projectdiscovery/utils/errkit"
fileutil "github.com/projectdiscovery/utils/file"
osutils "github.com/projectdiscovery/utils/os"
unitutils "github.com/projectdiscovery/utils/unit"
urlutil "github.com/projectdiscovery/utils/url"
)
// Writer is an interface which writes output to somewhere for nuclei events.
type Writer interface {
// Close closes the output writer interface
Close()
// Colorizer returns the colorizer instance for writer
Colorizer() aurora.Aurora
// Write writes the event to file and/or screen.
Write(*ResultEvent) error
// WriteFailure writes the optional failure event for template to file and/or screen.
WriteFailure(*InternalWrappedEvent) error
// Request logs a request in the trace log
Request(templateID, url, requestType string, err error)
// RequestStatsLog logs a request stats log
RequestStatsLog(statusCode, response string)
// WriteStoreDebugData writes the request/response debug data to file
WriteStoreDebugData(host, templateID, eventType string, data string)
// ResultCount returns the total number of results written
ResultCount() int
}
// StandardWriter is a writer writing output to file and screen for results.
type StandardWriter struct {
json bool
jsonReqResp bool
timestamp bool
noMetadata bool
matcherStatus bool
mutex *sync.Mutex
aurora aurora.Aurora
outputFile io.WriteCloser
traceFile io.WriteCloser
errorFile io.WriteCloser
severityColors func(severity.Severity) string
storeResponse bool
storeResponseDir string
omitTemplate bool
DisableStdout bool
AddNewLinesOutputFile bool // by default this is only done for stdout
KeysToRedact []string
// JSONLogRequestHook is a hook that can be used to log request/response
// when using custom server code with output
JSONLogRequestHook func(*JSONLogRequest)
resultCount atomic.Int32
}
var _ Writer = &StandardWriter{}
var decolorizerRegex = regexp.MustCompile(`\x1B\[[0-9;]*[a-zA-Z]`)
// InternalEvent is an internal output generation structure for nuclei.
type InternalEvent map[string]interface{}
func (ie InternalEvent) Set(k string, v interface{}) {
ie[k] = v
}
// InternalWrappedEvent is a wrapped event with operators result added to it.
type InternalWrappedEvent struct {
// Mutex is internal field which is implicitly used
// to synchronize callback(event) and interactsh polling updates
// Refer protocols/http.Request.ExecuteWithResults for more details
sync.RWMutex
InternalEvent InternalEvent
Results []*ResultEvent
OperatorsResult *operators.Result
UsesInteractsh bool
// Only applicable if interactsh is used
// This is used to avoid duplicate successful interactsh events
InteractshMatched atomic.Bool
}
func (iwe *InternalWrappedEvent) CloneShallow() *InternalWrappedEvent {
return &InternalWrappedEvent{
InternalEvent: maps.Clone(iwe.InternalEvent),
Results: nil,
OperatorsResult: nil,
UsesInteractsh: iwe.UsesInteractsh,
}
}
func (iwe *InternalWrappedEvent) HasOperatorResult() bool {
iwe.RLock()
defer iwe.RUnlock()
return iwe.OperatorsResult != nil
}
func (iwe *InternalWrappedEvent) HasResults() bool {
iwe.RLock()
defer iwe.RUnlock()
return len(iwe.Results) > 0
}
func (iwe *InternalWrappedEvent) SetOperatorResult(operatorResult *operators.Result) {
iwe.Lock()
defer iwe.Unlock()
iwe.OperatorsResult = operatorResult
}
// ResultEvent is a wrapped result event for a single nuclei output.
type ResultEvent struct {
// Template is the relative filename for the template
Template string `json:"template,omitempty"`
// TemplateURL is the URL of the template for the result inside the nuclei
// templates repository if it belongs to the repository.
TemplateURL string `json:"template-url,omitempty"`
// TemplateID is the ID of the template for the result.
TemplateID string `json:"template-id"`
// TemplatePath is the path of template
TemplatePath string `json:"template-path,omitempty"`
// TemplateEncoded is the base64 encoded template
TemplateEncoded string `json:"template-encoded,omitempty"`
// Info contains information block of the template for the result.
Info model.Info `json:"info,inline"`
// MatcherName is the name of the matcher matched if any.
MatcherName string `json:"matcher-name,omitempty"`
// ExtractorName is the name of the extractor matched if any.
ExtractorName string `json:"extractor-name,omitempty"`
// Type is the type of the result event.
Type string `json:"type"`
// Host is the host input on which match was found.
Host string `json:"host,omitempty"`
// Port is port of the host input on which match was found (if applicable).
Port string `json:"port,omitempty"`
// Scheme is the scheme of the host input on which match was found (if applicable).
Scheme string `json:"scheme,omitempty"`
// URL is the Base URL of the host input on which match was found (if applicable).
URL string `json:"url,omitempty"`
// Path is the path input on which match was found.
Path string `json:"path,omitempty"`
// Matched contains the matched input in its transformed form.
Matched string `json:"matched-at,omitempty"`
// ExtractedResults contains the extraction result from the inputs.
ExtractedResults []string `json:"extracted-results,omitempty"`
// Request is the optional, dumped request for the match.
Request string `json:"request,omitempty"`
// Response is the optional, dumped response for the match.
Response string `json:"response,omitempty"`
// Metadata contains any optional metadata for the event
Metadata map[string]interface{} `json:"meta,omitempty"`
// IP is the IP address for the found result event.
IP string `json:"ip,omitempty"`
// Timestamp is the time the result was found at.
Timestamp time.Time `json:"timestamp"`
// Interaction is the full details of interactsh interaction.
Interaction *server.Interaction `json:"interaction,omitempty"`
// CURLCommand is an optional curl command to reproduce the request
// Only applicable if the report is for HTTP.
CURLCommand string `json:"curl-command,omitempty"`
// MatcherStatus is the status of the match
MatcherStatus bool `json:"matcher-status"`
// Lines is the line count for the specified match
Lines []int `json:"matched-line,omitempty"`
// GlobalMatchers identifies whether the matches was detected in the response
// of another template's result event
GlobalMatchers bool `json:"global-matchers,omitempty"`
// IssueTrackers is the metadata for issue trackers
IssueTrackers map[string]IssueTrackerMetadata `json:"issue_trackers,omitempty"`
// ReqURLPattern when enabled contains base URL pattern that was used to generate the request
// must be enabled by setting protocols.ExecuterOptions.ExportReqURLPattern to true
ReqURLPattern string `json:"req_url_pattern,omitempty"`
// Fields related to HTTP Fuzzing functionality of nuclei.
// The output contains additional fields when the result is
// for a fuzzing template.
IsFuzzingResult bool `json:"is_fuzzing_result,omitempty"`
FuzzingMethod string `json:"fuzzing_method,omitempty"`
FuzzingParameter string `json:"fuzzing_parameter,omitempty"`
FuzzingPosition string `json:"fuzzing_position,omitempty"`
AnalyzerDetails string `json:"analyzer_details,omitempty"`
FileToIndexPosition map[string]int `json:"-"`
TemplateVerifier string `json:"-"`
Error string `json:"error,omitempty"`
}
type IssueTrackerMetadata struct {
// IssueID is the ID of the issue created
IssueID string `json:"id,omitempty"`
// IssueURL is the URL of the issue created
IssueURL string `json:"url,omitempty"`
}
// NewStandardWriter creates a new output writer based on user configurations
func NewStandardWriter(options *types.Options) (*StandardWriter, error) {
resumeBool := options.Resume != ""
auroraColorizer := aurora.NewAurora(!options.NoColor)
var outputFile io.WriteCloser
if options.Output != "" {
output, err := newFileOutputWriter(options.Output, resumeBool)
if err != nil {
return nil, errors.Wrap(err, "could not create output file")
}
outputFile = output
}
var traceOutput io.WriteCloser
if options.TraceLogFile != "" {
output, err := newFileOutputWriter(options.TraceLogFile, resumeBool)
if err != nil {
return nil, errors.Wrap(err, "could not create output file")
}
traceOutput = output
}
var errorOutput io.WriteCloser
if options.ErrorLogFile != "" {
output, err := newFileOutputWriter(options.ErrorLogFile, resumeBool)
if err != nil {
return nil, errors.Wrap(err, "could not create error file")
}
errorOutput = output
}
// Try to create output folder if it doesn't exist
if options.StoreResponse && !fileutil.FolderExists(options.StoreResponseDir) {
if err := fileutil.CreateFolder(options.StoreResponseDir); err != nil {
gologger.Fatal().Msgf("Could not create output directory '%s': %s\n", options.StoreResponseDir, err)
}
}
writer := &StandardWriter{
json: options.JSONL,
jsonReqResp: !options.OmitRawRequests,
noMetadata: options.NoMeta,
matcherStatus: options.MatcherStatus,
timestamp: options.Timestamp,
aurora: auroraColorizer,
mutex: &sync.Mutex{},
outputFile: outputFile,
traceFile: traceOutput,
errorFile: errorOutput,
severityColors: colorizer.New(auroraColorizer),
storeResponse: options.StoreResponse,
storeResponseDir: options.StoreResponseDir,
omitTemplate: options.OmitTemplate,
KeysToRedact: options.Redact,
}
if v := os.Getenv("DISABLE_STDOUT"); v == "true" || v == "1" {
writer.DisableStdout = true
}
return writer, nil
}
func (w *StandardWriter) ResultCount() int {
return int(w.resultCount.Load())
}
// Write writes the event to file and/or screen.
func (w *StandardWriter) Write(event *ResultEvent) error {
if event.Error != "" && !w.matcherStatus {
return nil
}
// Enrich the result event with extra metadata on the template-path and url.
if event.TemplatePath != "" {
event.Template, event.TemplateURL = utils.TemplatePathURL(types.ToString(event.TemplatePath), types.ToString(event.TemplateID), event.TemplateVerifier)
}
if len(w.KeysToRedact) > 0 {
event.Request = redactKeys(event.Request, w.KeysToRedact)
event.Response = redactKeys(event.Response, w.KeysToRedact)
event.CURLCommand = redactKeys(event.CURLCommand, w.KeysToRedact)
event.Matched = redactKeys(event.Matched, w.KeysToRedact)
}
event.Timestamp = time.Now()
var data []byte
var err error
if w.json {
data, err = w.formatJSON(event)
} else {
data = w.formatScreen(event)
}
if err != nil {
return errors.Wrap(err, "could not format output")
}
if len(data) == 0 {
return nil
}
w.mutex.Lock()
defer w.mutex.Unlock()
if !w.DisableStdout {
_, _ = os.Stdout.Write(data)
_, _ = os.Stdout.Write([]byte("\n"))
}
if w.outputFile != nil {
if !w.json {
data = decolorizerRegex.ReplaceAll(data, []byte(""))
}
if _, writeErr := w.outputFile.Write(data); writeErr != nil {
return errors.Wrap(writeErr, "could not write to output")
}
if w.AddNewLinesOutputFile && w.json {
_, _ = w.outputFile.Write([]byte("\n"))
}
}
w.resultCount.Add(1)
return nil
}
func redactKeys(data string, keysToRedact []string) string {
for _, key := range keysToRedact {
keyPattern := regexp.MustCompile(fmt.Sprintf(`(?i)(%s\s*[:=]\s*["']?)[^"'\r\n&]+(["'\r\n]?)`, regexp.QuoteMeta(key)))
data = keyPattern.ReplaceAllString(data, `$1***$2`)
}
return data
}
// JSONLogRequest is a trace/error log request written to file
type JSONLogRequest struct {
Template string `json:"template"`
Type string `json:"type"`
Input string `json:"input"`
Timestamp *time.Time `json:"timestamp,omitempty"`
Address string `json:"address"`
Error string `json:"error"`
Kind string `json:"kind,omitempty"`
Attrs interface{} `json:"attrs,omitempty"`
}
// Request writes a log the requests trace log
func (w *StandardWriter) Request(templatePath, input, requestType string, requestErr error) {
if w.traceFile == nil && w.errorFile == nil && w.JSONLogRequestHook == nil {
return
}
request := getJSONLogRequestFromError(templatePath, input, requestType, requestErr)
if w.timestamp {
ts := time.Now()
request.Timestamp = &ts
}
data, err := jsoniter.Marshal(request)
if err != nil {
return
}
if w.JSONLogRequestHook != nil {
w.JSONLogRequestHook(request)
}
if w.traceFile != nil {
_, _ = w.traceFile.Write(data)
}
if requestErr != nil && w.errorFile != nil {
_, _ = w.errorFile.Write(data)
}
}
func getJSONLogRequestFromError(templatePath, input, requestType string, requestErr error) *JSONLogRequest {
request := &JSONLogRequest{
Template: templatePath,
Input: input,
Type: requestType,
}
parsed, _ := urlutil.ParseAbsoluteURL(input, false)
if parsed != nil {
request.Address = parsed.Hostname()
port := parsed.Port()
if port == "" {
switch parsed.Scheme {
case urlutil.HTTP:
port = "80"
case urlutil.HTTPS:
port = "443"
}
}
request.Address += ":" + port
}
errX := errkit.FromError(requestErr)
if errX == nil {
request.Error = "none"
} else {
request.Kind = errkit.ErrKindUnknown.String()
var cause error
if len(errX.Errors()) > 1 {
cause = errX.Errors()[0]
}
if cause == nil {
cause = errX
}
cause = tryParseCause(cause)
request.Error = cause.Error()
request.Kind = errkit.GetErrorKind(requestErr, nucleierr.ErrTemplateLogic).String()
if len(errX.Attrs()) > 0 {
request.Attrs = slog.GroupValue(errX.Attrs()...)
}
}
// check if address slog attr is available in error if set use it
if val := errkit.GetAttrValue(requestErr, "address"); val.Any() != nil {
request.Address = val.String()
}
return request
}
// Colorizer returns the colorizer instance for writer
func (w *StandardWriter) Colorizer() aurora.Aurora {
return w.aurora
}
// Close closes the output writing interface
func (w *StandardWriter) Close() {
if w.outputFile != nil {
_ = w.outputFile.Close()
}
if w.traceFile != nil {
_ = w.traceFile.Close()
}
if w.errorFile != nil {
_ = w.errorFile.Close()
}
}
// WriteFailure writes the failure event for template to file and/or screen.
func (w *StandardWriter) WriteFailure(wrappedEvent *InternalWrappedEvent) error {
if !w.matcherStatus {
return nil
}
if len(wrappedEvent.Results) > 0 {
errs := []error{}
for _, result := range wrappedEvent.Results {
result.MatcherStatus = false // just in case
if err := w.Write(result); err != nil {
errs = append(errs, err)
}
}
if len(errs) > 0 {
return multierr.Combine(errs...)
}
return nil
}
// if no results were found, manually create a failure event
event := wrappedEvent.InternalEvent
templatePath, templateURL := utils.TemplatePathURL(types.ToString(event["template-path"]), types.ToString(event["template-id"]), types.ToString(event["template-verifier"]))
var templateInfo model.Info
if event["template-info"] != nil {
templateInfo = event["template-info"].(model.Info)
}
fields := protocolUtils.GetJsonFieldsFromURL(types.ToString(event["host"]))
if types.ToString(event["ip"]) != "" {
fields.Ip = types.ToString(event["ip"])
}
if types.ToString(event["path"]) != "" {
fields.Path = types.ToString(event["path"])
}
data := &ResultEvent{
Template: templatePath,
TemplateURL: templateURL,
TemplateID: types.ToString(event["template-id"]),
TemplatePath: types.ToString(event["template-path"]),
Info: templateInfo,
Type: types.ToString(event["type"]),
Host: fields.Host,
Path: fields.Path,
Port: fields.Port,
Scheme: fields.Scheme,
URL: fields.URL,
IP: fields.Ip,
Request: types.ToString(event["request"]),
Response: types.ToString(event["response"]),
MatcherStatus: false,
Timestamp: time.Now(),
//FIXME: this is workaround to encode the template when no results were found
TemplateEncoded: w.encodeTemplate(types.ToString(event["template-path"])),
Error: types.ToString(event["error"]),
}
return w.Write(data)
}
var maxTemplateFileSizeForEncoding = unitutils.Mega
func (w *StandardWriter) encodeTemplate(templatePath string) string {
data, err := os.ReadFile(templatePath)
if err == nil && !w.omitTemplate && len(data) <= maxTemplateFileSizeForEncoding && config.DefaultConfig.IsCustomTemplate(templatePath) {
return base64.StdEncoding.EncodeToString(data)
}
return ""
}
func sanitizeFileName(fileName string) string {
fileName = strings.ReplaceAll(fileName, "http:", "")
fileName = strings.ReplaceAll(fileName, "https:", "")
fileName = strings.ReplaceAll(fileName, "/", "_")
fileName = strings.ReplaceAll(fileName, "\\", "_")
fileName = strings.ReplaceAll(fileName, "-", "_")
fileName = strings.ReplaceAll(fileName, ".", "_")
if osutils.IsWindows() {
fileName = strings.ReplaceAll(fileName, ":", "_")
}
fileName = strings.TrimPrefix(fileName, "__")
return fileName
}
func (w *StandardWriter) WriteStoreDebugData(host, templateID, eventType string, data string) {
if w.storeResponse {
if len(host) > 60 {
host = host[:57] + "..."
}
if len(templateID) > 100 {
templateID = templateID[:97] + "..."
}
filename := sanitizeFileName(fmt.Sprintf("%s_%s", host, templateID))
subFolder := filepath.Join(w.storeResponseDir, sanitizeFileName(eventType))
if !fileutil.FolderExists(subFolder) {
_ = fileutil.CreateFolder(subFolder)
}
filename = filepath.Join(subFolder, fmt.Sprintf("%s.txt", filename))
f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
gologger.Error().Msgf("Could not open debug output file: %s", err)
return
}
_, _ = fmt.Fprintln(f, data)
_ = f.Close()
}
}
// tryParseCause tries to parse the cause of given error
// this is legacy support due to use of errorutil in existing libraries
// but this should not be required once all libraries are updated
func tryParseCause(err error) error {
if err == nil {
return nil
}
msg := err.Error()
if strings.HasPrefix(msg, "ReadStatusLine:") {
// last index is actual error (from rawhttp)
parts := strings.Split(msg, ":")
return errkit.New(strings.TrimSpace(parts[len(parts)-1]))
}
if strings.Contains(msg, "read ") {
// same here
parts := strings.Split(msg, ":")
return errkit.New(strings.TrimSpace(parts[len(parts)-1]))
}
return err
}
func (w *StandardWriter) RequestStatsLog(statusCode, response string) {}
================================================
FILE: pkg/output/output_stats.go
================================================
package output
import (
"github.com/logrusorgru/aurora"
"github.com/projectdiscovery/nuclei/v3/pkg/output/stats"
)
// StatsOutputWriter implements writer interface for stats observation
type StatsOutputWriter struct {
colorizer aurora.Aurora
Tracker *stats.Tracker
}
var _ Writer = &StatsOutputWriter{}
// NewStatsOutputWriter returns a new StatsOutputWriter instance.
func NewTrackerWriter(t *stats.Tracker) *StatsOutputWriter {
return &StatsOutputWriter{
colorizer: aurora.NewAurora(true),
Tracker: t,
}
}
func (tw *StatsOutputWriter) Close() {}
func (tw *StatsOutputWriter) Colorizer() aurora.Aurora {
return tw.colorizer
}
func (tw *StatsOutputWriter) Write(event *ResultEvent) error {
return nil
}
func (tw *StatsOutputWriter) WriteFailure(event *InternalWrappedEvent) error {
return nil
}
func (tw *StatsOutputWriter) Request(templateID, url, requestType string, err error) {
if err == nil {
return
}
jsonReq := getJSONLogRequestFromError(templateID, url, requestType, err)
tw.Tracker.TrackErrorKind(jsonReq.Error)
}
func (tw *StatsOutputWriter) WriteStoreDebugData(host, templateID, eventType string, data string) {}
func (tw *StatsOutputWriter) RequestStatsLog(statusCode, response string) {
tw.Tracker.TrackStatusCode(statusCode)
tw.Tracker.TrackWAFDetected(response)
}
func (tw *StatsOutputWriter) ResultCount() int {
return 0
}
================================================
FILE: pkg/output/output_test.go
================================================
package output
import (
"fmt"
"strings"
"testing"
"github.com/pkg/errors"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/stretchr/testify/require"
)
func TestStandardWriterRequest(t *testing.T) {
t.Run("WithoutTraceAndError", func(t *testing.T) {
w, err := NewStandardWriter(&types.Options{})
require.NoError(t, err)
require.NotPanics(t, func() {
w.Request("path", "input", "http", nil)
w.Close()
})
})
t.Run("TraceAndErrorWithoutError", func(t *testing.T) {
traceWriter := &testWriteCloser{}
errorWriter := &testWriteCloser{}
w, err := NewStandardWriter(&types.Options{})
w.traceFile = traceWriter
w.errorFile = errorWriter
require.NoError(t, err)
w.Request("path", "input", "http", nil)
require.Equal(t, `{"template":"path","type":"http","input":"input","address":"input:","error":"none"}`, traceWriter.String())
require.Empty(t, errorWriter.String())
})
t.Run("ErrorWithWrappedError", func(t *testing.T) {
errorWriter := &testWriteCloser{}
w, err := NewStandardWriter(&types.Options{})
w.errorFile = errorWriter
require.NoError(t, err)
w.Request(
"misconfiguration/tcpconfig.yaml",
"https://example.com/tcpconfig.html",
"http",
fmt.Errorf("GET https://example.com/tcpconfig.html/tcpconfig.html giving up after 2 attempts: %w", errors.New("context deadline exceeded (Client.Timeout exceeded while awaiting headers)")),
)
require.Equal(t, `{"template":"misconfiguration/tcpconfig.yaml","type":"http","input":"https://example.com/tcpconfig.html","address":"example.com:443","error":"cause=\"context deadline exceeded (Client.Timeout exceeded while awaiting headers)\"","kind":"unknown-error"}`, errorWriter.String())
})
}
type testWriteCloser struct {
strings.Builder
}
func (w testWriteCloser) Close() error {
return nil
}
================================================
FILE: pkg/output/standard_writer.go
================================================
package output
import (
"io"
"os"
"sync"
"github.com/logrusorgru/aurora"
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
fileutil "github.com/projectdiscovery/utils/file"
)
// WriterOptions contains configuration options for a writer
type WriterOptions func(s *StandardWriter) error
// WithJson writes output in json format
func WithJson(json bool, dumpReqResp bool) WriterOptions {
return func(s *StandardWriter) error {
s.json = json
s.jsonReqResp = dumpReqResp
return nil
}
}
// WithTimestamp writes output with timestamp
func WithTimestamp(timestamp bool) WriterOptions {
return func(s *StandardWriter) error {
s.timestamp = timestamp
return nil
}
}
// WithNoMetadata disables metadata output
func WithNoMetadata(noMetadata bool) WriterOptions {
return func(s *StandardWriter) error {
s.noMetadata = noMetadata
return nil
}
}
// WithMatcherStatus writes output with matcher status
func WithMatcherStatus(matcherStatus bool) WriterOptions {
return func(s *StandardWriter) error {
s.matcherStatus = matcherStatus
return nil
}
}
// WithAurora sets the aurora instance for the writer
func WithAurora(aurora aurora.Aurora) WriterOptions {
return func(s *StandardWriter) error {
s.aurora = aurora
return nil
}
}
// WithWriter sets the writer for the writer
func WithWriter(outputFile io.WriteCloser) WriterOptions {
return func(s *StandardWriter) error {
s.outputFile = outputFile
return nil
}
}
// WithTraceSink sets the writer where trace output is written
func WithTraceSink(traceFile io.WriteCloser) WriterOptions {
return func(s *StandardWriter) error {
s.traceFile = traceFile
return nil
}
}
// WithErrorSink sets the writer where error output is written
func WithErrorSink(errorFile io.WriteCloser) WriterOptions {
return func(s *StandardWriter) error {
s.errorFile = errorFile
return nil
}
}
// WithSeverityColors sets the color function for severity
func WithSeverityColors(severityColors func(severity.Severity) string) WriterOptions {
return func(s *StandardWriter) error {
s.severityColors = severityColors
return nil
}
}
// WithStoreResponse sets the store response option
func WithStoreResponse(storeResponse bool, respDir string) WriterOptions {
return func(s *StandardWriter) error {
s.storeResponse = storeResponse
s.storeResponseDir = respDir
return nil
}
}
// NewWriter creates a new output writer
// if no writer is specified it writes to stdout
func NewWriter(opts ...WriterOptions) (*StandardWriter, error) {
s := &StandardWriter{
mutex: &sync.Mutex{},
DisableStdout: true,
AddNewLinesOutputFile: true,
}
for _, opt := range opts {
if err := opt(s); err != nil {
return nil, err
}
}
if s.aurora == nil {
s.aurora = aurora.NewAurora(false)
}
if s.outputFile == nil {
s.outputFile = os.Stdout
}
// Try to create output folder if it doesn't exist
if s.storeResponse && !fileutil.FolderExists(s.storeResponseDir) {
if err := fileutil.CreateFolder(s.storeResponseDir); err != nil {
return nil, err
}
}
return s, nil
}
================================================
FILE: pkg/output/stats/stats.go
================================================
// Package stats provides a stats tracker for tracking Status Codes,
// Errors & WAF detection events.
//
// It is wrapped and called by output.Writer interface.
package stats
import (
_ "embed"
"fmt"
"sort"
"strconv"
"sync/atomic"
"github.com/logrusorgru/aurora"
"github.com/projectdiscovery/nuclei/v3/pkg/output/stats/waf"
mapsutil "github.com/projectdiscovery/utils/maps"
)
// Tracker is a stats tracker instance for nuclei scans
type Tracker struct {
// counters for various stats
statusCodes *mapsutil.SyncLockMap[string, *atomic.Int32]
errorCodes *mapsutil.SyncLockMap[string, *atomic.Int32]
wafDetected *mapsutil.SyncLockMap[string, *atomic.Int32]
// internal stuff
wafDetector *waf.WafDetector
}
// NewTracker creates a new Tracker instance.
func NewTracker() *Tracker {
return &Tracker{
statusCodes: mapsutil.NewSyncLockMap[string, *atomic.Int32](),
errorCodes: mapsutil.NewSyncLockMap[string, *atomic.Int32](),
wafDetected: mapsutil.NewSyncLockMap[string, *atomic.Int32](),
wafDetector: waf.NewWafDetector(),
}
}
// TrackStatusCode tracks the status code of a request
func (t *Tracker) TrackStatusCode(statusCode string) {
t.incrementCounter(t.statusCodes, statusCode)
}
// TrackErrorKind tracks the error kind of a request
func (t *Tracker) TrackErrorKind(errKind string) {
t.incrementCounter(t.errorCodes, errKind)
}
// TrackWAFDetected tracks the waf detected of a request
//
// First it detects if a waf is running and if so, it increments
// the counter for the waf.
func (t *Tracker) TrackWAFDetected(httpResponse string) {
waf, ok := t.wafDetector.DetectWAF(httpResponse)
if !ok {
return
}
t.incrementCounter(t.wafDetected, waf)
}
func (t *Tracker) incrementCounter(m *mapsutil.SyncLockMap[string, *atomic.Int32], key string) {
if counter, ok := m.Get(key); ok {
counter.Add(1)
} else {
newCounter := new(atomic.Int32)
newCounter.Store(1)
_ = m.Set(key, newCounter)
}
}
type StatsOutput struct {
StatusCodeStats map[string]int `json:"status_code_stats"`
ErrorStats map[string]int `json:"error_stats"`
WAFStats map[string]int `json:"waf_stats"`
}
func (t *Tracker) GetStats() *StatsOutput {
stats := &StatsOutput{
StatusCodeStats: make(map[string]int),
ErrorStats: make(map[string]int),
WAFStats: make(map[string]int),
}
_ = t.errorCodes.Iterate(func(k string, v *atomic.Int32) error {
stats.ErrorStats[k] = int(v.Load())
return nil
})
_ = t.statusCodes.Iterate(func(k string, v *atomic.Int32) error {
stats.StatusCodeStats[k] = int(v.Load())
return nil
})
_ = t.wafDetected.Iterate(func(k string, v *atomic.Int32) error {
waf, ok := t.wafDetector.GetWAF(k)
if !ok {
return nil
}
stats.WAFStats[waf.Name] = int(v.Load())
return nil
})
return stats
}
// DisplayTopStats prints the most relevant statistics for CLI
func (t *Tracker) DisplayTopStats(noColor bool) {
stats := t.GetStats()
if len(stats.StatusCodeStats) > 0 {
fmt.Printf("\n%s\n", aurora.Bold(aurora.Blue("Top Status Codes:")))
topStatusCodes := getTopN(stats.StatusCodeStats, 6)
for _, item := range topStatusCodes {
if noColor {
fmt.Printf(" %s: %d\n", item.Key, item.Value)
} else {
color := getStatusCodeColor(item.Key)
fmt.Printf(" %s: %d\n", aurora.Colorize(item.Key, color), item.Value)
}
}
}
if len(stats.ErrorStats) > 0 {
fmt.Printf("\n%s\n", aurora.Bold(aurora.Red("Top Errors:")))
topErrors := getTopN(stats.ErrorStats, 5)
for _, item := range topErrors {
if noColor {
fmt.Printf(" %s: %d\n", item.Key, item.Value)
} else {
fmt.Printf(" %s: %d\n", aurora.Red(item.Key), item.Value)
}
}
}
if len(stats.WAFStats) > 0 {
fmt.Printf("\n%s\n", aurora.Bold(aurora.Yellow("WAF Detections:")))
for name, count := range stats.WAFStats {
if noColor {
fmt.Printf(" %s: %d\n", name, count)
} else {
fmt.Printf(" %s: %d\n", aurora.Yellow(name), count)
}
}
}
}
// Helper struct for sorting
type kv struct {
Key string
Value int
}
// getTopN returns top N items from a map, sorted by value
func getTopN(m map[string]int, n int) []kv {
var items []kv
for k, v := range m {
items = append(items, kv{k, v})
}
sort.Slice(items, func(i, j int) bool {
return items[i].Value > items[j].Value
})
if len(items) > n {
items = items[:n]
}
return items
}
// getStatusCodeColor returns appropriate color for status code
func getStatusCodeColor(statusCode string) aurora.Color {
code, _ := strconv.Atoi(statusCode)
switch {
case code >= 200 && code < 300:
return aurora.GreenFg
case code >= 300 && code < 400:
return aurora.BlueFg
case code >= 400 && code < 500:
return aurora.YellowFg
case code >= 500:
return aurora.RedFg
default:
return aurora.WhiteFg
}
}
================================================
FILE: pkg/output/stats/stats_test.go
================================================
package stats
import (
"testing"
)
func TestTrackErrorKind(t *testing.T) {
tracker := NewTracker()
// Test single increment
tracker.TrackErrorKind("timeout")
if count, _ := tracker.errorCodes.Get("timeout"); count == nil || count.Load() != 1 {
t.Errorf("expected error kind timeout count to be 1, got %v", count)
}
// Test multiple increments
tracker.TrackErrorKind("timeout")
if count, _ := tracker.errorCodes.Get("timeout"); count == nil || count.Load() != 2 {
t.Errorf("expected error kind timeout count to be 2, got %v", count)
}
// Test different error kind
tracker.TrackErrorKind("connection-refused")
if count, _ := tracker.errorCodes.Get("connection-refused"); count == nil || count.Load() != 1 {
t.Errorf("expected error kind connection-refused count to be 1, got %v", count)
}
}
func TestTrackWaf_Detect(t *testing.T) {
tracker := NewTracker()
tracker.TrackWAFDetected("Attention Required! | Cloudflare")
if count, _ := tracker.wafDetected.Get("cloudflare"); count == nil || count.Load() != 1 {
t.Errorf("expected waf detected count to be 1, got %v", count)
}
}
================================================
FILE: pkg/output/stats/waf/regexes.json
================================================
{
"__copyright__": "Copyright (c) 2019-2021 Miroslav Stampar (@stamparm), MIT. See the file 'LICENSE' for copying permission",
"__notice__": "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software",
"__url__": "Taken from: https://raw.githubusercontent.com/stamparm/identYwaf/refs/heads/master/data.json",
"wafs": {
"360": {
"company": "360",
"name": "360",
"regex": "493|/wzws-waf-cgi/",
"signatures": [
"9778:RVZXum61OEhCWapBYKcPk4JzWOpohM4JiUcMr2RXg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4VmkwI3FZjxtDtAeq+c36A5chW1XaTC",
"9ccc:RVZXum61OEhCWapBYKcPk4JzWOpohM4JiUcMr2RXg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4VmkwI3FZjxtDtAeq+c36A4chW1XaTC"
]
},
"aesecure": {
"company": "aeSecure",
"name": "aeSecure",
"regex": "aesecure_denied\\.png|aesecure-code: \\d+",
"signatures": [
"8a4b:RVdXu260OEhCWapBYKcPk4JzWOtohM4JiUcMrmRXg1uQJbX3uhdOn9htOj+hX7AB16FcPxJOdLsXo2tKaK99n+i7c4RmkgI2FZnxtDtBeq+c36A4chW1XaTD"
]
},
"airlock": {
"company": "Phion/Ergon",
"name": "Airlock",
"regex": "The server detected a syntax error in your request",
"signatures": [
"3e2c:RVZXu261OEhCWapBYKcPk4JzWOtohM4IiUcMr2RXg1uQJbX3uhdOn9htOj+hX7AB16FcPxJPdLsXomtKaK59n+i6c4RmkwI2FZjxtDtAeq6c36A5chW1XaTD"
]
},
"alertlogic": {
"company": "Alert Logic",
"name": "Alert Logic",
"regex": "(?s)timed_redirect\\(seconds, url\\).+?
Reference ID:",
"signatures": []
},
"aliyundun": {
"company": "Alibaba Cloud Computing",
"name": "AliYunDun",
"regex": "Sorry, your request has been blocked as it may cause potential threats to the server's security|//errors\\.aliyun\\.com/",
"signatures": [
"e082:RVZXum61OElCWapAYKYPkoJzWOpohM4JiUYMr2RXg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTC"
]
},
"anquanbao": {
"company": "Anquanbao",
"name": "Anquanbao",
"regex": "/aqb_cc/error/",
"signatures": [
"c790:RVZXum61OElCWapAYKYPk4JzWOpohM4JiUYMr2RXg1uQJbX3uhdOn9hsOj+hXrAB16FcPxJPdLsXo2tLaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC",
"d3d3:RVZXum61OElCWapAYKYPk4JzWOpohM4JiUYMr2RXg1uQJbX3uhdOn9hsOj+hXrAB16FcPxJPdLsXo2tLaK99n+i7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTC"
]
},
"approach": {
"company": "Approach",
"name": "Approach",
"regex": "Approach.+?Web Application (Firewall|Filtering)",
"signatures": [
"fef0:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJLX2uhZOnthsOj6hXrAA16BcPhJOdLoXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c36A5chW1XKTD"
]
},
"armor": {
"company": "Armor Defense",
"name": "Armor Protection",
"regex": "This request has been blocked by website protection from Armor",
"signatures": [
"03ec:RVZXum60OEhCWapBYKYPk4JzWOtohM4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq6c36A4chS1XaTC",
"1160:RVZXum60OEhCWapBYKYPk4JyWOtohM4IiUcMr2RWg1qQJbX3uhZOnthsOj6hXrAA16BcPhJOdLoXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC"
],
"note": "Uses SecureSphere (Imperva) (Reference: https://www.imperva.com/resources/case_studies/CS_Armor.pdf)"
},
"asm": {
"company": "F5 Networks",
"name": "Application Security Manager",
"regex": "The requested URL was rejected\\. Please consult with your administrator|security\\.f5aas\\.com",
"signatures": [
"2f81:RVZXum60OEhCWapBYKcPk4JzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hXrAB16FcPxJPdLsXo2tLaK99n+i7c4RmkgI3FZjxtDtAeq+c36A4chS1XaTC",
"4fd0:RVZXum60OEhCWapBYKcPk4JzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkwI3FZjxtDtAeq6c3qA4chS1XaTC",
"5904:RVZXum60OEhCWapBYKcPk4JzWOpohc4IiUcMr2RWg1uQJbX3uhdOnthtOj+hXrAB16FcPxJPdLsXo2tLaK99n+i7c4RmkwI3FZjxtTtAeq+c3qA4chS1XaTC",
"8bcf:RVZXum60OEhCWapBYKcPk4JzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkwI3FZjxtTtAeq6c36A5chS1XaTC",
"540f:RVZXum60OEhCWapBYKcPk4JzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkwI3FZjxtTtAeq+c36A5chS1XaTC",
"c7ba:RVZXum60OEhCWKpAYKYPkoJzWOpohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXomtLaK99n+i7c4VmkwI3FZjxtDtAeq6c3qA4chS1XaTC",
"fb21:RVZXum60OEhCWapBYKcPk4JzWOpohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkgI3FZjxtDtAeq+c36A5chW1XaTC",
"b6ff:RVZXum61OEhCWapBYKcPkoJzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkwI3FZjxtDtAeq+c36A4chW1XaTC",
"3b1e:RVZXum60OEhCWapBYKcPk4JyWOpohM4IiUcMr2RWg1qQJLX3uhdOnthtOj+hXrAB16FcPxJPdLsXo2tKaK99nui7c4RmkgI2FZjxtDtAeq6c3qA5chS1XKTC",
"620c:RVZXum60OEhCWapBYKcPkoJzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkgI2FZjxtDtAeq+c36A5chW1XaTC",
"b9a0:RVZXum60OEhCWapBYKcPk4JzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkwI3FZjxtDtAeq+c3qA4chW1XaTC",
"ccb6:RVdXum61OEhCWapBYKcPk4JzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkwI3FZjxtTtAeq+c36A5chW1XaTC",
"9138:RVZXum60OEhCWapBYKcPk4JzWOpohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkwI3FZjxtDtAeq6c3qA4chS1XaTC",
"54cc:RVZXum61OEhCWapBYKcPkoJzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkwI3FZjxtDtAeq6c3qA4chS1XaTC",
"4c83:RVZXum60OEhCWapBYKcPk4JzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4VmkwI3FZjxtDtAeq+c36A5chW1XaTC",
"8453:RVZXum60OEhCWapBYKcPk4JzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkwI3FZjxtDtAeq+c36A4chS1XaTC"
]
},
"astra": {
"company": "Czar Securities",
"name": "Astra",
"regex": "(?s)unfortunately our website protection system.+?//www\\.getastra\\.com",
"signatures": []
},
"aws": {
"company": "Amazon",
"name": "AWS WAF",
"regex": "(?i)HTTP/1.+\\b403\\b.+\\s+Server: aws|(?s)Request blocked.+?Generated by cloudfront",
"signatures": [
"2998:RVZXu261OEhCWapBYKcPk4JzWOpohM4IiUcMr2RWg1uQJbX3uhZOnthsOj6hXrAA16BcPhJOdLoXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC",
"fffa:RVZXum60OEhCWapAYKYPk4JyWOpohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPhJPdLsXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC",
"9de0:RVZXu261OEhCWapBYKcPk4JzWOpohM4IiUcMr2RWg1uQJbX3uhZOnthtOj+hXrAA16BcPhJOdLoXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC",
"34a8:RVZXu261OEhCWapBYKcPk4JzWOpohM4IiUcMr2RWg1uQJbX3uhdOn9htOj+hXrAB16BcPxJOdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC",
"1104:RVZXum61OEhCWapBYKcPk4JzWOpohM4IiUcMr2RXg1uQJbX3uhZOnthsOj6hXrAA16BcPhJOdLoXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC",
"ea40:RVZXu261OEhCWapBYKcPk4JzWOtohM4IiUcMr2RWg1uQJbX3uhdOn9htOj+hXrAB16BcPxJOdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC"
]
},
"barracuda": {
"company": "Barracuda Networks",
"name": "Barracuda",
"regex": "\\bbarracuda_|barra_counter_session=|when this page occurred and the event ID found at the bottom of the page|