Repository: projectdiscovery/nuclei Branch: dev Commit: 0ae6d9040ce0 Files: 1011 Total size: 4.2 MB Directory structure: gitextract_eat1zd1s/ ├── .github/ │ ├── DISCUSSION_TEMPLATE.md │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.yml │ │ ├── config.yml │ │ └── reference-templates/ │ │ ├── README.md │ │ └── feature-request-reference.yml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── auto_assign.yml │ ├── dependabot.yml │ ├── release.yml │ └── workflows/ │ ├── auto-merge.yaml │ ├── compat-checks.yaml │ ├── flamegraph.yaml │ ├── generate-docs.yaml │ ├── generate-pgo.yaml │ ├── govulncheck.yaml │ ├── memogen.yaml │ ├── perf-regression.yaml │ ├── release.yaml │ ├── stale.yaml │ ├── tests.yaml │ └── typos.yaml ├── .goreleaser.yml ├── .run/ │ ├── DSLFunctionsIT.run.xml │ ├── IntegrationTests.run.xml │ ├── RegressionTests.run.xml │ └── UnitTests.run.xml ├── CLAUDE.md ├── CONTRIBUTING.md ├── DEBUG.md ├── DESIGN.md ├── Dockerfile ├── Dockerfile.goreleaser ├── LICENSE.md ├── Makefile ├── README.md ├── README_CN.md ├── README_ES.md ├── README_ID.md ├── README_JP.md ├── README_KR.md ├── README_PT-BR.md ├── README_TR.md ├── SYNTAX-REFERENCE.md ├── THANKS.md ├── _typos.toml ├── cmd/ │ ├── docgen/ │ │ └── docgen.go │ ├── functional-test/ │ │ ├── main.go │ │ ├── run.sh │ │ ├── targets-1000.txt │ │ ├── targets-150.txt │ │ ├── targets-250.txt │ │ ├── targets.txt │ │ └── testcases.txt │ ├── generate-checksum/ │ │ └── main.go │ ├── integration-test/ │ │ ├── code.go │ │ ├── custom-dir.go │ │ ├── dns.go │ │ ├── dsl.go │ │ ├── exporters.go │ │ ├── file.go │ │ ├── flow.go │ │ ├── fuzz.go │ │ ├── generic.go │ │ ├── headless.go │ │ ├── http.go │ │ ├── integration-test.go │ │ ├── interactsh.go │ │ ├── javascript.go │ │ ├── library.go │ │ ├── loader.go │ │ ├── matcher-status.go │ │ ├── multi.go │ │ ├── network.go │ │ ├── offline-http.go │ │ ├── profile-loader.go │ │ ├── ssl.go │ │ ├── template-dir.go │ │ ├── template-path.go │ │ ├── templates-dir-env.go │ │ ├── websocket.go │ │ ├── whois.go │ │ └── workflow.go │ ├── memogen/ │ │ ├── function.tpl │ │ └── memogen.go │ ├── nuclei/ │ │ ├── issue-tracker-config.yaml │ │ ├── main.go │ │ ├── main_benchmark_test.go │ │ └── testdata/ │ │ └── benchmark/ │ │ └── multiproto/ │ │ ├── basic-template-multiproto-mixed.yaml │ │ ├── basic-template-multiproto-raw.yaml │ │ └── basic-template-multiproto.yaml │ ├── scan-charts/ │ │ └── main.go │ ├── tmc/ │ │ ├── main.go │ │ └── types.go │ └── tools/ │ ├── fuzzplayground/ │ │ └── main.go │ └── signer/ │ └── main.go ├── examples/ │ ├── advanced/ │ │ └── advanced.go │ ├── simple/ │ │ └── simple.go │ └── with_speed_control/ │ └── main.go ├── gh_retry.sh ├── go.mod ├── go.sum ├── helm/ │ ├── Chart.yaml │ ├── templates/ │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ ├── hpa.yaml │ │ ├── interactsh-deployment.yaml │ │ ├── interactsh-ingress.yaml │ │ ├── interactsh-service.yaml │ │ ├── nuclei-configmap.yaml │ │ ├── nuclei-cron.yaml │ │ └── serviceaccount.yaml │ └── values.yaml ├── integration_tests/ │ ├── debug.sh │ ├── dsl/ │ │ ├── hide-version-warning.yaml │ │ └── show-version-warning.yaml │ ├── flow/ │ │ ├── conditional-flow-negative.yaml │ │ ├── conditional-flow.yaml │ │ ├── dns-ns-probe.yaml │ │ ├── flow-hide-matcher.yaml │ │ ├── iterate-one-value-flow.yaml │ │ └── iterate-values-flow.yaml │ ├── fuzz/ │ │ ├── fuzz-body-generic-sqli.yaml │ │ ├── fuzz-body-json-sqli.yaml │ │ ├── fuzz-body-multipart-form-sqli.yaml │ │ ├── fuzz-body-params-sqli.yaml │ │ ├── fuzz-body-xml-sqli.yaml │ │ ├── fuzz-body.yaml │ │ ├── fuzz-cookie-error-sqli.yaml │ │ ├── fuzz-headless.yaml │ │ ├── fuzz-host-header-injection.yaml │ │ ├── fuzz-mode.yaml │ │ ├── fuzz-multi-mode.yaml │ │ ├── fuzz-path-sqli.yaml │ │ ├── fuzz-query-num-replace.yaml │ │ ├── fuzz-query.yaml │ │ ├── fuzz-type.yaml │ │ └── testData/ │ │ └── ginandjuice.proxify.yaml │ ├── generic/ │ │ └── auth/ │ │ └── certificate/ │ │ ├── assets/ │ │ │ ├── client.crt │ │ │ ├── client.key │ │ │ └── server.crt │ │ └── http-get.yaml │ ├── library/ │ │ ├── test.json │ │ └── test.yaml │ ├── loader/ │ │ ├── basic.yaml │ │ ├── condition-matched.yaml │ │ ├── excluded-template.yaml │ │ ├── get-headers.yaml │ │ ├── get.yaml │ │ ├── template-list.yaml │ │ └── workflow-list.yaml │ ├── profile-loader/ │ │ └── basic.yml │ ├── protocols/ │ │ ├── code/ │ │ │ ├── pre-condition.yaml │ │ │ ├── ps1-snippet.yaml │ │ │ ├── pwsh-echo.yaml │ │ │ ├── py-env-var.yaml │ │ │ ├── py-file.yaml │ │ │ ├── py-interactsh.yaml │ │ │ ├── py-nosig.yaml │ │ │ ├── py-snippet.yaml │ │ │ ├── py-virtual.yaml │ │ │ ├── pyfile.py │ │ │ ├── sh-virtual.yaml │ │ │ └── unsigned.yaml │ │ ├── dns/ │ │ │ ├── a.yaml │ │ │ ├── aaaa.yaml │ │ │ ├── caa.yaml │ │ │ ├── cname-fingerprint.yaml │ │ │ ├── cname.yaml │ │ │ ├── dsl-matcher-variable.yaml │ │ │ ├── ns.yaml │ │ │ ├── payload.yaml │ │ │ ├── ptr.yaml │ │ │ ├── srv.yaml │ │ │ ├── tlsa.yaml │ │ │ ├── txt.yaml │ │ │ └── variables.yaml │ │ ├── file/ │ │ │ ├── data/ │ │ │ │ ├── test1.txt │ │ │ │ ├── test2.txt │ │ │ │ └── test3.txt │ │ │ ├── extract.yaml │ │ │ ├── matcher-with-and.yaml │ │ │ ├── matcher-with-nested-and.yaml │ │ │ └── matcher-with-or.yaml │ │ ├── headless/ │ │ │ ├── file-upload-negative.yaml │ │ │ ├── file-upload.yaml │ │ │ ├── headless-basic.yaml │ │ │ ├── headless-dsl.yaml │ │ │ ├── headless-extract-values.yaml │ │ │ ├── headless-header-action.yaml │ │ │ ├── headless-header-status-test.yaml │ │ │ ├── headless-local.yaml │ │ │ ├── headless-payloads.yaml │ │ │ ├── headless-self-contained.yaml │ │ │ ├── headless-waitevent.yaml │ │ │ └── variables.yaml │ │ ├── http/ │ │ │ ├── annotation-timeout.yaml │ │ │ ├── cl-body-with-header.yaml │ │ │ ├── cl-body-without-header.yaml │ │ │ ├── cli-with-constants.yaml │ │ │ ├── constants-with-threads.yaml │ │ │ ├── custom-attack-type.yaml │ │ │ ├── default-matcher-condition.yaml │ │ │ ├── disable-path-automerge.yaml │ │ │ ├── disable-redirects.yaml │ │ │ ├── dsl-functions.yaml │ │ │ ├── dsl-matcher-variable.yaml │ │ │ ├── get-all-ips.yaml │ │ │ ├── get-case-insensitive.yaml │ │ │ ├── get-headers.yaml │ │ │ ├── get-host-redirects.yaml │ │ │ ├── get-override-sni.yaml │ │ │ ├── get-query-string.yaml │ │ │ ├── get-redirects-chain-headers.yaml │ │ │ ├── get-redirects.yaml │ │ │ ├── get-sni-unsafe.yaml │ │ │ ├── get-sni.yaml │ │ │ ├── get-without-scheme.yaml │ │ │ ├── get.yaml │ │ │ ├── http-matcher-extractor-dy-extractor.yaml │ │ │ ├── http-paths.yaml │ │ │ ├── http-preprocessor.yaml │ │ │ ├── interactsh-requests-mc-and.yaml │ │ │ ├── interactsh-stop-at-first-match.yaml │ │ │ ├── interactsh-with-payloads.yaml │ │ │ ├── interactsh.yaml │ │ │ ├── matcher-status-and-cluster.yaml │ │ │ ├── matcher-status-and.yaml │ │ │ ├── matcher-status.yaml │ │ │ ├── multi-http-var-sharing.yaml │ │ │ ├── multi-request.yaml │ │ │ ├── post-body.yaml │ │ │ ├── post-json-body.yaml │ │ │ ├── post-multipart-body.yaml │ │ │ ├── race-condition-with-delay.yaml │ │ │ ├── race-multiple.yaml │ │ │ ├── race-simple.yaml │ │ │ ├── race-with-variables.yaml │ │ │ ├── raw-cookie-reuse.yaml │ │ │ ├── raw-dynamic-extractor.yaml │ │ │ ├── raw-get-query.yaml │ │ │ ├── raw-get.yaml │ │ │ ├── raw-path-single-slash.yaml │ │ │ ├── raw-path-trailing-slash.yaml │ │ │ ├── raw-payload.yaml │ │ │ ├── raw-post-body.yaml │ │ │ ├── raw-unsafe-path-single-slash.yaml │ │ │ ├── raw-unsafe-path.yaml │ │ │ ├── raw-unsafe-request.yaml │ │ │ ├── raw-unsafe-with-params.yaml │ │ │ ├── raw-with-params.yaml │ │ │ ├── redirect-match-url.yaml │ │ │ ├── request-condition-new.yaml │ │ │ ├── request-condition.yaml │ │ │ ├── response-data-literal-reuse.yaml │ │ │ ├── self-contained-file-input.yaml │ │ │ ├── self-contained-with-params.yaml │ │ │ ├── self-contained-with-path.yaml │ │ │ ├── self-contained.yaml │ │ │ ├── stop-at-first-match-with-extractors.yaml │ │ │ ├── stop-at-first-match.yaml │ │ │ ├── variable-dsl-function.yaml │ │ │ ├── variables-threads-previous.yaml │ │ │ └── variables.yaml │ │ ├── javascript/ │ │ │ ├── multi-ports.yaml │ │ │ ├── mysql-connect.yaml │ │ │ ├── net-https.yaml │ │ │ ├── net-multi-step.yaml │ │ │ ├── no-port-args.yaml │ │ │ ├── postgres-pass-brute.yaml │ │ │ ├── redis-pass-brute.yaml │ │ │ ├── rsync-test.yaml │ │ │ ├── ssh-server-fingerprint.yaml │ │ │ ├── telnet-auth-test.yaml │ │ │ └── vnc-pass-brute.yaml │ │ ├── keys/ │ │ │ ├── README.md │ │ │ ├── ci-private-key.pem │ │ │ └── ci.crt │ │ ├── multi/ │ │ │ ├── dynamic-values.yaml │ │ │ ├── evaluate-variables.yaml │ │ │ └── exported-response-vars.yaml │ │ ├── network/ │ │ │ ├── basic.yaml │ │ │ ├── hex.yaml │ │ │ ├── multi-step.yaml │ │ │ ├── net-https-timeout.yaml │ │ │ ├── net-https.yaml │ │ │ ├── network-port.yaml │ │ │ ├── same-address.yaml │ │ │ ├── self-contained.yaml │ │ │ └── variables.yaml │ │ ├── offlinehttp/ │ │ │ ├── data/ │ │ │ │ └── req-resp-with-http-keywords.txt │ │ │ ├── offline-allowed-paths.yaml │ │ │ ├── offline-raw.yaml │ │ │ └── rfc-req-resp.yaml │ │ ├── ssl/ │ │ │ ├── basic-ztls.yaml │ │ │ ├── basic.yaml │ │ │ ├── custom-cipher.yaml │ │ │ ├── custom-version.yaml │ │ │ ├── multi-req.yaml │ │ │ └── ssl-with-vars.yaml │ │ ├── websocket/ │ │ │ ├── basic.yaml │ │ │ ├── cswsh.yaml │ │ │ ├── no-cswsh.yaml │ │ │ └── path.yaml │ │ └── whois/ │ │ └── basic.yaml │ ├── run.sh │ ├── subdomains.txt │ ├── test-issue-tracker-config1.yaml │ ├── test-issue-tracker-config2.yaml │ └── workflow/ │ ├── basic.yaml │ ├── code-template-1.yaml │ ├── code-template-2.yaml │ ├── code-value-share-workflow.yaml │ ├── complex-conditions.yaml │ ├── condition-matched.yaml │ ├── condition-unmatched.yaml │ ├── dns-value-share-template-1.yaml │ ├── dns-value-share-template-2.yaml │ ├── dns-value-share-template-3.yaml │ ├── dns-value-share-workflow.yaml │ ├── headless-1.yaml │ ├── http-1.yaml │ ├── http-2.yaml │ ├── http-3.yaml │ ├── http-value-share-template-1.yaml │ ├── http-value-share-template-2.yaml │ ├── http-value-share-workflow.yaml │ ├── match-1.yaml │ ├── match-2.yaml │ ├── match-3.yaml │ ├── matcher-name.yaml │ ├── multimatch-value-share-template.yaml │ ├── multimatch-value-share-workflow.yaml │ ├── multiprotocol-value-share-template.yaml │ ├── multiprotocol-value-share-workflow.yaml │ ├── nomatch-1.yaml │ └── shared-cookie.yaml ├── internal/ │ ├── colorizer/ │ │ └── colorizer.go │ ├── httpapi/ │ │ └── apiendpoint.go │ ├── pdcp/ │ │ ├── utils.go │ │ └── writer.go │ ├── runner/ │ │ ├── banner.go │ │ ├── healthcheck.go │ │ ├── inputs.go │ │ ├── lazy.go │ │ ├── options.go │ │ ├── options_test.go │ │ ├── proxy.go │ │ ├── runner.go │ │ ├── runner_test.go │ │ └── templates.go │ └── server/ │ ├── dedupe.go │ ├── nuclei_sdk.go │ ├── requests_worker.go │ ├── scope/ │ │ ├── extensions.go │ │ ├── scope.go │ │ └── scope_test.go │ ├── server.go │ └── templates/ │ └── index.html ├── lib/ │ ├── README.md │ ├── config.go │ ├── example_test.go │ ├── helper.go │ ├── multi.go │ ├── sdk.go │ ├── sdk_private.go │ ├── sdk_test.go │ └── tests/ │ └── sdk_test.go ├── nuclei-jsonschema.json ├── pkg/ │ ├── authprovider/ │ │ ├── authx/ │ │ │ ├── basic_auth.go │ │ │ ├── bearer_auth.go │ │ │ ├── cookies_auth.go │ │ │ ├── dynamic.go │ │ │ ├── dynamic_test.go │ │ │ ├── file.go │ │ │ ├── file_test.go │ │ │ ├── headers_auth.go │ │ │ ├── query_auth.go │ │ │ ├── strategy.go │ │ │ └── testData/ │ │ │ └── example-auth.yaml │ │ ├── file.go │ │ ├── file_test.go │ │ ├── interface.go │ │ └── multi.go │ ├── catalog/ │ │ ├── aws/ │ │ │ ├── catalog.go │ │ │ └── catalog_test.go │ │ ├── catalog.go │ │ ├── config/ │ │ │ ├── constants.go │ │ │ ├── ignorefile.go │ │ │ ├── nucleiconfig.go │ │ │ ├── template.go │ │ │ └── template_test.go │ │ ├── disk/ │ │ │ ├── catalog.go │ │ │ ├── errors.go │ │ │ ├── find.go │ │ │ ├── known-files.go │ │ │ └── path.go │ │ ├── index/ │ │ │ ├── filter.go │ │ │ ├── filter_test.go │ │ │ ├── index.go │ │ │ ├── index_test.go │ │ │ └── metadata.go │ │ └── loader/ │ │ ├── ai_loader.go │ │ ├── filter/ │ │ │ └── path_filter.go │ │ ├── loader.go │ │ ├── loader_bench_test.go │ │ ├── loader_test.go │ │ └── remote_loader.go │ ├── core/ │ │ ├── engine.go │ │ ├── engine_test.go │ │ ├── execute_options.go │ │ ├── executors.go │ │ ├── executors_test.go │ │ ├── workflow_execute.go │ │ ├── workflow_execute_test.go │ │ └── workpool.go │ ├── external/ │ │ └── customtemplates/ │ │ ├── azure_blob.go │ │ ├── github.go │ │ ├── github_test.go │ │ ├── gitlab.go │ │ ├── s3.go │ │ └── templates_provider.go │ ├── fuzz/ │ │ ├── analyzers/ │ │ │ ├── analyzers.go │ │ │ └── time/ │ │ │ ├── analyzer.go │ │ │ ├── time_delay.go │ │ │ └── time_delay_test.go │ │ ├── component/ │ │ │ ├── body.go │ │ │ ├── body_test.go │ │ │ ├── component.go │ │ │ ├── cookie.go │ │ │ ├── cookie_test.go │ │ │ ├── headers.go │ │ │ ├── headers_test.go │ │ │ ├── path.go │ │ │ ├── path_test.go │ │ │ ├── query.go │ │ │ ├── query_test.go │ │ │ ├── value.go │ │ │ └── value_test.go │ │ ├── dataformat/ │ │ │ ├── dataformat.go │ │ │ ├── dataformat_test.go │ │ │ ├── form.go │ │ │ ├── json.go │ │ │ ├── kv.go │ │ │ ├── multipart.go │ │ │ ├── multipart_test.go │ │ │ ├── raw.go │ │ │ └── xml.go │ │ ├── doc.go │ │ ├── execute.go │ │ ├── execute_race_test.go │ │ ├── frequency/ │ │ │ └── tracker.go │ │ ├── fuzz.go │ │ ├── fuzz_test.go │ │ ├── parts.go │ │ ├── parts_frequency_test.go │ │ ├── parts_test.go │ │ ├── stats/ │ │ │ ├── db.go │ │ │ ├── db_test.go │ │ │ ├── simple.go │ │ │ └── stats.go │ │ └── type.go │ ├── input/ │ │ ├── README.md │ │ ├── formats/ │ │ │ ├── README.md │ │ │ ├── burp/ │ │ │ │ ├── burp.go │ │ │ │ └── burp_test.go │ │ │ ├── formats.go │ │ │ ├── json/ │ │ │ │ ├── json.go │ │ │ │ └── json_test.go │ │ │ ├── openapi/ │ │ │ │ ├── downloader.go │ │ │ │ ├── downloader_test.go │ │ │ │ ├── examples.go │ │ │ │ ├── generator.go │ │ │ │ ├── openapi.go │ │ │ │ └── openapi_test.go │ │ │ ├── swagger/ │ │ │ │ ├── downloader.go │ │ │ │ ├── downloader_test.go │ │ │ │ ├── swagger.go │ │ │ │ └── swagger_test.go │ │ │ ├── testdata/ │ │ │ │ ├── burp.xml │ │ │ │ ├── ginandjuice.proxify.json │ │ │ │ ├── ginandjuice.proxify.yaml │ │ │ │ ├── openapi.yaml │ │ │ │ ├── postman.json │ │ │ │ ├── swagger.yaml │ │ │ │ └── ytt/ │ │ │ │ ├── ginandjuice.ytt.yaml │ │ │ │ ├── ytt-profile.yaml │ │ │ │ └── ytt-vars.yaml │ │ │ └── yaml/ │ │ │ ├── multidoc.go │ │ │ ├── multidoc_test.go │ │ │ └── ytt.go │ │ ├── provider/ │ │ │ ├── chunked.go │ │ │ ├── http/ │ │ │ │ └── multiformat.go │ │ │ ├── interface.go │ │ │ ├── list/ │ │ │ │ ├── hmap.go │ │ │ │ ├── hmap_test.go │ │ │ │ ├── tests/ │ │ │ │ │ ├── AS134029.txt │ │ │ │ │ └── AS14421.txt │ │ │ │ └── utils.go │ │ │ └── simple.go │ │ ├── transform.go │ │ ├── transform_test.go │ │ └── types/ │ │ ├── http.go │ │ ├── http_test.go │ │ └── probe.go │ ├── installer/ │ │ ├── doc.go │ │ ├── template.go │ │ ├── template_test.go │ │ ├── util.go │ │ ├── versioncheck.go │ │ ├── versioncheck_test.go │ │ └── zipslip_unix_test.go │ ├── js/ │ │ ├── CONTRIBUTE.md │ │ ├── DESIGN.md │ │ ├── THANKS.md │ │ ├── compiler/ │ │ │ ├── compiler.go │ │ │ ├── compiler_test.go │ │ │ ├── init.go │ │ │ ├── non-pool.go │ │ │ └── pool.go │ │ ├── devtools/ │ │ │ ├── README.md │ │ │ ├── bindgen/ │ │ │ │ ├── INSTALL.md │ │ │ │ ├── README.md │ │ │ │ ├── cmd/ │ │ │ │ │ └── bindgen/ │ │ │ │ │ └── main.go │ │ │ │ ├── generator.go │ │ │ │ ├── output.go │ │ │ │ └── templates/ │ │ │ │ ├── go_class.tmpl │ │ │ │ ├── js_class.tmpl │ │ │ │ └── markdown_class.tmpl │ │ │ ├── scrapefuncs/ │ │ │ │ ├── README.md │ │ │ │ └── main.go │ │ │ └── tsgen/ │ │ │ ├── README.md │ │ │ ├── astutil.go │ │ │ ├── cmd/ │ │ │ │ └── tsgen/ │ │ │ │ ├── main.go │ │ │ │ └── tsmodule.go.tmpl │ │ │ ├── parser.go │ │ │ ├── scrape.go │ │ │ └── types.go │ │ ├── generated/ │ │ │ ├── README.md │ │ │ ├── go/ │ │ │ │ ├── libbytes/ │ │ │ │ │ └── bytes.go │ │ │ │ ├── libfs/ │ │ │ │ │ └── fs.go │ │ │ │ ├── libgoconsole/ │ │ │ │ │ └── goconsole.go │ │ │ │ ├── libikev2/ │ │ │ │ │ └── ikev2.go │ │ │ │ ├── libkerberos/ │ │ │ │ │ └── kerberos.go │ │ │ │ ├── libldap/ │ │ │ │ │ └── ldap.go │ │ │ │ ├── libmssql/ │ │ │ │ │ └── mssql.go │ │ │ │ ├── libmysql/ │ │ │ │ │ └── mysql.go │ │ │ │ ├── libnet/ │ │ │ │ │ └── net.go │ │ │ │ ├── liboracle/ │ │ │ │ │ └── oracle.go │ │ │ │ ├── libpop3/ │ │ │ │ │ └── pop3.go │ │ │ │ ├── libpostgres/ │ │ │ │ │ └── postgres.go │ │ │ │ ├── librdp/ │ │ │ │ │ └── rdp.go │ │ │ │ ├── libredis/ │ │ │ │ │ └── redis.go │ │ │ │ ├── librsync/ │ │ │ │ │ └── rsync.go │ │ │ │ ├── libsmb/ │ │ │ │ │ └── smb.go │ │ │ │ ├── libsmtp/ │ │ │ │ │ └── smtp.go │ │ │ │ ├── libssh/ │ │ │ │ │ └── ssh.go │ │ │ │ ├── libstructs/ │ │ │ │ │ └── structs.go │ │ │ │ ├── libtelnet/ │ │ │ │ │ └── telnet.go │ │ │ │ └── libvnc/ │ │ │ │ └── vnc.go │ │ │ └── ts/ │ │ │ ├── bytes.ts │ │ │ ├── fs.ts │ │ │ ├── goconsole.ts │ │ │ ├── ikev2.ts │ │ │ ├── index.ts │ │ │ ├── kerberos.ts │ │ │ ├── ldap.ts │ │ │ ├── mssql.ts │ │ │ ├── mysql.ts │ │ │ ├── net.ts │ │ │ ├── oracle.ts │ │ │ ├── pop3.ts │ │ │ ├── postgres.ts │ │ │ ├── rdp.ts │ │ │ ├── redis.ts │ │ │ ├── rsync.ts │ │ │ ├── smb.ts │ │ │ ├── smtp.ts │ │ │ ├── ssh.ts │ │ │ ├── structs.ts │ │ │ ├── telnet.ts │ │ │ └── vnc.ts │ │ ├── global/ │ │ │ ├── exports.js │ │ │ ├── helpers.go │ │ │ ├── js/ │ │ │ │ ├── active_directory.js │ │ │ │ └── dump.js │ │ │ ├── scripts.go │ │ │ └── scripts_test.go │ │ ├── gojs/ │ │ │ ├── gojs.go │ │ │ └── set.go │ │ ├── libs/ │ │ │ ├── LICENSE.md │ │ │ ├── bytes/ │ │ │ │ └── buffer.go │ │ │ ├── fs/ │ │ │ │ └── fs.go │ │ │ ├── goconsole/ │ │ │ │ └── log.go │ │ │ ├── ikev2/ │ │ │ │ └── ikev2.go │ │ │ ├── kerberos/ │ │ │ │ ├── kerberosx.go │ │ │ │ └── sendtokdc.go │ │ │ ├── ldap/ │ │ │ │ ├── adenum.go │ │ │ │ ├── ldap.go │ │ │ │ └── utils.go │ │ │ ├── mssql/ │ │ │ │ ├── memo.mssql.go │ │ │ │ └── mssql.go │ │ │ ├── mysql/ │ │ │ │ ├── memo.mysql.go │ │ │ │ ├── memo.mysql_private.go │ │ │ │ ├── mysql.go │ │ │ │ └── mysql_private.go │ │ │ ├── net/ │ │ │ │ └── net.go │ │ │ ├── oracle/ │ │ │ │ ├── memo.oracle.go │ │ │ │ ├── oracle.go │ │ │ │ └── oracledialer.go │ │ │ ├── pop3/ │ │ │ │ ├── memo.pop3.go │ │ │ │ └── pop3.go │ │ │ ├── postgres/ │ │ │ │ ├── memo.postgres.go │ │ │ │ └── postgres.go │ │ │ ├── rdp/ │ │ │ │ ├── memo.rdp.go │ │ │ │ └── rdp.go │ │ │ ├── redis/ │ │ │ │ ├── memo.redis.go │ │ │ │ └── redis.go │ │ │ ├── rsync/ │ │ │ │ ├── memo.rsync.go │ │ │ │ └── rsync.go │ │ │ ├── smb/ │ │ │ │ ├── memo.smb.go │ │ │ │ ├── memo.smb_private.go │ │ │ │ ├── memo.smbghost.go │ │ │ │ ├── smb.go │ │ │ │ ├── smb_private.go │ │ │ │ └── smbghost.go │ │ │ ├── smtp/ │ │ │ │ ├── msg.go │ │ │ │ └── smtp.go │ │ │ ├── ssh/ │ │ │ │ ├── memo.ssh.go │ │ │ │ └── ssh.go │ │ │ ├── structs/ │ │ │ │ ├── smbexploit.js │ │ │ │ └── structs.go │ │ │ ├── telnet/ │ │ │ │ ├── memo.telnet.go │ │ │ │ └── telnet.go │ │ │ └── vnc/ │ │ │ ├── memo.vnc.go │ │ │ └── vnc.go │ │ └── utils/ │ │ ├── nucleijs.go │ │ ├── pgwrap/ │ │ │ └── pgwrap.go │ │ └── util.go │ ├── keys/ │ │ ├── key.go │ │ └── nuclei.crt │ ├── loader/ │ │ ├── parser/ │ │ │ └── parser.go │ │ └── workflow/ │ │ └── workflow_loader.go │ ├── model/ │ │ ├── model.go │ │ ├── model_test.go │ │ ├── types/ │ │ │ ├── severity/ │ │ │ │ ├── severities.go │ │ │ │ ├── severity.go │ │ │ │ └── severity_test.go │ │ │ ├── stringslice/ │ │ │ │ ├── stringslice.go │ │ │ │ └── stringslice_raw.go │ │ │ └── userAgent/ │ │ │ └── user_agent.go │ │ └── workflow_loader.go │ ├── operators/ │ │ ├── cache/ │ │ │ ├── cache.go │ │ │ └── cache_test.go │ │ ├── common/ │ │ │ └── dsl/ │ │ │ ├── dsl.go │ │ │ └── dsl_test.go │ │ ├── extractors/ │ │ │ ├── compile.go │ │ │ ├── doc.go │ │ │ ├── extract.go │ │ │ ├── extract_test.go │ │ │ ├── extractor_types.go │ │ │ ├── extractors.go │ │ │ └── util.go │ │ ├── matchers/ │ │ │ ├── compile.go │ │ │ ├── doc.go │ │ │ ├── match.go │ │ │ ├── match_test.go │ │ │ ├── matchers.go │ │ │ ├── matchers_types.go │ │ │ ├── validate.go │ │ │ └── validate_test.go │ │ ├── operators.go │ │ └── operators_test.go │ ├── output/ │ │ ├── doc.go │ │ ├── file_output_writer.go │ │ ├── format_json.go │ │ ├── format_screen.go │ │ ├── multi_writer.go │ │ ├── output.go │ │ ├── output_stats.go │ │ ├── output_test.go │ │ ├── standard_writer.go │ │ └── stats/ │ │ ├── stats.go │ │ ├── stats_test.go │ │ └── waf/ │ │ ├── regexes.json │ │ ├── waf.go │ │ └── waf_test.go │ ├── progress/ │ │ ├── doc.go │ │ └── progress.go │ ├── projectfile/ │ │ ├── httputil.go │ │ └── project.go │ ├── protocols/ │ │ ├── code/ │ │ │ ├── code.go │ │ │ ├── code_test.go │ │ │ └── helpers.go │ │ ├── common/ │ │ │ ├── automaticscan/ │ │ │ │ ├── automaticscan.go │ │ │ │ ├── automaticscan_test.go │ │ │ │ ├── doc.go │ │ │ │ └── util.go │ │ │ ├── contextargs/ │ │ │ │ ├── contextargs.go │ │ │ │ ├── doc.go │ │ │ │ ├── metainput.go │ │ │ │ └── variables.go │ │ │ ├── expressions/ │ │ │ │ ├── expressions.go │ │ │ │ ├── expressions_test.go │ │ │ │ ├── variables.go │ │ │ │ └── variables_test.go │ │ │ ├── generators/ │ │ │ │ ├── attack_types.go │ │ │ │ ├── attack_types_test.go │ │ │ │ ├── env.go │ │ │ │ ├── env_test.go │ │ │ │ ├── generators.go │ │ │ │ ├── generators_test.go │ │ │ │ ├── load.go │ │ │ │ ├── load_test.go │ │ │ │ ├── maps.go │ │ │ │ ├── maps_bench_test.go │ │ │ │ ├── maps_test.go │ │ │ │ ├── options.go │ │ │ │ ├── options_bench_test.go │ │ │ │ ├── options_test.go │ │ │ │ ├── slice.go │ │ │ │ └── validate.go │ │ │ ├── globalmatchers/ │ │ │ │ └── globalmatchers.go │ │ │ ├── helpers/ │ │ │ │ ├── deserialization/ │ │ │ │ │ ├── deserialization.go │ │ │ │ │ ├── helpers.go │ │ │ │ │ ├── java.go │ │ │ │ │ └── testdata/ │ │ │ │ │ ├── Deserialize.java │ │ │ │ │ ├── README.md │ │ │ │ │ └── ValueObject.java │ │ │ │ ├── eventcreator/ │ │ │ │ │ └── eventcreator.go │ │ │ │ ├── responsehighlighter/ │ │ │ │ │ ├── hexdump.go │ │ │ │ │ ├── response_highlighter.go │ │ │ │ │ └── response_highlighter_test.go │ │ │ │ └── writer/ │ │ │ │ └── writer.go │ │ │ ├── hosterrorscache/ │ │ │ │ ├── hosterrorscache.go │ │ │ │ └── hosterrorscache_test.go │ │ │ ├── interactsh/ │ │ │ │ ├── const.go │ │ │ │ ├── interactsh.go │ │ │ │ └── options.go │ │ │ ├── marker/ │ │ │ │ └── marker.go │ │ │ ├── protocolinit/ │ │ │ │ └── init.go │ │ │ ├── protocolstate/ │ │ │ │ ├── context.go │ │ │ │ ├── dialers.go │ │ │ │ ├── file.go │ │ │ │ ├── headless.go │ │ │ │ ├── js.go │ │ │ │ ├── memguardian.go │ │ │ │ ├── memguardian_test.go │ │ │ │ ├── memoizer.go │ │ │ │ └── state.go │ │ │ ├── randomip/ │ │ │ │ ├── randomip.go │ │ │ │ └── randomip_test.go │ │ │ ├── replacer/ │ │ │ │ ├── replacer.go │ │ │ │ └── replacer_test.go │ │ │ ├── uncover/ │ │ │ │ └── uncover.go │ │ │ ├── utils/ │ │ │ │ ├── excludematchers/ │ │ │ │ │ ├── excludematchers.go │ │ │ │ │ └── excludematchers_test.go │ │ │ │ └── vardump/ │ │ │ │ ├── dump.go │ │ │ │ ├── dump_test.go │ │ │ │ └── vars.go │ │ │ └── variables/ │ │ │ ├── doc.go │ │ │ ├── variables.go │ │ │ ├── variables_bench_test.go │ │ │ └── variables_test.go │ │ ├── dns/ │ │ │ ├── cluster.go │ │ │ ├── dns.go │ │ │ ├── dns_test.go │ │ │ ├── dns_types.go │ │ │ ├── dnsclientpool/ │ │ │ │ └── clientpool.go │ │ │ ├── operators.go │ │ │ ├── operators_test.go │ │ │ ├── request.go │ │ │ └── request_test.go │ │ ├── file/ │ │ │ ├── file.go │ │ │ ├── find.go │ │ │ ├── find_test.go │ │ │ ├── operators.go │ │ │ ├── operators_test.go │ │ │ ├── request.go │ │ │ └── request_test.go │ │ ├── headless/ │ │ │ ├── engine/ │ │ │ │ ├── action.go │ │ │ │ ├── action_types.go │ │ │ │ ├── engine.go │ │ │ │ ├── hijack.go │ │ │ │ ├── http_client.go │ │ │ │ ├── instance.go │ │ │ │ ├── page.go │ │ │ │ ├── page_actions.go │ │ │ │ ├── page_actions_test.go │ │ │ │ ├── rules.go │ │ │ │ └── util.go │ │ │ ├── headless.go │ │ │ ├── operators.go │ │ │ ├── operators_test.go │ │ │ └── request.go │ │ ├── http/ │ │ │ ├── build_request.go │ │ │ ├── build_request_test.go │ │ │ ├── cluster.go │ │ │ ├── cluster_test.go │ │ │ ├── http.go │ │ │ ├── http_method_types.go │ │ │ ├── http_test.go │ │ │ ├── httpclientpool/ │ │ │ │ ├── clientpool.go │ │ │ │ ├── errors.go │ │ │ │ └── options.go │ │ │ ├── httputils/ │ │ │ │ ├── misc.go │ │ │ │ └── spm.go │ │ │ ├── operators.go │ │ │ ├── operators_test.go │ │ │ ├── race/ │ │ │ │ └── syncedreadcloser.go │ │ │ ├── raw/ │ │ │ │ ├── doc.go │ │ │ │ ├── raw.go │ │ │ │ └── raw_test.go │ │ │ ├── request.go │ │ │ ├── request_annotations.go │ │ │ ├── request_annotations_test.go │ │ │ ├── request_condition.go │ │ │ ├── request_fuzz.go │ │ │ ├── request_generator.go │ │ │ ├── request_generator_test.go │ │ │ ├── request_test.go │ │ │ ├── signature.go │ │ │ ├── signer/ │ │ │ │ ├── aws-sign.go │ │ │ │ └── signer.go │ │ │ ├── signerpool/ │ │ │ │ └── signerpool.go │ │ │ ├── utils.go │ │ │ ├── utils_test.go │ │ │ └── validate.go │ │ ├── javascript/ │ │ │ ├── js.go │ │ │ ├── js_test.go │ │ │ └── testcases/ │ │ │ ├── ms-sql-detect.yaml │ │ │ ├── oracle-auth-test.yaml │ │ │ ├── redis-pass-brute.yaml │ │ │ └── ssh-server-fingerprint.yaml │ │ ├── network/ │ │ │ ├── network.go │ │ │ ├── network_input_types.go │ │ │ ├── network_test.go │ │ │ ├── networkclientpool/ │ │ │ │ └── clientpool.go │ │ │ ├── operators.go │ │ │ ├── operators_test.go │ │ │ ├── request.go │ │ │ └── request_test.go │ │ ├── offlinehttp/ │ │ │ ├── find.go │ │ │ ├── find_test.go │ │ │ ├── offlinehttp.go │ │ │ ├── operators.go │ │ │ ├── operators_test.go │ │ │ ├── read_response.go │ │ │ ├── read_response_test.go │ │ │ └── request.go │ │ ├── protocols.go │ │ ├── ssl/ │ │ │ ├── ssl.go │ │ │ └── ssl_test.go │ │ ├── utils/ │ │ │ ├── fields.go │ │ │ ├── fields_test.go │ │ │ ├── http/ │ │ │ │ ├── requtils.go │ │ │ │ └── requtils_test.go │ │ │ ├── utils.go │ │ │ ├── utils_test.go │ │ │ ├── variables.go │ │ │ └── variables_test.go │ │ ├── websocket/ │ │ │ └── websocket.go │ │ └── whois/ │ │ ├── rdapclientpool/ │ │ │ └── clientpool.go │ │ └── whois.go │ ├── reporting/ │ │ ├── client.go │ │ ├── dedupe/ │ │ │ ├── dedupe.go │ │ │ └── dedupe_test.go │ │ ├── exporters/ │ │ │ ├── es/ │ │ │ │ └── elasticsearch.go │ │ │ ├── jsonexporter/ │ │ │ │ └── jsonexporter.go │ │ │ ├── jsonl/ │ │ │ │ └── jsonl.go │ │ │ ├── markdown/ │ │ │ │ ├── markdown.go │ │ │ │ └── util/ │ │ │ │ ├── markdown_formatter.go │ │ │ │ ├── markdown_utils.go │ │ │ │ └── markdown_utils_test.go │ │ │ ├── mongo/ │ │ │ │ └── mongo.go │ │ │ ├── pdf/ │ │ │ │ ├── pdf.go │ │ │ │ └── pdf_test.go │ │ │ ├── sarif/ │ │ │ │ └── sarif.go │ │ │ └── splunk/ │ │ │ └── splunkhec.go │ │ ├── format/ │ │ │ ├── format.go │ │ │ ├── format_utils.go │ │ │ └── format_utils_test.go │ │ ├── options.go │ │ ├── reporting.go │ │ └── trackers/ │ │ ├── filters/ │ │ │ └── filters.go │ │ ├── gitea/ │ │ │ └── gitea.go │ │ ├── github/ │ │ │ └── github.go │ │ ├── gitlab/ │ │ │ └── gitlab.go │ │ ├── jira/ │ │ │ ├── jira.go │ │ │ └── jira_test.go │ │ └── linear/ │ │ ├── jsonutil/ │ │ │ └── jsonutil.go │ │ └── linear.go │ ├── scan/ │ │ ├── charts/ │ │ │ ├── charts.go │ │ │ └── echarts.go │ │ ├── events/ │ │ │ ├── scan_noop.go │ │ │ ├── stats_build.go │ │ │ └── utils.go │ │ └── scan_context.go │ ├── templates/ │ │ ├── cache.go │ │ ├── cache_test.go │ │ ├── cluster.go │ │ ├── cluster_test.go │ │ ├── compile.go │ │ ├── compile_bench_test.go │ │ ├── compile_test.go │ │ ├── doc.go │ │ ├── extensions/ │ │ │ └── extensions.go │ │ ├── log.go │ │ ├── log_test.go │ │ ├── parser.go │ │ ├── parser_config.go │ │ ├── parser_error.go │ │ ├── parser_stats.go │ │ ├── parser_test.go │ │ ├── parser_validate.go │ │ ├── preprocessors.go │ │ ├── signer/ │ │ │ ├── default.go │ │ │ ├── handler.go │ │ │ ├── handler_test.go │ │ │ ├── tmpl_signer.go │ │ │ └── tmpl_signer_test.go │ │ ├── stats.go │ │ ├── tag_filter.go │ │ ├── tag_filter_test.go │ │ ├── template_sign.go │ │ ├── templates.go │ │ ├── templates_doc.go │ │ ├── templates_doc_examples.go │ │ ├── templates_test.go │ │ ├── templates_utils.go │ │ ├── tests/ │ │ │ ├── global-matcher.yaml │ │ │ ├── json-template.json │ │ │ ├── match-1.yaml │ │ │ ├── multiproto.json │ │ │ ├── multiproto.yaml │ │ │ ├── no-author.yaml │ │ │ ├── no-req.yaml │ │ │ ├── workflow-global-matchers.yaml │ │ │ ├── workflow-invalid.yaml │ │ │ └── workflow.yaml │ │ ├── types/ │ │ │ ├── cluster_mappings.go │ │ │ └── types.go │ │ ├── validator_singleton.go │ │ └── workflows.go │ ├── testutils/ │ │ ├── fuzzplayground/ │ │ │ ├── db.go │ │ │ ├── server.go │ │ │ └── sqli_test.go │ │ ├── integration.go │ │ ├── testheadless/ │ │ │ ├── headless_local.go │ │ │ └── headless_runtime.go │ │ └── testutils.go │ ├── tmplexec/ │ │ ├── README.md │ │ ├── doc.go │ │ ├── exec.go │ │ ├── flow/ │ │ │ ├── README.md │ │ │ ├── builtin/ │ │ │ │ └── dedupe.go │ │ │ ├── doc.go │ │ │ ├── flow_executor.go │ │ │ ├── flow_executor_test.go │ │ │ ├── flow_internal.go │ │ │ ├── testcases/ │ │ │ │ ├── condition-flow-extractors.yaml │ │ │ │ ├── condition-flow-no-operators.yaml │ │ │ │ ├── condition-flow.yaml │ │ │ │ ├── nuclei-flow-dns-id.yaml │ │ │ │ ├── nuclei-flow-dns-prefix.yaml │ │ │ │ └── nuclei-flow-dns.yaml │ │ │ ├── util.go │ │ │ ├── util_test.go │ │ │ └── vm.go │ │ ├── generic/ │ │ │ └── exec.go │ │ ├── interface.go │ │ ├── multiproto/ │ │ │ ├── README.md │ │ │ ├── doc.go │ │ │ ├── multi.go │ │ │ ├── multi_test.go │ │ │ └── testcases/ │ │ │ ├── multiprotodynamic.yaml │ │ │ └── multiprotowithprefix.yaml │ │ └── utils/ │ │ └── utils.go │ ├── types/ │ │ ├── interfaces.go │ │ ├── nucleierr/ │ │ │ └── kinds.go │ │ ├── resume.go │ │ ├── scanstrategy/ │ │ │ └── scan_strategy.go │ │ └── types.go │ ├── utils/ │ │ ├── capture_writer.go │ │ ├── expand/ │ │ │ └── expand.go │ │ ├── http_probe.go │ │ ├── http_probe_test.go │ │ ├── index.go │ │ ├── insertion_ordered_map.go │ │ ├── insertion_ordered_map_test.go │ │ ├── json/ │ │ │ ├── doc.go │ │ │ ├── json.go │ │ │ ├── json_fallback.go │ │ │ ├── jsoncodec.go │ │ │ └── message.go │ │ ├── monitor/ │ │ │ ├── monitor.go │ │ │ └── monitor_test.go │ │ ├── stats/ │ │ │ ├── doc.go │ │ │ └── stats.go │ │ ├── telnetmini/ │ │ │ ├── doc.go │ │ │ ├── ntlm.go │ │ │ ├── smb.go │ │ │ └── telnet.go │ │ ├── template_path.go │ │ ├── utils.go │ │ ├── utils_test.go │ │ └── yaml/ │ │ ├── preprocess.go │ │ └── yaml_decode_wrapper.go │ └── workflows/ │ ├── doc.go │ ├── workflows.go │ └── workflows_test.go └── static/ └── regression-cycle.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/DISCUSSION_TEMPLATE.md ================================================ # Nuclei Discussion Guidelines ## Before Creating a Discussion 1. **Search existing discussions and issues** to avoid duplicates 2. **Check the documentation** and README first 3. **Browse the FAQ** and common questions ## Bug Reports in Discussions When reporting a bug in [Q&A Discussions](https://github.com/projectdiscovery/nuclei/discussions/categories/q-a), please include: ### Required Information: - **Clear title** with `[BUG]` prefix (e.g., "[BUG] Nuclei crashes when...") - **Current behavior** - What's happening now? - **Expected behavior** - What should happen instead? - **Steps to reproduce** - Commands or actions that trigger the issue - **Environment details**: - OS and version - Nuclei version (`nuclei -version`) - Go version (if installed via `go install`) - **Log output** - Run with `-verbose` or `-debug` for detailed logs - **Redact sensitive information** - Remove target URLs, credentials, etc. ### After Discussion: - Maintainers will review and validate the bug report - Valid bugs will be converted to issues with proper labels and tracking - Questions and misconfigurations will be resolved in the discussion ## Feature Requests in Discussions When requesting a feature in [Ideas Discussions](https://github.com/projectdiscovery/nuclei/discussions/categories/ideas), please include: ### Required Information: - **Clear title** with `[FEATURE]` prefix (e.g., "[FEATURE] Add support for...") - **Feature description** - What do you want to be added? - **Use case** - Why is this feature needed? What problem does it solve? - **Implementation ideas** - If you have suggestions on how it could work - **Alternatives considered** - What other solutions have you thought about? ### After Discussion: - Community and maintainers will discuss the feasibility - Popular and viable features will be converted to issues - Similar features may be grouped together - Rejected features will be explained in the discussion ## Getting Help For general questions, troubleshooting, and "how-to" topics: - Use [Q&A Discussions](https://github.com/projectdiscovery/nuclei/discussions/categories/q-a) - Join the [Discord server](https://discord.gg/projectdiscovery) #nuclei channel - Check existing discussions for similar questions ## Discussion to Issue Conversion Process Only maintainers can convert discussions to issues. The process: 1. **Validation** - Maintainers review the discussion for completeness and validity 2. **Classification** - Determine if it's a bug, feature, enhancement, etc. 3. **Issue creation** - Create a properly formatted issue with appropriate labels 4. **Linking** - Link the issue back to the original discussion 5. **Resolution** - Mark the discussion as resolved or close it This process ensures: - High-quality issues that are actionable - Proper triage and labeling - Reduced noise in the issue tracker - Community involvement in the validation process ## Why This Process? - **Better organization** - Issues contain only validated, actionable items - **Community input** - Discussions allow for community feedback before escalation - **Quality control** - Maintainers ensure proper formatting and information - **Reduced maintenance** - Fewer invalid or duplicate issues to manage - **Clear separation** - Questions vs. actual bugs/features are clearly distinguished ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.yml ================================================ name: Bug Report description: Create a report to help us to improve the Nuclei. title: "[BUG] ..." labels: ["Type: Bug"] body: - type: markdown attributes: value: | Thanks for taking the time to fill out this bug report! For support requests, FAQs or "How to" questions, please use the [GitHub Discussions](https://github.com/projectdiscovery/nuclei/discussions) section instead or join our [Discord server](https://discord.gg/projectdiscovery) to discuss the idea on the **#nuclei** channel. :warning: **Issues missing important information may be closed without further investigation.** - type: checkboxes attributes: label: Is there an existing issue for this? description: Please search to see if an issue already exists for the bug you encountered. options: - label: I have searched the existing issues. required: true - type: textarea attributes: label: Current Behavior description: A concise description of what you're experiencing. validations: required: true - type: textarea attributes: label: Expected Behavior description: A concise description of what you expected to happen. validations: required: true - type: textarea attributes: label: Steps To Reproduce description: | Steps to reproduce the behavior, for example, commands to run Nuclei. 💡 Prefer copying text output over using screenshots for easier troubleshooting. 📝 For a more detailed output that could help in troubleshooting, you may want to run Nuclei with the **`-verbose`** or **`-debug`** flags. This will provide additional insights into what's happening under the hood.
📊 For performance or memory investigations, use **`-profile-mem`**, which generates `*.cpu`, `*.mem`, and `*.trace` files. Since GitHub doesn't support these formats directly, compress them (e.g., .zip or .tar.gz) and attach the archive under the "Anything else" section below. :warning: **Please redact any literal target hosts/URLs or other sensitive information.** placeholder: | 1. Run `nuclei -t ...` validations: required: true - type: textarea attributes: label: Relevant log output description: | Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 💡 Prefer copying text output over using screenshots for easier troubleshooting. 📝 For a more detailed output that could help in troubleshooting, you may want to run Nuclei with the **`-verbose`** or **`-debug`** flags. This will provide additional insights into what's happening under the hood.
📊 For performance or memory investigations, use **`-profile-mem`**, which generates `*.cpu`, `*.mem`, and `*.trace` files. Since GitHub doesn't support these formats directly, compress them (e.g., .zip or .tar.gz) and attach the archive under the "Anything else" section below. :warning: **Please redact any literal target hosts/URLs or other sensitive information.** render: shell - type: textarea attributes: label: Environment description: | Examples: - **OS**: Ubuntu 24.04 - **Nuclei** (`nuclei -version`): v3.6.0 - **Go** (`go version`): go1.24.0 _(only if you've installed it via the `go install` command)_ value: | - OS: - Nuclei: - Go: render: markdown validations: required: true - type: textarea attributes: label: Anything else? description: | Links? References? Templates? CPU, memory, and trace profiles? Anything that will give us more context about the issue you are encountering! Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: 💡 Request a Feature (Start with Discussion) url: https://github.com/orgs/projectdiscovery/discussions/new?category=ideas about: Share your feature idea in discussions first. This helps validate and refine the request before creating an issue. - name: ❓ Ask Questions / Get Help url: https://github.com/orgs/projectdiscovery/discussions about: Get help and ask questions about using Nuclei. Many questions don't require issues. - name: 🔍 Browse Existing Issues url: https://github.com/projectdiscovery/nuclei/issues about: Check existing issues to see if your problem has already been reported or is being worked on. - name: 💬 Connect with PD Team (Discord) url: https://discord.gg/projectdiscovery about: Join our Discord for real-time discussions and community support on the #nuclei channel. ================================================ FILE: .github/ISSUE_TEMPLATE/reference-templates/README.md ================================================ # Issue Template References ## Overview This folder contains the preserved issue templates that are **not** directly accessible to users. These templates serve as references for maintainers when converting discussions to issues. ## New Workflow ### For Users: 1. **All reports start in Discussions** - Users cannot create issues directly 2. Bug reports go to [Q&A Discussions](https://github.com/projectdiscovery/nuclei/discussions/categories/q-a) 3. Feature requests go to [Ideas Discussions](https://github.com/projectdiscovery/nuclei/discussions/categories/ideas) 4. This helps filter out duplicate questions, invalid reports, and ensures proper triage ### For Maintainers: 1. **Review discussions** in both Q&A and Ideas categories 2. **Validate the reports** - ensure they're actual bugs/valid feature requests 3. **Use reference templates** when converting discussions to issues: - Copy content from `bug-report-reference.yml` or `feature-request-reference.yml` - Create a new issue manually with the appropriate template structure - Link back to the original discussion - Close the discussion or mark it as resolved ## Benefits - **Better triage**: Avoid cluttering issues with questions and invalid reports - **Community involvement**: Discussions allow for community input before creating issues - **Quality control**: Maintainers can ensure issues follow proper format and contain necessary information - **Reduced noise**: Only validated, actionable items become issues ## Reference Templates - `bug-report-reference.yml` - Use when converting bug reports from discussions to issues - `feature-request-reference.yml` - Use when converting feature requests from discussions to issues ## Converting a Discussion to Issue 1. Identify a valid discussion that needs to become an issue 2. Go to the main repository's Issues tab 3. Click "New Issue" 4. Manually create the issue using the reference template structure 5. Include all relevant information from the discussion 6. Add a comment linking back to the original discussion 7. Apply appropriate labels 8. Close or mark the discussion as resolved with a link to the created issue ================================================ FILE: .github/ISSUE_TEMPLATE/reference-templates/feature-request-reference.yml ================================================ name: Feature Request description: Request feature to implement in the Nuclei. title: "[FEATURE] ..." labels: ["Type: Enhancement"] body: - type: markdown attributes: value: | Thanks for taking the time to fill out this feature request! Please make sure to provide a detailed description with all the relevant information that might be required to start working on this feature. In case you are not sure about your request or whether the particular feature is already supported or not, please [start a discussion](https://github.com/projectdiscovery/nuclei/discussions/categories/ideas) instead. Join our [Discord server](https://discord.gg/projectdiscovery) to discuss the idea on the **#nuclei** channel. - type: textarea attributes: label: Describe your feature request description: A clear and concise description of feature to implement. validations: required: true - type: textarea attributes: label: Describe the use case of the feature description: A clear and concise description of the feature request's motivation and the use-cases in which it could be useful. validations: required: true - type: textarea attributes: label: Describe alternatives you've considered description: A clear and concise description of any alternative solutions or features you've considered. validations: required: false - type: textarea attributes: label: Additional context description: Add any other context about the feature request here. validations: required: false ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ## Proposed changes ### Proof ## Checklist - [ ] Pull request is created against the [dev](https://github.com/projectdiscovery/nuclei/tree/dev) branch - [ ] All checks passed (lint, unit/integration/regression tests etc.) with my changes - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] I have added necessary documentation (if appropriate) ================================================ FILE: .github/auto_assign.yml ================================================ addReviewers: true reviewers: - dogancanbakir - dwisiswant0 - mzack9999 numberOfReviewers: 1 skipKeywords: - '@dependabot' ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "weekly" target-branch: "dev" commit-message: prefix: "chore" include: "scope" allow: - dependency-name: "github.com/projectdiscovery/*" groups: modules: patterns: ["github.com/projectdiscovery/*"] security: applies-to: "security-updates" patterns: ["*"] exclude-patterns: ["github.com/projectdiscovery/*"] labels: - "Type: Maintenance" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" target-branch: "dev" commit-message: prefix: "chore" include: "scope" groups: workflows: patterns: ["*"] exclude-patterns: ["projectdiscovery/actions/*"] labels: - "Type: Maintenance" # # Maintain dependencies for docker # - package-ecosystem: "docker" # directory: "/" # schedule: # interval: "weekly" # target-branch: "dev" # commit-message: # prefix: "chore" # include: "scope" # labels: # - "Type: Maintenance" ================================================ FILE: .github/release.yml ================================================ changelog: exclude: authors: - dependabot categories: - title: 🎉 New Features labels: - "Type: Enhancement" - title: 🐞 Bug Fixes labels: - "Type: Bug" - title: 🔨 Maintenance labels: - "Type: Maintenance" - title: Other Changes labels: - "*" ================================================ FILE: .github/workflows/auto-merge.yaml ================================================ name: 🔀 Auto merge PR on: # pull_request: # types: [opened, synchronize, reopened, ready_for_review] # pull_request_review: # types: [submitted] workflow_run: workflows: ["♾️ Compatibility Checks"] types: [completed] permissions: contents: write pull-requests: write jobs: auto-merge-dependabot: runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - uses: projectdiscovery/actions/pr/approve@v1 - uses: projectdiscovery/actions/pr/merge@v1 with: auto: "true" ================================================ FILE: .github/workflows/compat-checks.yaml ================================================ name: ♾️ Compatibility Checks on: pull_request: types: [opened, synchronize] branches: - dev jobs: compat-checks: if: ${{ github.actor == 'dependabot[bot]' }} runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v6 - uses: projectdiscovery/actions/setup/go/compat-checks@v1 with: go-version: "stable" release-test: true ================================================ FILE: .github/workflows/flamegraph.yaml ================================================ name: 📊 Flamegraph on: workflow_call: {} jobs: flamegraph: name: "Flamegraph" env: GITHUB_TOKEN: "${{ github.token }}" PROFILE_MEM: "/tmp/nuclei-profile" TARGET_URL: "http://honey.scanme.sh/-/?foo=bar" runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: projectdiscovery/actions/setup/go@v1 - uses: projectdiscovery/actions/cache/go-rod-browser@v1 - uses: projectdiscovery/actions/cache/nuclei@v1 - run: make build - name: "Setup environment (push)" if: ${{ github.event_name == 'push' }} run: | echo "PROFILE_MEM=${PROFILE_MEM}-${GITHUB_REF_NAME}-${GITHUB_SHA}" >> $GITHUB_ENV echo "FLAMEGRAPH_NAME=nuclei-${GITHUB_REF_NAME} (${GITHUB_SHA})" >> $GITHUB_ENV - name: "Setup environment (pull_request)" if: ${{ github.event_name == 'pull_request' }} run: | echo "PROFILE_MEM=${PROFILE_MEM}-pr-${{ github.event.number }}" >> $GITHUB_ENV echo "FLAMEGRAPH_NAME=nuclei (PR #${{ github.event.number }})" >> $GITHUB_ENV - run: ./bin/nuclei -update-templates - run: | ./bin/nuclei -silent -target="${TARGET_URL}" \ -disable-update-check \ -no-mhe -no-httpx \ -code -dast -file -headless \ -enable-self-contained -enable-global-matchers \ -rate-limit=0 \ -concurrency=250 \ -headless-concurrency=100 \ -payload-concurrency=250 \ -bulk-size=250 \ -headless-bulk-size=100 \ -profile-mem="${PROFILE_MEM}" - uses: projectdiscovery/actions/flamegraph@v1 id: flamegraph-cpu with: profile: "${{ env.PROFILE_MEM }}.cpu" name: "${{ env.FLAMEGRAPH_NAME }} CPU profiles" continue-on-error: true - uses: projectdiscovery/actions/flamegraph@v1 id: flamegraph-mem with: profile: "${{ env.PROFILE_MEM }}.mem" name: "${{ env.FLAMEGRAPH_NAME }} memory profiles" continue-on-error: true - if: ${{ steps.flamegraph-mem.outputs.message == '' }} run: | echo "::notice::CPU flamegraph: ${{ steps.flamegraph-cpu.outputs.url }}" echo "::notice::Memory (heap) flamegraph: ${{ steps.flamegraph-mem.outputs.url }}" ================================================ FILE: .github/workflows/generate-docs.yaml ================================================ name: ⏰ Generate Docs on: push: branches: - dev workflow_dispatch: {} jobs: publish-docs: if: ${{ !endsWith(github.actor, '[bot]') }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: projectdiscovery/actions/setup/go@v1 - uses: projectdiscovery/actions/setup/git@v1 - run: make syntax-docs - run: git status -s | wc -l | xargs -I {} echo CHANGES={} >> $GITHUB_OUTPUT id: status - uses: projectdiscovery/actions/commit@v1 if: steps.status.outputs.CHANGES > 0 with: files: | SYNTAX-REFERENCE.md nuclei-jsonschema.json message: 'docs: update syntax & JSON schema 🤖' - run: git push origin $GITHUB_REF ================================================ FILE: .github/workflows/generate-pgo.yaml ================================================ name: 👤 Generate PGO on: workflow_dispatch: {} workflow_call: {} jobs: pgo: runs-on: ubuntu-latest if: github.repository == 'projectdiscovery/nuclei' permissions: contents: write env: GITHUB_TOKEN: "${{ github.token }}" PGO_FILE: "cmd/nuclei/default.pgo" TARGET: "https://honey.scanme.sh" TARGET_COUNT: "100" TARGET_LIST: "/tmp/targets.txt" PROFILE_MEM: "/tmp/nuclei-profile" steps: - uses: actions/checkout@v6 - uses: projectdiscovery/actions/setup/go@v1 - uses: projectdiscovery/actions/cache/go-rod-browser@v1 - uses: projectdiscovery/actions/cache/nuclei@v1 - run: | for i in {1..${{ env.TARGET_COUNT }}}; do echo "${{ env.TARGET }}/-/?_=${i}" >> "${{ env.TARGET_LIST }}"; done - run: make build - run: ./bin/nuclei -update-templates - run: | ./bin/nuclei -silent -list="${TARGET_LIST}" \ -disable-update-check \ -no-mhe -no-httpx \ -code -dast -file -headless \ -enable-self-contained -enable-global-matchers \ -rate-limit=0 \ -concurrency=250 \ -headless-concurrency=100 \ -payload-concurrency=250 \ -bulk-size=250 \ -headless-bulk-size=100 \ -profile-mem="${PROFILE_MEM}" \ env: DISABLE_STDOUT: "1" - run: mv "${PROFILE_MEM}.cpu" "${PGO_FILE}" - uses: actions/upload-artifact@v7 with: name: "pgo" path: "${{ env.PGO_FILE }}" overwrite: true ================================================ FILE: .github/workflows/govulncheck.yaml ================================================ name: 🐛 govulncheck on: schedule: - cron: '0 0 * * 0' # Weekly workflow_dispatch: {} jobs: govulncheck: runs-on: ubuntu-latest if: github.repository == 'projectdiscovery/nuclei' permissions: actions: read contents: read security-events: write env: OUTPUT: "/tmp/results.sarif" steps: - uses: actions/checkout@v6 - uses: projectdiscovery/actions/setup/go@v1 - run: go install golang.org/x/vuln/cmd/govulncheck@latest - run: | govulncheck -scan package -format sarif ./... | \ jq '(.runs[].tool.driver.rules[]?.properties.tags)? |= unique' > $OUTPUT - uses: github/codeql-action/upload-sarif@v4 with: sarif_file: "${{ env.OUTPUT }}" category: "govulncheck" ================================================ FILE: .github/workflows/memogen.yaml ================================================ name: 💾 Memoize Functions on: push: branches: - dev paths: - 'pkg/js/libs/**' - 'cmd/memogen/**' workflow_dispatch: {} jobs: memogen: if: ${{ !endsWith(github.actor, '[bot]') }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: projectdiscovery/actions/setup/go@v1 - uses: projectdiscovery/actions/setup/git@v1 - run: make memogen - run: git status -s | wc -l | xargs -I {} echo CHANGES={} >> $GITHUB_OUTPUT id: status - uses: projectdiscovery/actions/commit@v1 if: steps.status.outputs.CHANGES > 0 with: files: | pkg/js/libs/ message: 'chore(js): update memoized functions 🤖' - run: git push origin $GITHUB_REF if: steps.status.outputs.CHANGES > 0 ================================================ FILE: .github/workflows/perf-regression.yaml ================================================ name: 🔨 Performance Regression on: workflow_call: {} workflow_dispatch: {} jobs: perf-regression: runs-on: ubuntu-latest if: github.repository == 'projectdiscovery/nuclei' env: BENCH_OUT: "/tmp/bench.out" steps: - uses: actions/checkout@v6 - uses: projectdiscovery/actions/setup/go@v1 - uses: projectdiscovery/actions/cache/go-rod-browser@v1 - uses: projectdiscovery/actions/cache/nuclei@v1 - run: make build-test - run: ./bin/nuclei.test -test.run - -test.bench=. -test.benchmem ./cmd/nuclei/ | tee $BENCH_OUT env: DISABLE_STDOUT: "1" - uses: actions/cache/restore@v5 with: path: ./cache key: ${{ runner.os }}-benchmark - uses: benchmark-action/github-action-benchmark@v1 with: name: 'RunEnumeration Benchmark' tool: 'go' output-file-path: ${{ env.BENCH_OUT }} external-data-json-path: ./cache/benchmark-data.json fail-on-alert: false github-token: ${{ secrets.GITHUB_TOKEN }} comment-on-alert: true summary-always: true - uses: actions/cache/save@v5 if: github.event_name == 'push' with: path: ./cache key: ${{ runner.os }}-benchmark ================================================ FILE: .github/workflows/release.yaml ================================================ name: 🎉 Release on: push: tags: - '*' workflow_dispatch: {} jobs: pgo: name: "Generate PGO" uses: ./.github/workflows/generate-pgo.yaml release: name: "Release" needs: ["pgo"] runs-on: ubuntu-latest-16-cores steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - uses: actions/download-artifact@v8 with: name: "pgo" path: "cmd/nuclei/" - uses: projectdiscovery/actions/setup/go@v1 with: go-version: "stable" - uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} - uses: projectdiscovery/actions/goreleaser@v1 with: release: true env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" SLACK_WEBHOOK: "${{ secrets.RELEASE_SLACK_WEBHOOK }}" DISCORD_WEBHOOK_ID: "${{ secrets.DISCORD_WEBHOOK_ID }}" DISCORD_WEBHOOK_TOKEN: "${{ secrets.DISCORD_WEBHOOK_TOKEN }}" ================================================ FILE: .github/workflows/stale.yaml ================================================ name: 💤 Stale on: schedule: - cron: '0 0 * * 0' # Weekly jobs: stale: runs-on: ubuntu-latest permissions: actions: write contents: write # only for delete-branch option issues: write pull-requests: write steps: - uses: actions/stale@v10 with: days-before-stale: 90 days-before-close: 7 stale-issue-label: "Status: Stale" stale-pr-label: "Status: Stale" stale-issue-message: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions! stale-pr-message: > This pull request has been automatically marked as stale due to inactivity. It will be closed in 7 days if no further activity occurs. Please update if you wish to keep it open. close-issue-message: > This issue has been automatically closed due to inactivity. If you think this is a mistake or would like to continue the discussion, please comment or feel free to reopen it. close-pr-message: > This pull request has been automatically closed due to inactivity. If you think this is a mistake or would like to continue working on it, please comment or feel free to reopen it. close-issue-label: "Status: Abandoned" close-pr-label: "Status: Abandoned" exempt-issue-labels: "Type: Enhancement" ================================================ FILE: .github/workflows/tests.yaml ================================================ name: 🔨 Tests on: push: branches: ["dev"] paths: - '**.go' - '**.mod' pull_request: paths: - '**.go' - '**.mod' workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: lint: name: "Lint" if: ${{ !endsWith(github.actor, '[bot]') }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: projectdiscovery/actions/setup/go@v1 - uses: projectdiscovery/actions/cache/go-rod-browser@v1 - uses: projectdiscovery/actions/golangci-lint/v2@v1 tests: name: "Tests" needs: ["lint"] env: GITHUB_TOKEN: "${{ github.token }}" strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macOS-latest] runs-on: "${{ matrix.os }}" steps: - uses: actions/checkout@v6 - uses: projectdiscovery/actions/setup/go@v1 - uses: projectdiscovery/actions/cache/go-rod-browser@v1 - uses: projectdiscovery/actions/cache/nuclei@v1 - uses: projectdiscovery/actions/free-disk-space@v1 with: llvm: 'false' php: 'false' mongodb: 'false' mysql: 'false' misc-packages: 'false' docker-images: 'false' tools-cache: 'false' - run: make vet - run: make build - run: make test env: PDCP_API_KEY: "${{ secrets.PDCP_API_KEY }}" - run: go run -race . -l ../functional-test/targets.txt -id tech-detect,tls-version if: ${{ matrix.os != 'windows-latest' }} # known issue: https://github.com/golang/go/issues/46099 working-directory: cmd/nuclei/ sdk: name: "Run example SDK" needs: ["tests"] runs-on: ubuntu-latest env: GITHUB_TOKEN: "${{ github.token }}" steps: - uses: actions/checkout@v6 - uses: projectdiscovery/actions/setup/go@v1 - uses: projectdiscovery/actions/cache/go-rod-browser@v1 - uses: projectdiscovery/actions/cache/nuclei@v1 - name: "Simple" run: go run . working-directory: examples/simple/ # - run: go run . # Temporarily disabled very flaky in github actions # working-directory: examples/advanced/ # TODO: FIX with ExecutionID (ref: https://github.com/projectdiscovery/nuclei/pull/6296) # - name: "with Speed Control" # run: go run . # working-directory: examples/with_speed_control/ integration: name: "Integration tests" needs: ["tests"] env: GITHUB_TOKEN: "${{ github.token }}" strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macOS-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 - uses: projectdiscovery/actions/setup/go@v1 - uses: projectdiscovery/actions/cache/nuclei@v1 - uses: projectdiscovery/actions/setup/python@v1 - uses: projectdiscovery/actions/cache/go-rod-browser@v1 - run: bash run.sh "${{ matrix.os }}" env: PDCP_API_KEY: "${{ secrets.PDCP_API_KEY }}" timeout-minutes: 50 working-directory: integration_tests/ functional: name: "Functional tests" needs: ["tests"] env: GITHUB_TOKEN: "${{ github.token }}" strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macOS-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 - uses: projectdiscovery/actions/setup/go@v1 - uses: projectdiscovery/actions/cache/nuclei@v1 - uses: projectdiscovery/actions/setup/python@v1 - uses: projectdiscovery/actions/cache/go-rod-browser@v1 - run: bash run.sh working-directory: cmd/functional-test/ validate: name: "Template validate" needs: ["tests"] runs-on: ubuntu-latest env: GITHUB_TOKEN: "${{ github.token }}" steps: - uses: actions/checkout@v6 - uses: projectdiscovery/actions/setup/go@v1 - uses: projectdiscovery/actions/cache/go-rod-browser@v1 - run: make template-validate codeql: name: "CodeQL analysis" needs: ["tests"] runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write steps: - uses: actions/checkout@v6 - uses: github/codeql-action/init@v4 with: languages: 'go' - uses: github/codeql-action/autobuild@v4 - uses: github/codeql-action/analyze@v4 release: name: "Release test" needs: ["tests"] runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: projectdiscovery/actions/setup/go@v1 with: go-version: "stable" - uses: projectdiscovery/actions/goreleaser@v1 flamegraph: name: "Flamegraph" needs: ["tests"] uses: ./.github/workflows/flamegraph.yaml perf-regression: name: "Performance regression" needs: ["tests"] uses: ./.github/workflows/perf-regression.yaml ================================================ FILE: .github/workflows/typos.yaml ================================================ name: 🔤 Typos on: push: branches: ["dev"] pull_request: workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: typos: name: "Spell Check" if: ${{ !endsWith(github.actor, '[bot]') }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: crate-ci/typos@v1.28.4 ================================================ FILE: .goreleaser.yml ================================================ version: 2 before: hooks: - go mod download - go mod verify builds: - main: cmd/nuclei/main.go binary: nuclei id: nuclei-cli env: - CGO_ENABLED=0 - GOEXPERIMENT=greenteagc goos: [windows, linux, darwin] goarch: [amd64, '386', arm, arm64] ignore: - goos: darwin goarch: '386' - goos: windows goarch: arm - goos: windows goarch: arm64 flags: - -trimpath - -pgo=auto ldflags: - -s - -w #- main: cmd/tmc/main.go # binary: tmc # id: annotate # # env: # - CGO_ENABLED=0 # # goos: [linux] # goarch: [amd64] archives: - formats: [zip] id: nuclei ids: [nuclei-cli] name_template: '{{ .ProjectName }}_{{ .Version }}_{{ if eq .Os "darwin" }}macOS{{ else }}{{ .Os }}{{ end }}_{{ .Arch }}' checksum: algorithm: sha256 dockers: - image_templates: - "projectdiscovery/{{ .ProjectName }}:{{ .Tag }}-amd64" - "projectdiscovery/{{ .ProjectName }}:v{{ .Major }}.{{ .Minor }}-amd64" - "projectdiscovery/{{ .ProjectName }}:v{{ .Major }}-amd64" - "projectdiscovery/{{ .ProjectName }}:latest-amd64" dockerfile: Dockerfile.goreleaser use: buildx build_flag_templates: - "--pull" - "--platform=linux/amd64" - "--label=org.opencontainers.image.created={{ .Date }}" - "--label=org.opencontainers.image.ref.name={{ .Tag }}" - "--label=org.opencontainers.image.revision={{ .FullCommit }}" - "--label=org.opencontainers.image.version={{ .Version }}" goarch: amd64 - image_templates: - "projectdiscovery/{{ .ProjectName }}:{{ .Tag }}-arm64" - "projectdiscovery/{{ .ProjectName }}:v{{ .Major }}.{{ .Minor }}-arm64" - "projectdiscovery/{{ .ProjectName }}:v{{ .Major }}-arm64" - "projectdiscovery/{{ .ProjectName }}:latest-arm64" dockerfile: Dockerfile.goreleaser use: buildx build_flag_templates: - "--pull" - "--platform=linux/arm64" - "--label=org.opencontainers.image.created={{ .Date }}" - "--label=org.opencontainers.image.ref.name={{ .Tag }}" - "--label=org.opencontainers.image.revision={{ .FullCommit }}" - "--label=org.opencontainers.image.version={{ .Version }}" goarch: arm64 # # NOTE(dwisiswant0): chromium doesn't support 32-bit on alpine # - image_templates: # - "projectdiscovery/{{ .ProjectName }}:{{ .Tag }}-386" # - "projectdiscovery/{{ .ProjectName }}:v{{ .Major }}.{{ .Minor }}-386" # - "projectdiscovery/{{ .ProjectName }}:v{{ .Major }}-386" # - "projectdiscovery/{{ .ProjectName }}:latest-386" # dockerfile: Dockerfile.goreleaser # use: buildx # build_flag_templates: # - "--pull" # - "--platform=linux/386" # - "--label=org.opencontainers.image.created={{ .Date }}" # - "--label=org.opencontainers.image.ref.name={{ .Tag }}" # - "--label=org.opencontainers.image.revision={{ .FullCommit }}" # - "--label=org.opencontainers.image.version={{ .Version }}" # goarch: "386" docker_manifests: - name_template: "projectdiscovery/{{ .ProjectName }}:{{ .Tag }}" image_templates: - "projectdiscovery/{{ .ProjectName }}:{{ .Tag }}-amd64" - "projectdiscovery/{{ .ProjectName }}:{{ .Tag }}-arm64" - name_template: "projectdiscovery/{{ .ProjectName }}:v{{ .Major }}.{{ .Minor }}" image_templates: - "projectdiscovery/{{ .ProjectName }}:v{{ .Major }}.{{ .Minor }}-amd64" - "projectdiscovery/{{ .ProjectName }}:v{{ .Major }}.{{ .Minor }}-arm64" - name_template: "projectdiscovery/{{ .ProjectName }}:v{{ .Major }}" image_templates: - "projectdiscovery/{{ .ProjectName }}:v{{ .Major }}-amd64" - "projectdiscovery/{{ .ProjectName }}:v{{ .Major }}-arm64" - name_template: "projectdiscovery/{{ .ProjectName }}:latest" image_templates: - "projectdiscovery/{{ .ProjectName }}:latest-amd64" - "projectdiscovery/{{ .ProjectName }}:latest-arm64" announce: slack: enabled: true channel: '#release' username: GoReleaser message_template: 'New Release: {{ .ProjectName }} {{.Tag}} is published! Check it out at {{ .ReleaseURL }}' discord: enabled: true message_template: '**New Release: {{ .ProjectName }} {{.Tag}}** is published! Check it out at {{ .ReleaseURL }}' ================================================ FILE: .run/DSLFunctionsIT.run.xml ================================================ ================================================ FILE: .run/IntegrationTests.run.xml ================================================ ================================================ FILE: .run/RegressionTests.run.xml ================================================ ================================================ FILE: .run/UnitTests.run.xml ================================================ ================================================ FILE: CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview Nuclei is a modern, high-performance vulnerability scanner built in Go that leverages YAML-based templates for customizable vulnerability detection. It supports multiple protocols (HTTP, DNS, TCP, SSL, WebSocket, WHOIS, JavaScript, Code) and is designed for zero false positives through real-world condition simulation. ## Development Commands ### Building and Testing - `make build` - Build the main nuclei binary to ./bin/nuclei - `make test` - Run unit tests with race detection - `make integration` - Run integration tests (builds and runs test suite) - `make functional` - Run functional tests - `make vet` - Run go vet for code analysis - `make tidy` - Clean up go modules ### Validation and Linting - `make template-validate` - Validate nuclei templates using the built binary - `go fmt ./...` - Format Go code - `go vet ./...` - Static analysis ### Development Tools - `make devtools-all` - Build all development tools (bindgen, tsgen, scrapefuncs) - `make jsupdate-all` - Update JavaScript bindings and TypeScript definitions - `make docs` - Generate documentation - `make memogen` - Generate memoization code for JavaScript libraries ### Testing Specific Components - Run single test: `go test -v ./pkg/path/to/package -run TestName` - Integration tests are in `integration_tests/` and can be run via `make integration` ## Architecture Overview ### Core Components - **cmd/nuclei** - Main CLI entry point with flag parsing and configuration - **internal/runner** - Core runner that orchestrates the entire scanning process - **pkg/core** - Execution engine with work pools and template clustering - **pkg/templates** - Template parsing, compilation, and management - **pkg/protocols** - Protocol implementations (HTTP, DNS, Network, etc.) - **pkg/operators** - Matching and extraction logic (matchers/extractors) - **pkg/catalog** - Template discovery and loading from disk/remote sources ### Protocol Architecture Each protocol (HTTP, DNS, Network, etc.) implements: - Request interface with Compile(), ExecuteWithResults(), Match(), Extract() methods - Operators embedding for matching/extraction functionality - Protocol-specific request building and execution logic ### Template System - Templates are YAML files defining vulnerability detection logic - Compiled into executable requests with operators (matchers/extractors) - Support for workflows (multistep template execution) - Template clustering optimizes identical requests across multiple templates ### Key Execution Flow 1. Template loading and compilation via pkg/catalog/loader 2. Input provider setup for targets 3. Engine creation with work pools for concurrency 4. Template execution with result collection via operators 5. Output writing and reporting integration ### JavaScript Integration - Custom JavaScript runtime for code protocol templates - Auto-generated bindings in pkg/js/generated/ - Library implementations in pkg/js/libs/ - Development tools for binding generation in pkg/js/devtools/ ## Template Development - Templates located in separate nuclei-templates repository - YAML format with info, requests, and operators sections - Support for multiple protocol types in single template - Built-in DSL functions for dynamic content generation - Template validation available via `make template-validate` ## Key Directories - **lib/** - SDK for embedding nuclei as a library - **examples/** - Usage examples for different scenarios - **integration_tests/** - Integration test suite with protocol-specific tests - **pkg/fuzz/** - Fuzzing engine and DAST capabilities - **pkg/input/** - Input processing for various formats (Burp, OpenAPI, etc.) - **pkg/reporting/** - Result export and issue tracking integrations ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to ProjectDiscovery/Nuclei We appreciate your interest in contributing to the projectdiscovery/nuclei! This document provides some basic guidelines for contributors. ## Getting Started - Always base your work from the `dev` branch, which is the development branch with the latest code. - Before creating a Pull Request (PR), make sure there is a corresponding issue for your contribution. If there isn't one already, please create one. - Include the problem description in the issue. ## Pull Requests When creating a PR, please follow these guidelines: - Link your PR to the corresponding issue. - Provide context in the PR description to help reviewers understand the changes. The more information you provide, the faster the review process will be. - Include an example of running the tool with the changed code, if applicable. Provide 'before' and 'after' examples if possible. - Include steps for functional testing or replication. - If you're adding a new feature, make sure to include unit tests. ## Code Style Please adhere to the existing coding style for consistency. ## Development To ensure your changes work as expected and strictly adhere to the project's standards, please run the following commands before submitting a PR: - **Run tests**: ```sh make test ``` - **Run linters/vet**: ```sh make vet ``` - **Build the project**: ```sh make build ``` ## Questions If you have any questions or need further guidance, please feel free to ask in the issue or PR, or [reach out to the maintainers](https://discord.gg/projectdiscovery). Thank you for your contribution! ================================================ FILE: DEBUG.md ================================================ ## Debugging Nuclei While Adding new features or fixing bugs or writing new templates to properly understand the behavior of that component, it is essential to understand what debugging options are available in nuclei. This guide lists all the debugging options available in nuclei. ### Template related debugging - `-debug` flag When this flag is provided, nuclei will print all requests that are being sent by nuclei to the target as well as the response received from the target. - `-debug-req` flag When this flag is provided, nuclei will print all requests that are being sent by nuclei to the target. - `-debug-resp` flag When this flag is provided, nuclei will print all responses that are being received by nuclei from the target. - `-ldf` flag When this flag is provided, nuclei will print the list of all helper functions available in this release of nuclei and exit. - `-svd` flag When this flag is provided, nuclei will print all `variables` pre- and post-execution of a request for a template. This is useful to understand what variables are available for a template and what values they have. - `-elog = errors.txt` flag When this flag is provided, nuclei will log all errors to the file specified. This is helpful when running large scans. ### Environment Variable Switches Nuclei was built with some environment variables in mind to help with debugging. These environment variables can be set to enable debugging of a particular component/functionality for nuclei. | Environment Variable | Description | | -------------------------------- | -------------------------------------------------------- | | `DEBUG=true` | Enables Printing Stack Traces for all errors | | `SHOW_DSL_ERRORS=true` | Enables Printing DSL Errors (that are hidden by default) | | `HIDE_TEMPLATE_SIG_WARNING=true` | Hides Template Signature Verification Warnings | | `NUCLEI_LOG_ALL=true` | Log All Events that were skipped in verbose mode | | `NUCLEI_CONFIG_DIR` | Sets custom configuration directory path | | `NUCLEI_TEMPLATES_DIR` | Sets custom nuclei templates directory path | ================================================ FILE: DESIGN.md ================================================ # Nuclei Architecture Document A brief overview of Nuclei Engine architecture. This document will be kept updated as the engine progresses. ## pkg/templates ### Template Template is the basic unit of input to the engine which describes the requests to be made, matching to be done, data to extract, etc. The template structure is described here. Template level attributes are defined here as well as convenience methods to validate, parse and compile templates creating executers. Any attributes etc. required for the template, engine or requests to function are also set here. Workflows are also compiled, their templates are loaded and compiled as well. Any validations etc. on the paths provided are also done here. `Parse` function is the main entry point which returns a template for a `filePath` and `executorOptions`. It compiles all the requests for the templates, all the workflows, as well as any self-contained request etc. It also caches the templates in an in-memory cache. ### Preprocessors Preprocessors are also applied here which can do things at template level. They get data of the template which they can alter at will on runtime. This is used in the engine to do random string generation. Custom processor can be used if they satisfy the following interface. ```go type Preprocessor interface { Process(data []byte) []byte } ``` ## pkg/model Model package implements Information structure for Nuclei Templates. `Info` contains all major metadata information for the template. `Classification` structure can also be used to provide additional context to vulnerability data. It also specifies a `WorkflowLoader` interface that is used during workflow loading in template compilation stage. ```go type WorkflowLoader interface { GetTemplatePathsByTags(tags []string) []string GetTemplatePaths(templatesList []string, noValidate bool) []string } ``` ## pkg/protocols Protocols package implements all the request protocols supported by Nuclei. This includes http, dns, network, headless and file requests as of now. ### Request It exposes a `Request` interface that is implemented by all the request protocols supported. ```go // Request is an interface implemented any protocol based request generator. type Request interface { Compile(options *ExecuterOptions) error Requests() int GetID() string Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) Extract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{} ExecuteWithResults(input string, dynamicValues, previous output.InternalEvent, callback OutputEventCallback) error MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent GetCompiledOperators() []*operators.Operators } ``` Many of these methods are similar across protocols while some are very protocol specific. A brief overview of the methods is provided below - - **Compile** - Compiles the request with provided options. - **Requests** - Returns total requests made. - **GetID** - Returns any ID for the request - **Match** - Used to perform matching for patterns using matchers - **Extract** - Used to perform extraction for patterns using extractors - **ExecuteWithResults** - Request execution function for input. - **MakeResultEventItem** - Creates a single result event for the intermediate `InternalWrappedEvent` output structure. - **MakeResultEvent** - Returns a slice of results based on an `InternalWrappedEvent` internal output event. - **GetCompiledOperators** - Returns the compiled operators for the request. `MakeDefaultResultEvent` function can be used as a default for `MakeResultEvent` function when no protocol-specific features need to be implemented for result generation. For reference protocol requests implementations, one can look at the below packages - 1. [pkg/protocols/http](./pkg/protocols/http) 2. [pkg/protocols/dns](./pkg/protocols/dns) 3. [pkg/protocols/network](./pkg/protocols/network) ### Executer All these different requests interfaces are converted to an Executer which is also an interface defined in `pkg/protocols` which is used during final execution of the template. ```go // Executer is an interface implemented any protocol based request executer. type Executer interface { Compile() error Requests() int Execute(input string) (bool, error) ExecuteWithResults(input string, callback OutputEventCallback) error } ``` The `ExecuteWithResults` function accepts a callback, which gets provided with results during execution in form of `*output.InternalWrappedEvent` structure. The default executer is provided in `pkg/protocols/common/executer` . It takes a list of Requests and relevant `ExecuterOptions` and implements the Executer interface required for template execution. The executer during Template compilation process is created from this package and used as-is. A different executer is the Clustered Requests executer which implements the Nuclei Request clustering functionality in `pkg/templates` We have a single HTTP request in cases where multiple templates can be clustered and multiple operator lists to match/extract. The first HTTP request is executed while all the template matcher/extractor are evaluated separately. For Workflow execution, a separate RunWorkflow function is used which executes the workflow independently of the template execution. With this basic premise set, we can now start exploring the current runner implementation which will also walk us through the architecture of nuclei. ## internal/runner ### Template loading The first process after all CLI specific initialisation is the loading of template/workflow paths that the user wants to run. This is done by the packages described below. #### pkg/catalog This package is used to get paths using mixed syntax. It takes a template directory and performs resolving for template paths both from provided template and current user directory. The syntax is very versatile and can include filenames, glob patterns, directories, absolute paths, and relative-paths. Next step is the initialisation of the reporting modules which is handled in `pkg/reporting`. #### pkg/reporting Reporting module contains exporters and trackers as well as a module for deduplication and a module for result formatting. Exporters and Trackers are interfaces defined in pkg/reporting. ```go // Tracker is an interface implemented by an issue tracker type Tracker interface { CreateIssue(event *output.ResultEvent) error } // Exporter is an interface implemented by an issue exporter type Exporter interface { Close() error Export(event *output.ResultEvent) error } ``` Exporters include `Elasticsearch`, `markdown`, `sarif` . Trackers include `GitHub`, `GitLab` and `Jira`. Each exporter and trackers implement their own configuration in YAML format and are very modular in nature, so adding new ones is easy. After reading all the inputs from various sources and initialisation other miscellaneous options, the next bit is the output writing which is done using `pkg/output` module. #### pkg/output Output package implements the output writing functionality for Nuclei. Output Writer implements the Writer interface which is called each time a result is found for nuclei. ```go // Writer is an interface which writes output to somewhere for nuclei events. type Writer interface { Close() Colorizer() aurora.Aurora Write(*ResultEvent) error Request(templateID, url, requestType string, err error) } ``` ResultEvent structure is passed to the Nuclei Output Writer which contains the entire detail of a found result. Various intermediary types like `InternalWrappedEvent` and `InternalEvent` are used throughout nuclei protocols and matchers to describe results in various stages of execution. Interactsh is also initialised if it is not explicitly disabled. #### pkg/protocols/common/interactsh Interactsh module is used to provide automatic Out-of-Band vulnerability identification in Nuclei. It uses two LRU caches, one for storing interactions for request URLs and one for storing requests for interaction URL. These both caches are used to correlated requests received to the Interactsh OOB server and Nuclei Instance. [Interactsh Client](https://github.com/projectdiscovery/interactsh/pkg/client) package does most of the heavy lifting of this module. Polling for interactions and server registration only starts when a template uses the interactsh module and is executed by nuclei. After that no registration is required for the entire run. ### RunEnumeration Next we arrive in the `RunEnumeration` function of the runner. `HostErrorsCache` is initialised which is used throughout the run of Nuclei enumeration to keep track of errors per host and skip further requests if the errors are greater than the provided threshold. The functionality for the error tracking cache is defined in [hosterrorscache.go](https://github.com/projectdiscovery/nuclei/blob/main/pkg/protocols/common/hosterrorscache/hosterrorscache.go) and is pretty simplistic in nature. Next the `WorkflowLoader` is initialised which used to load workflows. It exists in `pkg/parsers/workflow_loader.go` The loader is initialised moving forward which is responsible for Using Catalog, Passed Tags, Filters, Paths, etc. to return compiled `Templates` and `Workflows`. #### pkg/catalog/loader First the input passed by the user as paths is normalised to absolute paths which is done by the `pkg/catalog` module. Next the path filter module is used to remove the excluded template/workflows paths. `pkg/parsers` module's `LoadTemplate`,`LoadWorkflow` functions are used to check if the templates pass the validation + are not excluded via tags/severity/etc. filters. If all checks are passed, then the template/workflow is parsed and returned in a compiled form by the `pkg/templates`'s `Parse` function. `Parse` function performs compilation of all the requests in a template + creates Executers from them returning a runnable Template/Workflow structure. Clustering module comes in next whose job is to cluster identical HTTP GET requests together (as a lot of the templates perform the same get requests many times, it's a good way to save many requests on large scans with lots of templates). ### pkg/operators Operators package implements all the matching and extracting logic of Nuclei. ```go // Operators contain the operators that can be applied on protocols type Operators struct { Matchers []*matchers.Matcher Extractors []*extractors.Extractor MatchersCondition string } ``` A protocol only needs to embed the `operators.Operators` type shown above, and it can utilise all the matching/extracting functionality of nuclei. ```go // 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) ``` The core of this process is the Execute function which takes an input dictionary as well as a Match and Extract function and return a `Result` structure which is used later during nuclei execution to check for results. ```go // Result is a result structure created from operators running on data. type Result struct { Matched bool Extracted bool Matches map[string][]string Extracts map[string][]string OutputExtracts []string DynamicValues map[string]interface{} PayloadValues map[string]interface{} } ``` The internal logics for matching and extracting for things like words, regexes, jq, paths, etc. is specified in `pkg/operators/matchers`, `pkg/operators/extractors`. Those packages should be investigated for further look into the topic. ### Template Execution `pkg/core` provides the engine mechanism which runs the templates/workflows on inputs. It exposes an `Execute` function which does the task of execution while also doing template clustering. The clustering can also be disabled optionally by the user. An example of using the core engine is provided below. ```go engine := core.New(r.options) engine.SetExecuterOptions(executerOpts) results := engine.ExecuteWithOpts(finalTemplates, r.hmapInputProvider, true) ``` ### Adding a New Protocol Protocols form the core of Nuclei Engine. All the request types like `http`, `dns`, etc. are implemented in form of protocol requests. A protocol must implement the `Protocol` and `Request` interfaces described above in `pkg/protocols`. We'll take the example of an existing protocol implementation - websocket for this short reference around Nuclei internals. The code for the websocket protocol is contained in `pkg/protocols/others/websocket`. Below a high level skeleton of the websocket implementation is provided with all the important parts present. ```go package websocket // Request is a request for the Websocket protocol type Request struct { // Operators for the current request go here. operators.Operators `yaml:",inline,omitempty"` CompiledOperators *operators.Operators `yaml:"-"` // description: | // Address contains address for the request Address string `yaml:"address,omitempty" jsonschema:"title=address for the websocket request,description=Address contains address for the request"` // declarations here } // Compile compiles the request generators preparing any requests possible. func (r *Request) Compile(options *protocols.ExecuterOptions) error { r.options = options // request compilation here as well as client creation if len(r.Matchers) > 0 || len(r.Extractors) > 0 { compiled := &r.Operators if err := compiled.Compile(); err != nil { return errors.Wrap(err, "could not compile operators") } r.CompiledOperators = compiled } return nil } // Requests returns the total number of requests the rule will perform func (r *Request) Requests() int { if r.generator != nil { return r.generator.NewIterator().Total() } return 1 } // GetID returns the ID for the request if any. func (r *Request) GetID() string { return "" } // ExecuteWithResults executes the protocol requests and returns results instead of writing them. func (r *Request) ExecuteWithResults(input string, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error { // payloads init here if err := r.executeRequestWithPayloads(input, hostname, value, previous, callback); err != nil { return err } return nil } // ExecuteWithResults executes the protocol requests and returns results instead of writing them. func (r *Request) executeRequestWithPayloads(input, hostname string, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error { header := http.Header{} // make the actual request here after setting all options event := eventcreator.CreateEventWithAdditionalOptions(r, data, r.options.Options.Debug || r.options.Options.DebugResponse, func(internalWrappedEvent *output.InternalWrappedEvent) { internalWrappedEvent.OperatorsResult.PayloadValues = payloadValues }) if r.options.Options.Debug || r.options.Options.DebugResponse { responseOutput := responseBuilder.String() gologger.Debug().Msgf("[%s] Dumped Websocket response for %s", r.options.TemplateID, input) gologger.Print().Msgf("%s", responsehighlighter.Highlight(event.OperatorsResult, responseOutput, r.options.Options.NoColor)) } callback(event) return nil } func (r *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent { data := &output.ResultEvent{ TemplateID: types.ToString(r.options.TemplateID), TemplatePath: types.ToString(r.options.TemplatePath), // ... setting more values for result event } return data } // Match performs matching operation for a matcher on model and returns: // true and a list of matched snippets if the matcher type is supports it // otherwise false and an empty string slice func (r *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) { return protocols.MakeDefaultMatchFunc(data, matcher) } // Extract performs extracting operation for an extractor on model and returns true or false. func (r *Request) Extract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{} { return protocols.MakeDefaultExtractFunc(data, matcher) } // MakeResultEvent creates a result event from internal wrapped event func (r *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent { return protocols.MakeDefaultResultEvent(r, wrapped) } // GetCompiledOperators returns a list of the compiled operators func (r *Request) GetCompiledOperators() []*operators.Operators { return []*operators.Operators{r.CompiledOperators} } // Type returns the type of the protocol request func (r *Request) Type() templateTypes.ProtocolType { return templateTypes.WebsocketProtocol } ``` Almost all of these protocols have boilerplate functions for which default implementations have been provided in the `providers` package. Examples are the implementation of `Match`, `Extract`, `MakeResultEvent`, `GetCompiledOperators`, etc. which are almost same throughout Nuclei protocols code. It is enough to copy-paste them unless customization is required. `eventcreator` package offers `CreateEventWithAdditionalOptions` function which can be used to create result events after doing request execution. Step by step description of how to add a new protocol to Nuclei - 1. Add the protocol implementation in `pkg/protocols` directory. If it's a small protocol with fewer options, considering adding it to the `pkg/protocols/others` directory. Add the enum for the new protocol to `pkg/templates/types/types.go`. 2. Add the protocol request structure to the `Template` structure fields. This is done in `pkg/templates/templates.go` with the corresponding import line. ```go import ( ... "github.com/projectdiscovery/nuclei/v3/pkg/protocols/others/websocket" ) // Template is a YAML input file which defines all the requests and // other metadata for a template. type Template struct { ... // description: | // Websocket contains the Websocket request to make in the template. RequestsWebsocket []*websocket.Request `yaml:"websocket,omitempty" json:"websocket,omitempty" jsonschema:"title=websocket requests to make,description=Websocket requests to make for the template"` ... } ``` Also add the protocol case to the `Type` function as well as the `TemplateTypes` array in the same `templates.go` file. ```go // TemplateTypes is a list of accepted template types var TemplateTypes = []string{ ... "websocket", } // Type returns the type of the template func (t *Template) Type() templateTypes.ProtocolType { ... case len(t.RequestsWebsocket) > 0: return templateTypes.WebsocketProtocol default: return "" } } ``` 3. Add the protocol request to the `Requests` function and `compileProtocolRequests` function in the `compile.go` file in same directory. ```go // Requests return the total request count for the template func (template *Template) Requests() int { return len(template.RequestsDNS) + ... len(template.RequestsSSL) + len(template.RequestsWebsocket) } // compileProtocolRequests compiles all the protocol requests for the template func (template *Template) compileProtocolRequests(options protocols.ExecuterOptions) error { ... case len(template.RequestsWebsocket) > 0: requests = template.convertRequestToProtocolsRequest(template.RequestsWebsocket) } template.Executer = executer.NewExecuter(requests, &options) return nil } ``` That's it, you've added a new protocol to Nuclei. The next good step would be to write integration tests which are described in `integration-tests` and `cmd/integration-tests` directories. ## Profiling and Tracing To analyze Nuclei's performance and resource usage, you can generate CPU & memory profiles and trace files using the `-profile-mem` flag: ```bash nuclei -t nuclei-templates/ -u https://example.com -profile-mem=nuclei-$(git describe --tags) ``` This command creates three files: * `nuclei.cpu`: CPU profile * `nuclei.mem`: Memory (heap) profile * `nuclei.trace`: Execution trace ### Analyzing the CPU/Memory Profiles * View the profile in the terminal: ```bash go tool pprof nuclei.{cpu,mem} ``` * Display overall CPU time for processing $$N$$ targets: ``` go tool pprof -top nuclei.cpu | grep "Total samples" ``` * Display top memory consumers: ```bash go tool pprof -top nuclei.mem | grep "$(go list -m)" | head -10 ``` * Visualize the profile in a web browser: ```bash go tool pprof -http=:$(shuf -i 1000-99999 -n 1) nuclei.{cpu,mem} ``` ### Analyzing the Trace File To examine the execution trace: ```bash go tool trace nuclei.trace ``` These tools help identify performance bottlenecks and memory leaks, allowing for targeted optimizations of Nuclei's codebase. ## Project Structure - [pkg/reporting](./pkg/reporting) - Reporting modules for nuclei. - [pkg/reporting/exporters/sarif](./pkg/reporting/exporters/sarif) - Sarif Result Exporter - [pkg/reporting/exporters/markdown](./pkg/reporting/exporters/markdown) - Markdown Result Exporter - [pkg/reporting/exporters/es](./pkg/reporting/exporters/es) - Elasticsearch Result Exporter - [pkg/reporting/dedupe](./pkg/reporting/dedupe) - Dedupe module for Results - [pkg/reporting/trackers/gitlab](./pkg/reporting/trackers/gitlab) - GitLab Issue Tracker Exporter - [pkg/reporting/trackers/jira](./pkg/reporting/trackers/jira) - Jira Issue Tracker Exporter - [pkg/reporting/trackers/github](./pkg/reporting/trackers/github) - GitHub Issue Tracker Exporter - [pkg/reporting/format](./pkg/reporting/format) - Result Formatting Functions - [pkg/parsers](./pkg/parsers) - Implements template as well as workflow loader for initial template discovery, validation and - loading. - [pkg/types](./pkg/types) - Contains CLI options as well as misc helper functions. - [pkg/progress](./pkg/progress) - Progress tracking - [pkg/operators](./pkg/operators) - Operators for Nuclei - [pkg/operators/common/dsl](./pkg/operators/common/dsl) - DSL functions for Nuclei YAML Syntax - [pkg/operators/matchers](./pkg/operators/matchers) - Matchers implementation - [pkg/operators/extractors](./pkg/operators/extractors) - Extractors implementation - [pkg/catalog](./pkg/catalog) - Template loading from disk helpers - [pkg/catalog/config](./pkg/catalog/config) - Internal configuration management - [pkg/catalog/loader](./pkg/catalog/loader) - Implements loading and validation of templates and workflows. - [pkg/catalog/loader/filter](./pkg/catalog/loader/filter) - Filter filters templates based on tags and paths - [pkg/output](./pkg/output) - Output module for nuclei - [pkg/workflows](./pkg/workflows) - Workflow execution logic + declarations - [pkg/utils](./pkg/utils) - Utility functions - [pkg/model](./pkg/model) - Template Info + misc - [pkg/templates](./pkg/templates) - Templates core starting point - [pkg/templates/cache](./pkg/templates/cache) - Templates cache - [pkg/protocols](./pkg/protocols) - Protocol Specification - [pkg/protocols/file](./pkg/protocols/file) - File protocol - [pkg/protocols/network](./pkg/protocols/network) - Network protocol - [pkg/protocols/common/expressions](./pkg/protocols/common/expressions) - Expression evaluation + Templating Support - [pkg/protocols/common/interactsh](./pkg/protocols/common/interactsh) - Interactsh integration - [pkg/protocols/common/generators](./pkg/protocols/common/generators) - Payload support for Requests (Sniper, etc.) - [pkg/protocols/common/executer](./pkg/protocols/common/executer) - Default Template Executer - [pkg/protocols/common/replacer](./pkg/protocols/common/replacer) - Template replacement helpers - [pkg/protocols/common/helpers/eventcreator](./pkg/protocols/common/helpers/eventcreator) - Result event creator - [pkg/protocols/common/helpers/responsehighlighter](./pkg/protocols/common/helpers/responsehighlighter) - Debug response highlighter - [pkg/protocols/common/helpers/deserialization](./pkg/protocols/common/helpers/deserialization) - Deserialization helper functions - [pkg/protocols/common/hosterrorscache](./pkg/protocols/common/hosterrorscache) - Host errors cache for tracking erroring hosts - [pkg/protocols/offlinehttp](./pkg/protocols/offlinehttp) - Offline http protocol - [pkg/protocols/http](./pkg/protocols/http) - HTTP protocol - [pkg/protocols/http/race](./pkg/protocols/http/race) - HTTP Race Module - [pkg/protocols/http/raw](./pkg/protocols/http/raw) - HTTP Raw Request Support - [pkg/protocols/headless](./pkg/protocols/headless) - Headless Module - [pkg/protocols/headless/engine](./pkg/protocols/headless/engine) - Internal Headless implementation - [pkg/protocols/dns](./pkg/protocols/dns) - DNS protocol - [pkg/projectfile](./pkg/projectfile) - Project File Implementation ### Notes 1. The matching as well as interim output functionality is a bit complex, we should simplify it a bit as well. ================================================ FILE: Dockerfile ================================================ # Build FROM golang:1.24-alpine AS builder RUN apk add build-base WORKDIR /app COPY . /app RUN make verify RUN make build # Release FROM alpine:latest RUN apk add --no-cache bind-tools chromium ca-certificates COPY --from=builder /app/bin/nuclei /usr/local/bin/ ENTRYPOINT ["nuclei"] ================================================ FILE: Dockerfile.goreleaser ================================================ FROM alpine:latest LABEL org.opencontainers.image.authors="ProjectDiscovery" LABEL org.opencontainers.image.description="Nuclei is a fast, customizable vulnerability scanner powered by the global security community and built on a simple YAML-based DSL, enabling collaboration to tackle trending vulnerabilities on the internet. It helps you find vulnerabilities in your applications, APIs, networks, DNS, and cloud configurations." LABEL org.opencontainers.image.licenses="MIT" LABEL org.opencontainers.image.title="nuclei" LABEL org.opencontainers.image.url="https://github.com/projectdiscovery/nuclei" RUN apk add --no-cache bind-tools chromium ca-certificates COPY nuclei /usr/local/bin/ ENTRYPOINT ["nuclei"] ================================================ FILE: LICENSE.md ================================================ MIT License Copyright (c) 2025 ProjectDiscovery, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ # Go parameters GOCMD := go GOBUILD := $(GOCMD) build GOBUILD_OUTPUT := GOBUILD_PACKAGES := GOBUILD_ADDITIONAL_ARGS := GOMOD := $(GOCMD) mod GOTEST := $(GOCMD) test GOFLAGS := -v # This should be disabled if the binary uses pprof LDFLAGS := -s -w ifneq ($(shell go env GOOS),darwin) LDFLAGS += -extldflags "-static" endif .PHONY: all build build-stats clean devtools-all devtools-bindgen devtools-scrapefuncs .PHONY: devtools-tsgen docs docgen dsl-docs functional fuzzplayground go-build lint lint-strict syntax-docs .PHONY: integration jsupdate-all jsupdate-bindgen jsupdate-tsgen memogen scan-charts test test-with-lint .PHONY: tidy ts verify download vet template-validate all: build clean: rm -f '${GOBUILD_OUTPUT}' 2>/dev/null go-build: clean go-build: CGO_ENABLED=0 $(GOBUILD) -trimpath $(GOFLAGS) -ldflags '${LDFLAGS}' $(GOBUILD_ADDITIONAL_ARGS) \ -o '${GOBUILD_OUTPUT}' $(GOBUILD_PACKAGES) build: GOFLAGS = -v -pgo=auto build: GOBUILD_OUTPUT = ./bin/nuclei build: GOBUILD_PACKAGES = cmd/nuclei/main.go build: go-build build-test: GOFLAGS = -v -pgo=auto build-test: GOBUILD_OUTPUT = ./bin/nuclei.test build-test: GOBUILD_PACKAGES = ./cmd/nuclei/ build-test: clean build-test: CGO_ENABLED=0 $(GOCMD) test -c -trimpath $(GOFLAGS) -ldflags '${LDFLAGS}' $(GOBUILD_ADDITIONAL_ARGS) \ -o '${GOBUILD_OUTPUT}' ${GOBUILD_PACKAGES} build-stats: GOBUILD_OUTPUT = ./bin/nuclei-stats build-stats: GOBUILD_PACKAGES = cmd/nuclei/main.go build-stats: GOBUILD_ADDITIONAL_ARGS = -tags=stats build-stats: go-build scan-charts: GOBUILD_OUTPUT = ./bin/scan-charts scan-charts: GOBUILD_PACKAGES = cmd/scan-charts/main.go scan-charts: go-build template-signer: GOBUILD_OUTPUT = ./bin/template-signer template-signer: GOBUILD_PACKAGES = cmd/tools/signer/main.go template-signer: go-build docgen: GOBUILD_OUTPUT = ./bin/docgen docgen: GOBUILD_PACKAGES = cmd/docgen/docgen.go docgen: bin = dstdocgen docgen: @if ! which $(bin) >/dev/null; then \ echo "Command $(bin) not found! Installing..."; \ go install -v github.com/projectdiscovery/yamldoc-go/cmd/docgen/$(bin)@latest; \ fi # TODO: FIX THIS PANIC $(GOCMD) generate pkg/templates/templates.go $(GOBUILD) -o "${GOBUILD_OUTPUT}" $(GOBUILD_PACKAGES) docs: docgen docs: ./bin/docgen docs.md nuclei-jsonschema.json syntax-docs: docgen syntax-docs: ./bin/docgen SYNTAX-REFERENCE.md nuclei-jsonschema.json test: GOFLAGS = -race -v -timeout 30m -count 1 test: $(GOTEST) $(GOFLAGS) ./... integration: cd integration_tests; bash run.sh functional: cd cmd/functional-test; bash run.sh tidy: $(GOMOD) tidy download: $(GOMOD) download verify: download $(GOMOD) verify vet: verify $(GOCMD) vet ./... devtools-bindgen: GOBUILD_OUTPUT = ./bin/bindgen devtools-bindgen: GOBUILD_PACKAGES = pkg/js/devtools/bindgen/cmd/bindgen/main.go devtools-bindgen: go-build devtools-tsgen: GOBUILD_OUTPUT = ./bin/tsgen devtools-tsgen: GOBUILD_PACKAGES = pkg/js/devtools/tsgen/cmd/tsgen/main.go devtools-tsgen: go-build devtools-scrapefuncs: GOBUILD_OUTPUT = ./bin/scrapefuncs devtools-scrapefuncs: GOBUILD_PACKAGES = pkg/js/devtools/scrapefuncs/main.go devtools-scrapefuncs: go-build devtools-all: devtools-bindgen devtools-tsgen devtools-scrapefuncs jsupdate-bindgen: GOBUILD_OUTPUT = ./bin/bindgen jsupdate-bindgen: GOBUILD_PACKAGES = pkg/js/devtools/bindgen/cmd/bindgen/main.go jsupdate-bindgen: go-build jsupdate-bindgen: ./$(GOBUILD_OUTPUT) -dir pkg/js/libs -out pkg/js/generated jsupdate-tsgen: GOBUILD_OUTPUT = ./bin/tsgen jsupdate-tsgen: GOBUILD_PACKAGES = pkg/js/devtools/tsgen/cmd/tsgen/main.go jsupdate-tsgen: go-build jsupdate-tsgen: ./$(GOBUILD_OUTPUT) -dir pkg/js/libs -out pkg/js/generated/ts jsupdate-all: jsupdate-bindgen jsupdate-tsgen ts: jsupdate-tsgen fuzzplayground: GOBUILD_OUTPUT = ./bin/fuzzplayground fuzzplayground: GOBUILD_PACKAGES = cmd/tools/fuzzplayground/main.go fuzzplayground: LDFLAGS = -s -w fuzzplayground: go-build memogen: GOBUILD_OUTPUT = ./bin/memogen memogen: GOBUILD_PACKAGES = cmd/memogen/memogen.go memogen: go-build memogen: ./$(GOBUILD_OUTPUT) -src pkg/js/libs -tpl cmd/memogen/function.tpl dsl-docs: GOBUILD_OUTPUT = ./bin/scrapefuncs dsl-docs: GOBUILD_PACKAGES = pkg/js/devtools/scrapefuncs/main.go dsl-docs: ./$(GOBUILD_OUTPUT) -out dsl.md template-validate: build template-validate: ./bin/nuclei -ut ./bin/nuclei -validate \ -et http/technologies \ -t dns \ -t ssl \ -t network \ -t http/exposures \ -ept code ./bin/nuclei -validate \ -w workflows \ -et http/technologies \ -ept code ================================================ FILE: README.md ================================================ ![nuclei](/static/nuclei-cover-image.png)
`English``中文``Korean``Indonesia``Spanish``日本語``Portuguese``Türkçe`

     



**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).

Nuclei template example for detecting TeamCity misconfiguration

#### Examples Visit [**our documentation**](https://docs.projectdiscovery.io/templates/introduction) for use cases and ideas. | Use case | Nuclei template | | :----------------------------------- | :------------------------------------------------- | | Detect known CVEs | **[CVE-2021-44228 (Log4Shell)](https://cloud.projectdiscovery.io/public/CVE-2021-45046)** | | Identify Out-of-Band vulnerabilities | **[Blind SQL Injection via OOB](https://cloud.projectdiscovery.io/public/CVE-2024-22120)** | | SQL Injection detection | **[Generic SQL Injection](https://cloud.projectdiscovery.io/public/CVE-2022-34265)** | | Cross-Site Scripting (XSS) | **[Reflected XSS Detection](https://cloud.projectdiscovery.io/public/CVE-2023-4173)** | | Default or weak passwords | **[Default Credentials Check](https://cloud.projectdiscovery.io/public/airflow-default-login)** | | Secret files or data exposure | **[Sensitive File Disclosure](https://cloud.projectdiscovery.io/public/airflow-configuration-exposure)** | | Identify open redirects | **[Open Redirect Detection](https://cloud.projectdiscovery.io/public/open-redirect)** | | Detect subdomain takeovers | **[Subdomain Takeover Templates](https://cloud.projectdiscovery.io/public/azure-takeover-detection)** | | Security misconfigurations | **[Unprotected Jenkins Console](https://cloud.projectdiscovery.io/public/unauthenticated-jenkins)** | | Weak SSL/TLS configurations | **[SSL Certificate Expiry](https://cloud.projectdiscovery.io/public/expired-ssl)** | | Misconfigured cloud services | **[Open S3 Bucket Detection](https://cloud.projectdiscovery.io/public/s3-public-read-acp)** | | Remote code execution vulnerabilities| **[RCE Detection Templates](https://cloud.projectdiscovery.io/public/CVE-2024-29824)** | | Directory traversal attacks | **[Path Traversal Detection](https://cloud.projectdiscovery.io/public/oracle-fatwire-lfi)** | | File inclusion vulnerabilities | **[Local/Remote File Inclusion](https://cloud.projectdiscovery.io/public/CVE-2023-6977)** |

## 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)
================================================ FILE: README_CN.md ================================================


Nuclei

基于YAML语法模板的定制化快速漏洞扫描器

工作流程安装对于安全工程师对于开发者文档致谢常见问题加入Discord

English中文KoreanIndonesiaSpanishPortuguese

--- Nuclei使用零误报的定制模板向目标发送请求,同时可以对主机进行批量快速扫描。Nuclei提供TCP、DNS、HTTP、FILE等各类协议的扫描,通过强大且灵活的模板,可以使用Nuclei模拟各种安全检查。 我们的[模板仓库](https://github.com/projectdiscovery/nuclei-templates)包含**超过300名**安全研究员和工程师提供的模板。 ## 工作流程

nuclei-flow

| :exclamation: **免责声明** | |---------------------------------| | **这个项目正在积极开发中**。预计发布会带来突破性的更改。更新前请查看版本更改日志。 | | 这个项目主要是为了作为一个独立的命令行工具而构建的。 **将Nuclei作为服务运行可能存在安全风险。** 强烈建议谨慎使用,并采取额外的安全措施。 | # 安装Nuclei Nuclei需要 **go1.24.2** 才能安装成功。执行下列命令安装最新版本的Nuclei ```sh go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest ```
Brew ```sh brew install nuclei ```
Docker ```sh docker pull projectdiscovery/nuclei:latest ```
**更多的安装方式 [请点击此处](https://nuclei.projectdiscovery.io/nuclei/get-started/).**
### Nuclei模板 自从[v2.5.2]((https://github.com/projectdiscovery/nuclei/releases/tag/v2.5.2))起,Nuclei就内置了自动下载和更新模板的功能。[**Nuclei模板**](https://github.com/projectdiscovery/nuclei-templates)仓库随时更新社区中可用的模板列表。 您仍然可以随时使用`update-templates`命令更新模板,您可以根据[模板指南](https://nuclei.projectdiscovery.io/templating-guide/)为您的个人工作流和需求编写模板。 YAML的语法规范在[这里](SYNTAX-REFERENCE.md)。
### 用法 ```sh nuclei -h ``` 这将显示Nuclei的帮助,以下是所有支持的命令。 ```console Nuclei是一款注重于可配置性、可扩展性和易用性的基于模板的快速漏洞扫描器。 用法: nuclei [命令] 命令: 目标: -u, -target string[] 指定扫描的目标URL/主机(多个目标则指定多个-u参数) -l, -list string 指定包含要扫描的目标URL/主机列表的文件路径(一行一个) -resume string 从指定文件恢复扫描并保存到指定文件(将禁用请求聚类) -sa, -scan-all-ips 扫描由目标解析出来的所有IP(针对域名对应多个IP的情况) -iv, -ip-version string[] 要扫描的主机名的IP版本(4,6)-(默认为4) 模板: -nt, -new-templates 仅运行最新发布的nuclei模板 -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 启用加载基于协议的代码模板 过滤: -a, -author string[] 执行指定作者的模板(逗号分隔,文件) -tags string[] 执行带指定tag的模板(逗号分隔,文件) -etags, -exclude-tags string[] 排除带指定tag的模板(逗号分隔,文件) -itags, -include-tags string[] 执行带有指定tag的模板,即使是被默认或者配置排除的模板 -id, -template-id string[] 执行指定id的模板(逗号分隔,文件) -eid, -exclude-id string[] 排除指定id的模板(逗号分隔,文件) -it, -include-templates string[] 执行指定模板,即使是被默认或配置排除的模板 -et, -exclude-templates string[] 排除指定模板或者模板目录(逗号分隔,文件) -em, -exclude-matchers string[] 排除指定模板matcher -s, -severity value[] 根据严重程度运行模板,可选值有:info,low,medium,high,critical -es, -exclude-severity value[] 根据严重程度排除模板,可选值有:info,low,medium,high,critical -pt, -type value[] 根据类型运行模板,可选值有:dns, file, http, headless, network, workflow, ssl, websocket, whois -ept, -exclude-type value[] 根据类型排除模板,可选值有:dns, file, http, headless, network, workflow, ssl, websocket, whois -tc, -template-condition string[] 根据表达式运行模板 输出: -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 在JSON、JSONL和Markdown中输出请求/响应对(仅结果)[已弃用,使用-omit-raw替代] -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)格式导出结果 配置: -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、cookie,以header:value的格式指定(cli,文件) -V, -var value 以key=value格式自定义变量 -r, -resolvers string 指定包含DNS解析服务列表的文件 -sr, -system-resolvers 当DNS错误时使用系统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 允许访问本地文件(payload文件) -lna, -restrict-local-network-access 阻止对本地/私有网络的连接 -i, -interface string 指定用于网络扫描的网卡 -at, -attack-type string payload的组合模式(batteringram,pitchfork,clusterbomb) -sip, -source-ip string 指定用于网络扫描的源IP -rsr, -response-size-read int 最大读取响应大小(默认:10485760字节) -rss, -response-size-save int 最大储存响应大小(默认:1048576字节) -reset 删除所有nuclei配置和数据文件(包括nuclei-templates) -tlsi, -tls-impersonate 启用实验性的Client Hello(ja3)TLS 随机化功能 交互: -inserver, -ineractsh-server string 使用interactsh反连检测平台(默认为oast.pro,oast.live,oast.site,oast.online,oast.fun,oast.me) -itoken, -interactsh-token string 指定反连检测平台的身份凭证 -interactions-cache-size int 指定保存在交互缓存中的请求数(默认:5000) -interactions-eviction int 从缓存中删除请求前等待的时间(默认为60秒) -interactions-poll-duration int 每个轮询前等待时间(默认为5秒) -interactions-cooldown-period int 退出轮询前的等待时间(默认为5秒) -ni, -no-interactsh 禁用反连检测平台,同时排除基于反连检测的模板 模糊测试: -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 查询字段 (ip,port,host) (默认 "ip:port") -ul, -uncover-limit int 查询结果数 (默认 100) -ur, -uncover-ratelimit int 查询速率,默认每分钟60个请求(默认 60) 限速: -rl, -rate-limit int 每秒最大请求量(默认:150) -rlm, -rate-limit-minute int 每分钟最大请求量 -bs, -bulk-size int 每个模板最大并行检测数(默认:25) -c, -concurrency int 并行执行的最大模板数量(默认:25) -hbs, -headless-bulk-size int 每个模板并行运行的无头主机最大数量(默认:10) -headc, -headless-concurrency int 并行指定无头主机最大数量(默认:10) -tlc, -template-loading-concurrency int 最大并发模板加载操作数(默认:50) 优化: -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 disable skipping host from scan based on errors -project 使用项目文件夹避免多次发送同一请求 -project-path string 设置特定的项目文件夹 -spm, -stop-at-first-path 得到一个结果后停止(或许会中断模板和工作流的逻辑) -stream 流模式 - 在不整理输入的情况下详细描述 -ss, -scan-strategy value 扫描时使用的策略(auto/host-spray/template-spray) (默认 auto) -irt, -input-read-timeout duration 输入读取超时时间(默认:3分钟) -nh, -no-httpx 禁用对非URL输入进行httpx探测 -no-stdin 禁用标准输入 无界面浏览器: -headless 启用需要无界面浏览器的模板 -page-timeout int 在无界面下超时秒数(默认:20) -sb, -show-brower 在无界面浏览器运行模板时,显示浏览器 -ho, -headless-options string[] 使用附加选项启动无界面浏览器 -sc, -system-chrome 不使用Nuclei自带的浏览器,使用本地浏览器 -cdpe, -cdp-endpoint string 通过Chrome DevTools Protocol (CDP)端点使用远程浏览器 -lha, -list-headless-action 列出可用的无界面操作 调试: -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 显示版本信息 -hm, -hang-monitor 启用对nuclei挂起协程的监控 -v, -verbose 显示详细信息 -profile-mem string 将Nuclei的内存转储成文件 -vv 显示额外的详细信息 -svd, -show-var-dump 显示用于调试的变量输出 -ep, -enable-pprof 启用pprof调试服务器 -tv, -templates-version 显示已安装的模板版本 -hc, -health-check 运行诊断检查 升级: -up, -update 更新Nuclei到最新版本 -ut, -update-templates 更新Nuclei模板到最新版 -ud, -update-template-dir string 指定模板目录 -duc, -disable-update-check 禁用nuclei程序与模板更新 统计: -stats 显示正在扫描的统计信息 -sj, -stats-json 将统计信息以JSONL格式输出到文件 -si, -stats-inerval int 显示统计信息更新的间隔秒数(默认:5) -mp, -metrics-port int 更改metrics服务的端口(默认:9092) 云服务: -auth 配置projectdiscovery云服务(pdcp)API密钥 -cup, -cloud-upload 将扫描结果上传到pdcp仪表板 -sid, -scan-id string 将扫描结果上传到指定的扫描ID 例子: 扫描一个单独的URL: $ nuclei -target example.com 对URL运行指定的模板: $ nuclei -target example.com -t http/cves/ -t ssl 扫描hosts.txt中的多个URL: $ nuclei -list hosts.txt 输出结果为JSON格式: $ 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 使用[社区提供的模板](https://github.com/projectdiscovery/nuclei-templates)扫描单个目标 ```sh nuclei -u https://example.com ``` 使用[社区提供的模板](https://github.com/projectdiscovery/nuclei-templates)扫描多个目标 ```sh nuclei -list urls.txt ``` Example of `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等 - 通过工作流和动态请求实现复杂的漏洞扫描 - 易于集成到CI/CD,旨在可以轻松的集成到周期扫描中,以主动检测漏洞的修复和重新出现

Learn More

**对于赏金猎人:** Nuclei允许您定制自己的测试方法,可以轻松的运行您的程序。此外Nuclei可以更容易的集成到您的漏洞扫描工作流中。 - 可以集成到其他工作流中 - 可以在几分钟处理上千台主机 - 使用YAML语法定制自动化测试 欢迎查看我们其他的开源项目,可能有适合您的赏金猎人工作流:[github.com/projectdiscovery](https://github.com/projectdiscovery),我们还使用[Chaos绘制了每日的DNS数据](https://chaos.projectdiscovery.io)。
**对于渗透测试:** Nuclei通过增加手动、自动的过程,极大地改变了安全评估的方式。一些公司已经在用Nuclei升级他们的手动测试步骤,可以使用Nulcei对数千台主机使用同样的流程自动化测试。 渗透测试员可以使用公共模板或者自定义模板来更快的完成渗透测试,特别是漏洞验证时,可以轻松的验证漏洞是否修复。 - 轻松根据您的要求创建标准清单(例如:OWASP TOP 10) - 通过[FUZZ](https://nuclei.projectdiscovery.io/templating-guide/protocols/http-fuzzing/)和[工作流](https://nuclei.projectdiscovery.io/templating-guide/workflows/)等功能,可以使用Nuclei完成复杂的手动步骤和重复性渗透测试 - 只需要重新运行Nuclei即可验证漏洞修复情况
# 对于开发和组织 Nuclei构建很简单,通过数百名安全研究员的社区模板,Nuclei可以随时扫描来了解安全威胁。Nuclei通常用来用于复测,以确定漏洞是否被修复。 - **CI/CD:** 工程师已经支持了CI/CD,可以通过Nuclei使用定制模板来监控模拟环境和生产环境 - **周期性扫描:** 使用Nuclei创建新发现的漏洞模板,通过Nuclei可以周期性扫描消除漏洞 我们有个[讨论组](https://github.com/projectdiscovery/nuclei-templates/discussions/693),黑客提交自己的模板后可以获得赏金,这可以减少资产的漏洞,并且减少重复。如果你想实行该计划,可以[联系我](mailto:contact@projectdiscovery.io)。我们非常乐意提供帮助,或者在[讨论组](https://github.com/projectdiscovery/nuclei-templates/discussions/693)中发布相关信息。

regression-cycle-with-nuclei

Learn More

### 将nuclei加入您的代码 有关使用Nuclei作为Library/SDK的完整指南,请访问[godoc](https://pkg.go.dev/github.com/projectdiscovery/nuclei/v3/lib#section-readme) ### 资源 - [使用PinkDraconian发现Nuclei的BUG (Robbe Van Roey)](https://www.youtube.com/watch?v=ewP0xVPW-Pk) 作者:[@PinkDraconian](https://twitter.com/PinkDraconian) - [Nuclei: 强而有力的扫描器](https://bishopfox.com/blog/nuclei-vulnerability-scan) 作者:Bishopfox - [WAF有效性检查](https://www.fastly.com/blog/the-waf-efficacy-framework-measuring-the-effectiveness-of-your-waf) 作者:Fastly - [在CI/CD中使用Nuclei实时扫描网页应用](https://blog.escape.tech/devsecops-part-iii-scanning-live-web-applications/) 作者:[@TristanKalos](https://twitter.com/TristanKalos) - [使用Nuclei扫描](https://blog.projectdiscovery.io/community-powered-scanning-with-nuclei/) - [Nuclei Unleashed - 快速编写复杂漏洞](https://blog.projectdiscovery.io/nuclei-unleashed-quickly-write-complex-exploits/) - [Nuclei - FUZZ一切](https://blog.projectdiscovery.io/nuclei-fuzz-all-the-things/) - [Nuclei + Interactsh Integration,用于自动化OOB测试](https://blog.projectdiscovery.io/nuclei-interactsh-integration/) - [武器化Nuclei](https://medium.com/@dwisiswant0/weaponizes-nuclei-workflows-to-pwn-all-the-things-cd01223feb77) 作者:[@dwisiswant0](https://github.com/dwisiswant0) - [如何使用Nuclei连续扫描?](https://medium.com/@dwisiswant0/how-to-scan-continuously-with-nuclei-fcb7e9d8b8b9) 作者:[@dwisiswant0](https://github.com/dwisiswant0) - [自动化攻击](https://dhiyaneshgeek.github.io/web/security/2021/07/19/hack-with-automation/) 作者:[@DhiyaneshGeek](https://github.com/DhiyaneshGeek) ### 致谢 感谢所有[社区贡献者提供的PR](https://github.com/projectdiscovery/nuclei/graphs/contributors),并不断更新此项目:heart: 如果你有想法或某种改进,欢迎你参与该项目,随时发送你的PR。

另外您可以了解其他类似的开源项目: [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) ### 许可证 Nuclei使用[MIT许可证](https://github.com/projectdiscovery/nuclei/blob/main/LICENSE.md)

Join Discord Check Nuclei Documentation

================================================ FILE: README_ES.md ================================================


Nuclei

Escáner de vulnerabilidades rápido y personalizable basado en un sencillo DSL basado en YAML.

Cómo funcionaInstalaciónDocumentaciónCréditosPreguntas FrecuentesÚnete a Discord

English中文KoreanIndonesiaSpanishPortuguese

--- 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

nuclei-flow

| :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)

Join Discord Check Nuclei Documentation

================================================ FILE: README_ID.md ================================================


Nuclei

Pemindai kerentanan yang cepat dan dapat disesuaikan berdasarkan DSL berbasis YAML sederhana.

Cara KerjaInstalasiUntuk Teknisi KeamananUntuk PengembangDokumentasiKreditTanya JawabGabung Discord

English中文KoreanIndonesiaSpanishPortuguese

--- 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

nuclei-flow

# 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.

Pelajari Selengkapnya

**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.

Siklus Regresi Berkelanjutan dengan Nuclei

Pelajari Selengkapnya

### 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)

Join Discord Cek Dokumentasi Nuclei

================================================ FILE: README_JP.md ================================================


Nuclei

シンプルなYAMLベースのDSLに基づいた高速でカスタマイズ可能な脆弱性スキャナー

動作原理インストールドキュメントクレジットFAQDiscordに参加

英語中国語韓国語インドネシア語スペイン語ポルトガル語

--- Nucleiは、テンプレートに基づいてターゲット間でリクエストを送信するために使用され、偽陽性がゼロであり、多数のホストで高速なスキャンを提供します。Nucleiは、TCP、DNS、HTTP、SSL、ファイル、Whois、Websocket、Headless、Codeなど、さまざまなプロトコルのスキャンを提供します。強力で柔軟なテンプレートを使用して、Nucleiはすべての種類のセキュリティチェックをモデル化するために使用できます。 **300人以上の** セキュリティ研究者およびエンジニアが提供するさまざまなタイプの脆弱性テンプレートを収容する[専用リポジトリ](https://github.com/projectdiscovery/nuclei-templates)を持っています。 ## 動作原理

nuclei-flow

| :exclamation: **免責事項** | |---------------------------------| | **このプロジェクトは積極的に開発されています**。リリースによって重大な変更が発生することがあります。更新する前にリリースの変更ログを確認してください。 | | このプロジェクトは主にスタンドアロンのCLIツールとして使用されることを目的として構築されました。**Nucleiをサービスとして実行すると、セキュリティリスクが生じる可能性があります。**注意して使用し、追加のセキュリティ対策を講じることをお勧めします。 | # Nucleiのインストール Nucleiを正常にインストールするには、**go1.24.2**が必要です。以下のコマンドを実行して最新バージョンをインストールしてください - ```sh go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest ```
Brew ```sh brew install nuclei ```
Docker ```sh docker pull projectdiscovery/nuclei:latest ```
**より多くのインストール方法は[こちら](https://docs.projectdiscovery.io/tools/nuclei/install)で見つけることができます。**
### 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テンプレートを更新することができます。Nucleiの[テンプレートガイド](https://docs.projectdiscovery.io/templates/)に従って、個々のワークフローとニーズに合わせた独自のチェックを作成することができます。 YAML DSLの構文リファレンスは[こちら](SYNTAX-REFERENCE.md)で確認できます。
### 使用方法 ```sh nuclei -h ``` これにより、ツールのヘルプが表示されます。ここには、サポートされているすべてのスイッチがあります。 ```console Nucleiは、広範な設定可能性、大規模な拡張性、および使いやすさに焦点を当てた、 高速でテンプレートベースの脆弱性スキャナーです。 使用法: ./nuclei [flags] フラグ: ターゲット: -u, -target string[] スキャンする対象のURL/ホスト -l, -list string スキャンする対象のURL/ホストのリストが含まれているファイルへのパス(1行に1つ) -resume string 指定されたファイルからスキャンを再開し、指定されたファイルに保存(クラスタリングは無効になります) -sa, -scan-all-ips DNSレコードに関連付けられているすべてのIPをスキャン -iv, -ip-version string[] ホスト名のスキャンするIPバージョン(4,6)-(デフォルトは4) テンプレート: -nt, -new-templates 最新のnuclei-templatesリリースに追加された新しいテンプレートのみを実行 -ntv, -new-templates-version string[] 特定のバージョンに追加された新しいテンプレートを実行 -as, -automatic-scan wappalyzer技術検出をタグマッピングに使用した自動Webスキャン -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 コードプロトコルベースのテンプレートのロードを有効にする フィルタリング: -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[] 式条件に基づいて実行するテンプレート 出力: -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)形式で結果をエクスポートするファイル 設定: -config string Nucleiの設定ファイルへのパス -fr, -follow-redirects HTTPテンプレートのリダイレクトをフォローすることを有効にする -fhr, -follow-host-redirects ================================================ FILE: README_KR.md ================================================


Nuclei

DSL 기반의 간단한 YAML을 기초로한 빠른 맞춤형 취약점 스캐너

작동 방식설치보안 엔지니어를 위한개발자를 위한문서CreditsFAQs Discord 참가

English中文한국어스페인어포르투갈어

--- Nuclei는 템플릿을 기반으로 대상 간에 요청을 보내기 위해 사용되며 긍정 오류(false positives)가 0이고 다수의 호스트에서 빠른 스캔을 제공합니다. Nuclei는 TCP, DNS, HTTP, SSL, File, Whois, Websocket, Headless 등을 포함한 다양한 프로토콜의 스캔을 제공합니다. 강력하고 유연한 템플릿을 통해 Nuclei는 모든 종류의 보안 검사를 모델링 할 수 있습니다. **300명 이상의** 보안 연구원과 엔지니어가 제공한 다양한 유형의 취약점 템플릿을 보관하는 [전용 저장소](https://github.com/projectdiscovery/nuclei-templates)를 보유하고 있습니다. ## 작동 방식

nuclei-flow

# 설치 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에 쉽게 통합할 수 있으며, 회귀 주기에 쉽게 통합되어 취약점의 수정 및 재출현을 능동적으로 확인할 수 있도록 설계됨.

Learn More

**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)에 게시할 수도 있습니다.

regression-cycle-with-nuclei

Learn More

### 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)

Join Discord Check Nuclei Documentation

================================================ FILE: README_PT-BR.md ================================================


Nuclei

Scanner de vulnerabilidades rápido e personalizável baseado em uma DSL simples baseada em YAML.

Como funcionaInstalaçãoDocumentaçãoCréditosPerguntas FrequentesJunte-se ao Discord

English中文KoreanIndonesiaSpanishPortuguese

--- 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

nuclei-flow

| :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)

Join Discord Check Nuclei Documentation

================================================ FILE: README_TR.md ================================================ ![nuclei](/static/nuclei-cover-image.png)
`English``中文``Korean``Indonesia``Spanish``日本語``Portuguese``Türkçe`

     



**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.

## İçindekiler - [**`Başlarken`**](#başlarken) - [_`1. Nuclei CLI`_](#1-nuclei-cli) - [_`2. Pro ve Kurumsal Sürümler`_](#2-pro-ve-kurumsal-sürümler) - [**`Dokümantasyon`**](#dokümantasyon) - [_`Komut Satırı Bayrakları`_](#komut-satırı-bayrakları) - [_`Tek hedef tarama`_](#tek-hedef-tarama) - [_`Çoklu hedef tarama`_](#çoklu-hedef-tarama) - [_`Ağ taraması`_](#ağ-taraması) - [_`Özel şablonunuzla tarama`_](#özel-şablonunuzla-tarama) - [_`Nuclei'yi ProjectDiscovery'ye Bağlayın`_](#nucleiyi-projectdiscoveryye-bağlayın) - [**`Nuclei Şablonları, Topluluk ve Ödüller`**](#nuclei-şablonları-topluluk-ve-ödüller-) 💎 - [**`Misyonumuz`**](#misyonumuz) - [**`Katkıda Bulunanlar`**](#katkıda-bulunanlar) ❤ - [**`Lisans`**](#lisans)

## 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).

TeamCity yanlış yapılandırmasını tespit etmek için Nuclei şablon örneği

#### Ö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.
id string
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 model.Info
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 ```

flow string
description: | Flow contains the execution flow for the template. examples: - flow: | for region in regions { http(0) } for vpc in vpcs { http(1) }

requests []http.Request
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), ''
http []http.Request
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 []dns.Request
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 []file.Request
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 []network.Request
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.

headless []headless.Request
Headless contains the headless request to make in the template.

ssl []ssl.Request
SSL contains the SSL request to make in the template.

websocket []websocket.Request
Websocket contains the Websocket request to make in the template.

whois []whois.Request
WHOIS contains the WHOIS request to make in the template.

code []code.Request
Code contains code snippets.

javascript []javascript.Request
Javascript contains the javascript request to make in the template.

self-contained bool
Self Contained marks Requests for the template as self-contained

stop-at-first-match bool
Stop execution once first match is found

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 variables.Variable
Variables contains any variables for the current request.

constants map[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 ```
name string
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 ```

Author of the template. Multiple values can also be specified separated by commas. Examples: ```yaml author: ```

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 ```

description string
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 ```

impact string
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 ```

severity severity.Holder
Severity of the template.

metadata map[string]interface{}
Metadata of the template. Examples: ```yaml metadata: customField1: customValue1 ```

classification model.Classification
Classification contains classification information about the template.

remediation string
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

## model.Classification Appears in: - model.Info.classification
CVE ID for the template Examples: ```yaml cve-id: CVE-2020-14420 ```

CWE ID for the template. Examples: ```yaml cwe-id: CWE-22 ```

cvss-metrics string
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-score float64
CVSS Score for the template. Examples: ```yaml cvss-score: "9.8" ```

epss-score float64
EPSS Score for the template. Examples: ```yaml epss-score: "0.42509" ```

epss-percentile float64
EPSS Percentile for the template. Examples: ```yaml epss-percentile: "0.42509" ```

cpe string
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 ```

id string
ID is the optional id of the request

name string
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

Method is the HTTP Request Method.

body string
Body is an optional parameter which contains HTTP Request body. Examples: ```yaml # Same Body for a Login POST request body: username=test&password=test ```

payloads map[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.

headers map[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_count int
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-redirects int
MaxRedirects is the maximum number of redirects that should be followed. Examples: ```yaml # Follow up to 5 redirects max-redirects: 5 ```

pipeline-concurrent-connections int
PipelineConcurrentConnections is number of connections to create during pipelining. Examples: ```yaml # Create 40 concurrent connections pipeline-concurrent-connections: 40 ```

pipeline-requests-per-connection int
PipelineRequestsPerConnection is number of requests to send per connection when pipelining. Examples: ```yaml # Send 100 requests per pipeline connection pipeline-requests-per-connection: 100 ```

threads int
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-size int
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 ```

fuzzing []fuzz.Rule
Fuzzing describes schema to fuzz http requests

Analyzer is an analyzer to use for matching the response.

self-contained bool
SelfContained specifies if the request is self-contained.

Signature is the request signature method Valid values: - AWS

skip-secret-file bool
SkipSecretFile skips the authentication or authorization configured in the secret file.

cookie-reuse bool
CookieReuse is an optional setting that enables cookie reuse for all requests defined in raw section.

disable-cookie bool
DisableCookie is an optional setting that disables cookie reuse

read-all bool
Enables force reading of the entire raw unsafe request body ignoring any specified content length headers.

redirects bool
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-redirects bool
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.

pipeline bool
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.

unsafe bool
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.

race bool
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-condition bool
ReqCondition automatically assigns numbers to requests and preserves their history. This allows matching on them later for multi-request conditions.

stop-at-first-match bool
StopAtFirstMatch stops the execution of the requests and template as soon as a match is found.

skip-variables-check bool
SkipVariablesCheck skips the check for unresolved variables in request

iterate-all bool
IterateAll iterates all the values extracted from internal extractors

digest-username string
DigestAuthUsername specifies the username for digest authentication

digest-password string
DigestAuthPassword specifies the password for digest authentication

disable-path-automerge bool
DisablePathAutomerge disables merging target url path with raw request path

pre-condition []matchers.Matcher
Fuzz PreCondition is matcher-like field to check if fuzzing should be performed on this request or not

pre-condition-operator string
FuzzPreConditionOperator is the operator between multiple PreConditions for fuzzing Default is OR

global-matchers bool
GlobalMatchers marks matchers as static and applies globally to all result events from other templates

## generators.AttackTypeHolder AttackTypeHolder is used to hold internal type of the protocol Appears in: - http.Request.attack - dns.Request.attack - network.Request.attack - headless.Request.attack - websocket.Request.attack - javascript.Request.attack
AttackType
Enum Values: - batteringram - pitchfork - clusterbomb

## 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
type string
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

part string
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

mode string
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-regex string
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
name string
Name is the name of the analyzer to use Valid values: - time_delay

parameters map[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
Type is the type of the matcher.

condition string
Condition is the optional condition between two matcher variables. By default, the condition is assumed to be OR. Valid values: - and - or

part string
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 ```

negative bool
Negative specifies if the match should be reversed It will only match if the condition is not true.

name string
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"] ```

encoding string
Encoding specifies the encoding for the words field if any. Valid values: - hex

case-insensitive bool
CaseInsensitive enables case-insensitive matches. Default is false. Valid values: - false - true

match-all bool
MatchAll enables matching for all matcher values. Default is false. Valid values: - false - true

internal bool
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
id string
ID is the optional id of the request

name string
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}}' ```

RequestType is the type of DNS request to make.

class string
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

retries int
Retries is the number of retries for the DNS request Examples: ```yaml # Use a retry of 3 to 5 generally retries: 5 ```

trace bool
Trace performs a trace operation for the target.

trace-max-recursion int
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.

payloads map[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.

threads int
Threads to use when sending iterating over payloads Examples: ```yaml # Send requests using 10 concurrent threads threads: 10 ```

recursion dns.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 ```

id string
ID is the optional id of the request

max-size string
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 ```

archive bool
elaborates archives

mime-type bool
enables mime types check

no-recursive bool
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
id string
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.

payloads map[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.

threads int
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 ```

inputs []network.Input
Inputs contains inputs for the network socket

port string
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-ports string
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-size int
ReadSize is the size of response to read at the end Default value for read-size is 1024. Examples: ```yaml read-size: 2048 ```

read-all bool
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-match bool
StopAtFirstMatch stops the execution of the requests and template as soon as a match is found.

## network.Input Appears in: - network.Request.inputs
data string
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

read int
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 ```

name string
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)
id string
ID is the optional id of the request

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.

payloads map[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.

steps []engine.Action
Steps is the list of actions to run for headless request

descriptions: | User-Agent is the type of user-agent to use for the request.

custom_user_agent string
description: | If UserAgent is set to custom, customUserAgent is the custom user-agent to use for the request.

stop-at-first-match bool
StopAtFirstMatch stops the execution of the requests and template as soon as a match is found.

fuzzing []fuzz.Rule
Fuzzing describes schema to fuzz headless requests

cookie-reuse bool
CookieReuse is an optional setting that enables cookie reuse

disable-cookie bool
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
args map[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/).

name string
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).

description string
Description is the optional description of the headless action

Action is the type of the action to perform.

## ActionTypeHolder ActionTypeHolder is used to hold internal type of the action Appears in: - engine.Action.action
ActionType
Enum Values: - navigate - script - click - rightclick - text - screenshot - time - select - files - waitdom - waitfcp - waitfmp - waitidle - waitload - waitstable - getresource - extract - setmethod - addheader - setheader - deleteheader - setbody - waitevent - dialog - keyboard - debug - sleep - waitvisible

## 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
id string
ID is the optional id of the request

address string
Address contains address for the request

min_version string
Minimum tls version - auto if not specified. Valid values: - sslv3 - tls10 - tls11 - tls12 - tls13

max_version string
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_mode string
description: | Tls Scan Mode - auto if not specified values: - "ctls" - "ztls" - "auto" - "openssl" # reverts to "auto" is openssl is not installed

tls_version_enum bool
TLS Versions Enum - false if not specified Enumerates supported TLS versions

tls_cipher_enum bool
TLS Ciphers Enum - false if not specified Enumerates supported TLS ciphers

tls_cipher_types []string
description: | TLS Cipher types to enumerate values: - "insecure" (default) - "weak" - "secure" - "all"

## 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
id string
ID is the optional id of the request

address string
Address contains address for the request

inputs []websocket.Input
Inputs contains inputs for the websocket protocol

headers map[string]string
Headers contains headers for the request.

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.

payloads map[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.

## websocket.Input Appears in: - websocket.Request.inputs
data string
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') ```

name string
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
id string
ID is the optional id of the request

query string
Query contains query for the request

server string
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
id string
ID is the optional id of the request

engine []string
Engine type

pre-condition string
PreCondition is a condition which is evaluated before sending the request.

args []string
Engine Arguments

pattern string
Pattern preferred for file name

source string
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
id string
description: | ID is request id in that protocol

init string
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-condition string
PreCondition is a condition which is evaluated before sending the request.

args map[string]interface{}
Args contains the arguments to pass to the javascript code.

code string
Code contains code to execute for the javascript request.

stop-at-first-match bool
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.

threads int
Payload concurrency i.e threads for sending requests. Examples: ```yaml # Send requests using 10 concurrent threads threads: 10 ```

payloads map[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 7 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-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!

Fruit Overlays

$92.79

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.

View cart
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: integration_tests/generic/auth/certificate/assets/client.crt ================================================ -----BEGIN CERTIFICATE----- MIIDEzCCAfsCFBDZsFEIb3QwKLzXLoqR/oaDwakYMA0GCSqGSIb3DQEBCwUAMEUx CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl cm5ldCBXaWRnaXRzIFB0eSBMdGQwIBcNMjMwNzI4MTAwODIyWhgPMzAwMzA5Mjkx MDA4MjJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUA A4IBDwAwggEKAoIBAQCp8/P9JAyE90ZrE1LZcJ/B24f79aazY8S/eeRRZsTvUP73 NrOznv1zhvJ9TKHUNcOouZ/NPQanNOiqkoigQwP7L2FA2bPOPAPIWBPWGdjSkeyZ 8MYbA7Or+16k2ZYvKsCarG/PgGeL0UFLe6INvZRMnk1s+iF0upcHv5BhjIfBwzh4 o2pLY1d9bbnEsuSNagOzIkQS3mI22d1YbJKxXP0m+tBk1gTqhUhwEAXNaIBCRscs xyv9pW7ZSjPabf/L0Md2yMcVs0+oK6rkQbAWrTTjN1lJ603BHh+keIDMwQnbMB0U AStJdyQpwa7hZ+5767+GxR7n85Twe1rSexmTl9/fAgMBAAEwDQYJKoZIhvcNAQEL BQADggEBAIOQE2DWqwse0srtG+7IS0EO3iP27lRKxd387wY1xq00o3depKReVpYm R8sZM1meumniH1QKoVFJpBHYoPzQMi8vMmI9AV3KWNFcCyf+jwc69Qab2erDNVsw 5mCCGXkrzLbCzmbPFZoyvMmBlsQSmOjwyGGIeXwfqKv/TPwOzKfSM/KkQmgRyUro GDT+TI5VhgvQyNLmkWNRhnI30DnlsQ1Bc0MEQ1hismOYxD4mCqufCOS3BmakDRNK QBz0xl0i5Dbf+e4o3rEaCGW/rzKkL1mm1TXqpDEy3UAwj+jIOZu5yByw5djfgSIX OEVuqklUASYAPeVdSyf/VAflLV9nGKI= -----END CERTIFICATE----- ================================================ FILE: integration_tests/generic/auth/certificate/assets/client.key ================================================ -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCp8/P9JAyE90Zr E1LZcJ/B24f79aazY8S/eeRRZsTvUP73NrOznv1zhvJ9TKHUNcOouZ/NPQanNOiq koigQwP7L2FA2bPOPAPIWBPWGdjSkeyZ8MYbA7Or+16k2ZYvKsCarG/PgGeL0UFL e6INvZRMnk1s+iF0upcHv5BhjIfBwzh4o2pLY1d9bbnEsuSNagOzIkQS3mI22d1Y bJKxXP0m+tBk1gTqhUhwEAXNaIBCRscsxyv9pW7ZSjPabf/L0Md2yMcVs0+oK6rk QbAWrTTjN1lJ603BHh+keIDMwQnbMB0UAStJdyQpwa7hZ+5767+GxR7n85Twe1rS exmTl9/fAgMBAAECggEAPZzaVGhQPZgjqEfeHkQtNqtuthJNd/Vwa3Y2JqiaNqRn epoTNcgq3EoM+Q3iETvYjf+VhmNcWRveSZBMBcWl2NdJa6hA/kBVorkDn/fI2jXa z8gxGbQS3AOKQTs8ribSooBnHJPRdifLgyD0FAUpkUlGin53yIionj99iU/YG48g 4dwkBIFHRcxertQyhu3YQ+XleJ35n7mNFwGzC7curRBPUHMImPASzVYQhVdN8OBt TZEoJw+2lmH4fIJYult27hcl2/pLs1FPvQFSLTIoqzaEzRhKdANkclmnhJjCBXzB 7RLUpKOv1Q28u+P5KH1nFBV/UuuxXrjFt4jhRdji2QKBgQDvv+W0GJWX5POfyRHT pAROclgVPEgS5vXQIelMdR76a72L/4Vm2/xeolWW1h5qmJF479V/+P+ppxb1IrUy 6+yGtkMiQE4CizhFGWivfXUTPZbdeeSpHMUl9tRZdBZWi3aXzJ/8DfCzD+ZVS4Vx +y62V4ymQyAqBWv2ast/ElEbowKBgQC1ePQgR+MNfz7/BaatCcLPwFG/kkqPVuzH //6HB+gAYTyuZsbLrYhCQsbsTjvQz0ExmTnNSeCjHTntQ+pZ8Tnuet9bHxKTRbvG 9Ol/J402EnY2tO/b8jKXHNNyLNImvWpJ4PpaLRKQVxLPei+JcEHyz4MVMrhIjX1b EhhDCZ6ulQKBgEUy+jX1MphY+QiRnJedq7CIyGu4roTmLOUaJKBw3bQiDN+vrO13 aWxXJqUWwEi8KKDjeJgrYn+xPqsajXpZJjfru4zTSrDpRiCLqO+eIoCfMkBSwnEd YLoIeFopa5knP9+orDSwQV0tpanQ1n+DpIP02R/UGCCI2BST1pCi1M5JAoGAC/+E PIIkO+c21gucmoIztCKmBQF6FoILw6lkPa9DIotLRMicyiieAquBlWwSvlqFl+7m iHEi/gXXp50+6FVvnBnZnJ+wTbZllODqczK9Pl74G+PYm/UmbSFFxZ27Az6wwVOz mbSzLoHjR35vmCmo4pHfu84PqxRXvmay3fPL3wUCgYEA0yZcvQqiTs+f4S/mZbhp fyPgurmowXUNgdijyeFoH+DMkwdWUJeBrinelQaXADUSXkKiA8gaoNGOIkDIBcve gdUhrY204MeoTYxnIb1dw6/KReya4YdRSMlYiX2hYEURIxdaJV5HcwW5ySMOzP87 t2+YVr4faAv4AS8k21pBGrc= -----END PRIVATE KEY----- ================================================ FILE: integration_tests/generic/auth/certificate/assets/server.crt ================================================ -----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----- ================================================ FILE: integration_tests/generic/auth/certificate/http-get.yaml ================================================ id: basic-get-with-cert info: name: Basic GET with Cert author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" matchers: - type: word words: - "Hello" ================================================ FILE: integration_tests/library/test.json ================================================ { "id": "go-integration-test", "info": { "name": "Basic Go Integration Test", "author": "pdteam", "severity": "info" }, "requests": [ { "method": "GET", "path": [ "{{BaseURL}}" ], "headers": { "test": "nuclei" }, "matchers": [ { "type": "word", "words": [ "This is test headers matcher text" ] } ] } ] } ================================================ FILE: integration_tests/library/test.yaml ================================================ id: go-integration-test info: name: Basic Go Integration Test author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" headers: test: nuclei matchers: - type: word words: - "This is test headers matcher text" ================================================ FILE: integration_tests/loader/basic.yaml ================================================ id: workflow-example info: name: Test Workflow Template author: pdteam severity: info workflows: - template: workflow/match-1.yaml - template: workflow/match-2.yaml ================================================ FILE: integration_tests/loader/condition-matched.yaml ================================================ id: condition-matched-workflow info: name: Condition Matched Workflow author: pdteam severity: info workflows: - template: workflow/match-1.yaml subtemplates: - template: workflow/match-2.yaml ================================================ FILE: integration_tests/loader/excluded-template.yaml ================================================ id: excluded-template info: name: Basic Excluded Template author: pdteam severity: info tags: fuzz http: - method: GET path: - "{{BaseURL}}" matchers: - type: word words: - "This is test matcher text" ================================================ FILE: integration_tests/loader/get-headers.yaml ================================================ id: basic-get-headers info: name: Basic GET Headers Request author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" headers: test: nuclei matchers: - type: word words: - "This is test headers matcher text" ================================================ FILE: integration_tests/loader/get.yaml ================================================ id: basic-get info: name: Basic GET Request author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" matchers: - type: word words: - "This is test matcher text" ================================================ FILE: integration_tests/loader/template-list.yaml ================================================ loader/get.yaml loader/get-headers.yaml ================================================ FILE: integration_tests/loader/workflow-list.yaml ================================================ loader/basic.yaml loader/condition-matched.yaml ================================================ FILE: integration_tests/profile-loader/basic.yml ================================================ tags: - kev ================================================ FILE: integration_tests/protocols/code/pre-condition.yaml ================================================ id: pre-condition-code info: name: example code template author: pdteam severity: info self-contained: true variables: OAST: "{{interactsh-url}}" code: - pre-condition: IsLinux() engine: - sh - bash source: | echo "$OAST" | base64 matchers: - type: dsl dsl: - true # digest: 490a00463044022048c083c338c0195f5012122d40c1009d2e2030c583e56558e0d6249a41e6f3f4022070656adf748f4874018d7a01fce116db10a3acd1f9b03e12a83906fb625b5c50:4a3eb6b4988d95847d4203be25ed1d46 ================================================ FILE: integration_tests/protocols/code/ps1-snippet.yaml ================================================ id: ps1-code-snippet info: name: ps1-code-snippet author: pdteam severity: info tags: code description: | ps1-code-snippet code: - engine: - powershell - powershell.exe args: - -ExecutionPolicy - Bypass - -File pattern: "*.ps1" source: | $stdin = [Console]::In $line = $stdin.ReadLine() Write-Host "hello from $line" matchers: - type: word words: - "hello from input" ================================================ FILE: integration_tests/protocols/code/pwsh-echo.yaml ================================================ id: pw-echo info: name: PowerShell Echo Test author: pdteam severity: info description: Tests PowerShell execution with an echo-like operation. tags: test,powershell,echo self-contained: true code: - engine: - pwsh - powershell - powershell.exe args: - -ExecutionPolicy - Bypass pattern: "*.ps1" source: | Write-Output "test-output-success" matchers: - type: word words: - "test-output-success" ================================================ FILE: integration_tests/protocols/code/py-env-var.yaml ================================================ id: py-code-snippet info: name: py-code-snippet author: pdteam severity: info tags: code description: | py-code-snippet code: - engine: - py - python3 source: | import sys,os print("hello from " + sys.stdin.read() + " " + os.getenv('baz')) matchers: - type: word words: - "hello from input baz" # digest: 4b0a00483046022100cbbdb7214f669d111b671d271110872dc8af2ab41cf5c312b6e4f64126f55337022100a60547952a0c2bea58388f2d2effe8ad73cd6b6fc92e73eb3c8f88beab6105ec:4a3eb6b4988d95847d4203be25ed1d46 ================================================ FILE: integration_tests/protocols/code/py-file.yaml ================================================ id: py-file info: name: py-file author: pdteam severity: info tags: code description: | py-file code: - engine: - py - python3 source: protocols/code/pyfile.py matchers: - type: word words: - "hello from input" # digest: 4a0a00473045022032b81e8bb7475abf27639b0ced71355497166d664698021f26498e7031d62a23022100e99ccde578bfc0b658f16427ae9a3d18922849d3ba3e022032ea0d2a8e77fadb:4a3eb6b4988d95847d4203be25ed1d46 ================================================ FILE: integration_tests/protocols/code/py-interactsh.yaml ================================================ id: testcode info: name: testcode author: testcode severity: info tags: code description: | testcode variables: i: "{{interactsh-url}}" code: - engine: - py - python3 # Simulate interactsh interaction source: | import os from urllib.request import urlopen urlopen("http://" + os.getenv('i')) matchers: - type: word part: interactsh_protocol words: - "http" # digest: 4a0a0047304502201a5dd0eddfab4f02588a5a8ac1947a5fa41fed80b59d698ad5cc00456296efb6022100fe6e608e38c060964800f5f863a7cdc93f686f2d0f4b52854f73948b808b4511:4a3eb6b4988d95847d4203be25ed1d46 ================================================ FILE: integration_tests/protocols/code/py-nosig.yaml ================================================ id: py-nosig info: name: py-nosig author: pdteam severity: info tags: code description: | Python code without signature code: - engine: - py - python3 source: | print("py unsigned code") matchers: - type: word words: - "py unsigned code" ================================================ FILE: integration_tests/protocols/code/py-snippet.yaml ================================================ id: py-code-snippet info: name: py-code-snippet author: pdteam severity: info tags: code description: | py-code-snippet code: - engine: - py - python3 - python source: | import sys print("hello from " + sys.stdin.read()) matchers: - type: word words: - "hello from input" # digest: 4b0a00483046022100ced1702728cc68f906c4c7d2c4d05ed071bfabee1e36eec7ebecbeca795a170c022100d20fd41796f130a8f9c4972fee85386d67d61eb5fc1119b1afe2a851eb2f3e65:4a3eb6b4988d95847d4203be25ed1d46 ================================================ FILE: integration_tests/protocols/code/py-virtual.yaml ================================================ id: signed-python-code-in-virtual-env info: name: signed-python-code-in-virtual-env author: pdteam severity: info tags: code description: | signed python code in virtual environment code: - engine: - sh - bash sandbox: working-dir: /tmp image: python:3.14 source: | #!/usr/bin/env python3 import sys print("hello from python virtual code") matchers: - type: word words: - "hello from python virtual code" ================================================ FILE: integration_tests/protocols/code/pyfile.py ================================================ import sys print("hello from " + sys.stdin.read()) ================================================ FILE: integration_tests/protocols/code/sh-virtual.yaml ================================================ id: signed-code-in-virtual-env info: name: signed-code-in-virtual-env author: pdteam severity: info tags: code description: | signed code in virtual environment code: - engine: - sh - bash sandbox: working-dir: /tmp image: ubuntu:latest source: | echo "hello from sh virtual code" matchers: - type: word words: - "hello from sh virtual code" # digest: 4a0a00473045022100a2a71c423b72e600ac2d78209482b48591924157d49e2dfb7767445b0073e92b022009a6365e84247e268c7ffcb2f9ed424529dada6f087d5f21400ab0b2a822ca56:4a3eb6b4988d95847d4203be25ed1d46 ================================================ FILE: integration_tests/protocols/code/unsigned.yaml ================================================ id: unsigned-code-snippet info: name: unsigned-code-snippet author: pdteam severity: info tags: code description: | unsigned-code-snippet code: - engine: - py - python3 source: | print("unsigned code") matchers: - type: word words: - "unsigned code" ================================================ FILE: integration_tests/protocols/dns/a.yaml ================================================ id: dns-a-query-example info: name: Test DNS A Query Template author: pdteam severity: info dns: - name: "{{FQDN}}" type: A class: inet recursion: true retries: 3 matchers: - type: word words: - "1.1.1.1" ================================================ FILE: integration_tests/protocols/dns/aaaa.yaml ================================================ id: dns-aaaa-query-example info: name: Test DNS AAAA Query Template author: pdteam severity: info dns: - name: "{{FQDN}}" type: AAAA class: inet recursion: true retries: 3 matchers: - type: word words: - "2606:4700:4700::1001" ================================================ FILE: integration_tests/protocols/dns/caa.yaml ================================================ id: caa-fingerprinting info: name: CAA Fingerprint author: pdteam severity: info tags: dns,caa dns: - name: "{{FQDN}}" type: CAA matchers: - type: word words: - "IN\tCAA" extractors: - type: regex group: 1 regex: - "IN\tCAA\t(.+)" ================================================ FILE: integration_tests/protocols/dns/cname-fingerprint.yaml ================================================ id: cname-fingerprint info: name: CNAME Fingerprint author: pdteam severity: info tags: dns,cname dns: - name: "{{FQDN}}" type: NS matchers: - type: word words: - "IN\tCNAME" extractors: - type: regex group: 1 regex: - "IN\tCNAME\t(.+)" ================================================ FILE: integration_tests/protocols/dns/cname.yaml ================================================ id: dns-cname-query-example info: name: Test DNS CNAME Query Template author: pdteam severity: info dns: - name: "{{FQDN}}" type: CNAME class: inet recursion: true retries: 3 matchers: - type: word part: all words: - "CNAME" ================================================ FILE: integration_tests/protocols/dns/dsl-matcher-variable.yaml ================================================ id: dns-template info: name: basic dns template author: pdteam severity: info dns: - name: "{{FQDN}}" type: CNAME matchers: - type: dsl dsl: - "rcode == 0" extractors: - type: dsl dsl: - rcode - cname - a - aaaa ================================================ FILE: integration_tests/protocols/dns/ns.yaml ================================================ id: dns-ns-query-example info: name: Test DNS NS Query Template author: pdteam severity: info dns: - name: "{{FQDN}}" type: NS class: inet recursion: true retries: 3 matchers: - type: word part: all words: - "NS" ================================================ FILE: integration_tests/protocols/dns/payload.yaml ================================================ id: dns-attack info: name: basic dns template author: pdteam severity: info dns: - name: "{{subdomain_wordlist}}.{{FQDN}}" type: A attack: batteringram payloads: subdomain_wordlist: - one - docs - drive matchers: - type: word words: - "IN\tA" extractors: - type: regex group: 1 regex: - "IN\tA\t(.+)" ================================================ FILE: integration_tests/protocols/dns/ptr.yaml ================================================ id: ptr-fingerprint info: name: PTR Fingerprint author: pdteam severity: info tags: dns,ptr dns: - name: "{{FQDN}}" type: PTR matchers: - type: word words: - "IN\tPTR" extractors: - type: regex group: 1 regex: - "IN\tPTR\t(.+)" ================================================ FILE: integration_tests/protocols/dns/srv.yaml ================================================ id: dns-a-query-example info: name: Test DNS SRV Query Template author: pdteam severity: info dns: - name: "{{FQDN}}" type: SRV class: inet recursion: true retries: 3 matchers: - type: word part: all words: - "SRV" ================================================ FILE: integration_tests/protocols/dns/tlsa.yaml ================================================ id: tlsa-fingerprinting info: name: TLSA Fingerprint author: pdteam severity: info tags: dns,tlsa dns: - name: "{{FQDN}}" type: TLSA matchers: - type: word words: - "IN\tTLSA" extractors: - type: regex group: 1 regex: - "IN\tTLSA\t(.+)" ================================================ FILE: integration_tests/protocols/dns/txt.yaml ================================================ id: dns-txt-query-example info: name: Test DNS TXT Query Template author: pdteam severity: info dns: - name: "{{FQDN}}" type: TXT class: inet recursion: true retries: 3 matchers: - type: word part: all words: - "TXT" ================================================ FILE: integration_tests/protocols/dns/variables.yaml ================================================ id: variables-example info: name: Variables Example author: pdteam severity: info variables: a1: "IN" dns: - name: "{{FQDN}}" type: A class: inet recursion: true retries: 3 matchers: - type: word words: - "{{a1}}" ================================================ FILE: integration_tests/protocols/file/data/test1.txt ================================================ AAA BBB ================================================ FILE: integration_tests/protocols/file/data/test2.txt ================================================ CCC DDD ================================================ FILE: integration_tests/protocols/file/data/test3.txt ================================================ 11 EE 11 11 FF 11 ================================================ FILE: integration_tests/protocols/file/extract.yaml ================================================ id: file-extract info: name: File with Extractor author: pdteam severity: info tags: file file: - extensions: - all extractors: - type: regex regex: - "(?m)11\\s(EE|FF)\\s11" ================================================ FILE: integration_tests/protocols/file/matcher-with-and.yaml ================================================ id: file-matcher-with-and info: name: File Matcher With AND author: pdteam severity: info tags: file file: - extensions: - all matchers-condition: and matchers: - type: word words: - "CCC" - type: word words: - "DDD" ================================================ FILE: integration_tests/protocols/file/matcher-with-nested-and.yaml ================================================ id: file-matcher-with-nested-and info: name: File Matcher With nested AND author: pdteam severity: info tags: file file: - extensions: - all matchers: - type: word words: - "CCC" - "DDD" condition: and ================================================ FILE: integration_tests/protocols/file/matcher-with-or.yaml ================================================ id: file-matcher-with-or info: name: File Matcher With OR author: pdteam severity: info tags: file file: - extensions: - all matchers: - type: word words: - "AA" - type: word words: - "BB" ================================================ FILE: integration_tests/protocols/headless/file-upload-negative.yaml ================================================ id: file-upload # template for testing when file upload is disabled info: name: Basic File Upload author: pdteam severity: info headless: - steps: - action: navigate args: url: "{{BaseURL}}" - action: waitload - action: files args: by: xpath xpath: /html/body/form/input[1] value: headless/file-upload.yaml - action: sleep args: duration: 2 - action: click args: by: x xpath: /html/body/form/input[2] matchers: - type: word words: - "Basic File Upload" ================================================ FILE: integration_tests/protocols/headless/file-upload.yaml ================================================ id: file-upload info: name: Basic File Upload author: pdteam severity: info headless: - steps: - action: navigate args: url: "{{BaseURL}}" - action: waitload - action: files args: by: xpath xpath: /html/body/form/input[1] value: protocols/headless/file-upload.yaml - action: sleep args: duration: 2 - action: click args: by: x xpath: /html/body/form/input[2] matchers: - type: word words: - "Basic File Upload" ================================================ FILE: integration_tests/protocols/headless/headless-basic.yaml ================================================ id: headless-basic info: name: Headless Basic author: pdteam severity: info tags: headless headless: - steps: - action: navigate args: url: "{{BaseURL}}/" - action: waitload matchers: - type: word words: - "" ================================================ FILE: integration_tests/protocols/headless/headless-dsl.yaml ================================================ id: headless-dsl info: name: Headless DSL author: dwisiswant0 severity: info tags: headless headless: - steps: - action: navigate args: url: "{{BaseURL}}/?_={{urlencode(concat('foo', '-', 'bar'))}}" - action: waitload matchers: - type: word words: - "foo-bar" ================================================ FILE: integration_tests/protocols/headless/headless-extract-values.yaml ================================================ id: headless-extract-values info: name: Headless Extract Value author: pdteam severity: info tags: headless headless: - steps: - action: navigate args: url: "{{BaseURL}}" - action: waitload # From headless/extract-urls.yaml - action: script name: extract args: code: | () => '\n' + [...new Set(Array.from(document.querySelectorAll('[src], [href], [url], [action]')).map(i => i.src || i.href || i.url || i.action))].join('\r\n') + '\n' matchers: - type: word words: - "test.html" extractors: - type: kval part: extract kval: - extract ================================================ FILE: integration_tests/protocols/headless/headless-header-action.yaml ================================================ id: headless-header-action info: name: Headless Header Action author: pdteam severity: info tags: headless headless: - steps: - action: setheader args: part: request key: Test value: test value - action: navigate args: url: "{{BaseURL}}/" - action: waitload matchers: - type: word words: - "test value" ================================================ FILE: integration_tests/protocols/headless/headless-header-status-test.yaml ================================================ id: headless-header-status-test info: name: headless header + status test author: pdteam severity: info headless: - steps: - args: url: "{{BaseURL}}" action: navigate - action: waitload matchers-condition: and matchers: - type: word part: header words: - text/plain - type: status status: - 200 ================================================ FILE: integration_tests/protocols/headless/headless-local.yaml ================================================ id: nuclei-headless-local info: name: Nuclei Headless Local author: pdteam severity: high headless: - steps: - action: navigate args: url: "{{BaseURL}}" - action: waitload ================================================ FILE: integration_tests/protocols/headless/headless-payloads.yaml ================================================ id: headless-payloads info: name: headless payloads example author: pdteam severity: info tags: headless headless: - attack: clusterbomb payloads: aa: - aa - bb bb: - cc - dd steps: - args: url: "{{BaseURL}}?aa={{aa}}&bb={{bb}}" action: navigate - action: waitload matchers: - type: word words: - "test" ================================================ FILE: integration_tests/protocols/headless/headless-self-contained.yaml ================================================ id: headless-self-contained info: name: Headless Self Contained author: pdteam severity: info tags: headless self-contained: true headless: - steps: - action: navigate args: url: "https://postman-echo.com/get?q={{query}}" - action: waitload matchers: - type: word words: - "selfcontained" ================================================ FILE: integration_tests/protocols/headless/headless-waitevent.yaml ================================================ id: headless-waitevent info: name: WaitEvent severity: info author: pdteam headless: - steps: # note waitevent must be used before navigating to any page # unlike waitload - action: waitevent args: event: 'Page.loadEventFired' max-duration: 15s - action: navigate args: url: "{{BaseURL}}/" matchers: - type: word words: - "" ================================================ FILE: integration_tests/protocols/headless/variables.yaml ================================================ id: variables-example info: name: Variables Example author: pdteam severity: info variables: a1: "{{base64('hello')}}" headless: - steps: - args: url: "{{BaseURL}}" action: navigate - action: waitload matchers: - type: word words: - "{{a1}}" ================================================ FILE: integration_tests/protocols/http/annotation-timeout.yaml ================================================ id: annotation-timeout info: name: Basic Annotation Timeout author: pdteam severity: info http: - raw: - | @timeout: 5s GET / HTTP/1.1 Host: {{Hostname}} matchers: - type: word words: - "This is test matcher text" ================================================ FILE: integration_tests/protocols/http/cl-body-with-header.yaml ================================================ id: cl-body-with-header info: name: CL Get Request - Body with header author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" matchers: - type: dsl dsl: - "content_length==50000 && len(body)==14" ================================================ FILE: integration_tests/protocols/http/cl-body-without-header.yaml ================================================ id: cl-body-without-header info: name: CL Get Request - Body without header author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" matchers: - type: dsl dsl: - "content_length==14" ================================================ FILE: integration_tests/protocols/http/cli-with-constants.yaml ================================================ id: cli-with-constants info: name: Cli Var with Constants author: pdteam severity: info constants: test: test-in-template http: - method: GET path: - "{{BaseURL}}?p={{test}}" matchers: - type: word words: - "test-in-template" ================================================ FILE: integration_tests/protocols/http/constants-with-threads.yaml ================================================ id: constants-with-threads info: name: Constants with Threads author: pdteam severity: info description: | Test that constants are properly resolved when using threads mode. constants: api_key: "supersecretkey123" api_version: "v2" http: - method: GET path: - "{{BaseURL}}/api/{{api_version}}" threads: 5 headers: X-API-Key: "{{api_key}}" matchers: - type: word words: - "supersecretkey123" - "v2" condition: and ================================================ FILE: integration_tests/protocols/http/custom-attack-type.yaml ================================================ id: custom-attack-type info: name: Custom Attack Type author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}/?a={{test}}&b={{values}}" payloads: test: - hello - another values: - data - hacking attack: pitchfork matchers: - type: word words: - "This is test custom payload" ================================================ FILE: integration_tests/protocols/http/default-matcher-condition.yaml ================================================ id: default-matcher-condition info: name: default-matcher-condition author: pdteam severity: info http: - raw: - | GET /?action=curltest&url={{interactsh-url}} HTTP/1.1 Host: {{Hostname}} matchers: - type: word part: interactsh_protocol words: - "dns" - type: status status: - 200 ================================================ FILE: integration_tests/protocols/http/disable-path-automerge.yaml ================================================ id: test info: name: test author: pdteam severity: info http: - raw: - | GET /api/v1/test?id=123 HTTP/1.1 Host: {{Hostname}} - | GET HTTP/1.1 Host: {{Hostname}} disable-path-automerge: true matchers: - type: status status: - 200 ================================================ FILE: integration_tests/protocols/http/disable-redirects.yaml ================================================ id: basic-disable-redirects info: name: Basic GET Redirects Request author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" redirects: true max-redirects: 2 matchers: - type: word words: - "This is test disable redirects matcher text" ================================================ FILE: integration_tests/protocols/http/dsl-functions.yaml ================================================ id: helper-functions-examples info: name: RAW Template with Helper Functions author: pdteam severity: info http: - raw: # Note for the integration test: dsl expression should not contain commas - | GET / HTTP/1.1 Host: {{Hostname}} 01: {{base64("Hello")}} 02: {{base64(1234)}} 03: {{base64_decode("SGVsbG8=")}} 04: {{base64_py("Hello")}} 05: {{compare_versions('v1.0.0', '>v0.0.1', 'test")}} 25: {{html_unescape("<body>test</body>")}} 26: {{join("_", "hello", "world")}} 27: {{len("Hello")}} 28: {{len(5555)}} 29: {{md5("Hello")}} 30: {{md5(1234)}} 31: {{mmh3("Hello")}} 32: {{print_debug(1+2, "Hello")}} 33: {{rand_base(5, "abc")}} 34: {{rand_base(5, "")}} 35: {{rand_base(5)}} 36: {{rand_char("abc")}} 37: {{rand_char("")}} 38: {{rand_char()}} 39: {{rand_int(1, 10)}} 40: {{rand_int(10)}} 41: {{rand_int()}} 42: {{rand_ip("192.168.0.0/24")}} 43: {{rand_ip("2002:c0a8::/24")}} 44: {{rand_ip("192.168.0.0/24","10.0.100.0/24")}} 45: {{rand_text_alpha(10, "abc")}} 46: {{rand_text_alpha(10, "")}} 47: {{rand_text_alpha(10)}} 48: {{rand_text_alphanumeric(10, "ab12")}} 49: {{rand_text_alphanumeric(10)}} 50: {{rand_text_numeric(10, 123)}} 51: {{rand_text_numeric(10)}} 52: {{regex("H([a-z]+)o", "Hello")}} 53: {{remove_bad_chars("abcd", "bc")}} 54: {{repeat("a", 5)}} 55: {{replace("Hello", "He", "Ha")}} 56: {{replace_regex("He123llo", "(\\d+)", "")}} 57: {{reverse("abc")}} 58: {{sha1("Hello")}} 59: {{sha256("Hello")}} 60: {{sha512("Hello")}} 61: {{to_lower("HELLO")}} 62: {{to_upper("hello")}} 63: {{trim("aaaHelloddd", "ad")}} 64: {{trim_left("aaaHelloddd", "ad")}} 65: {{trim_prefix("aaHelloaa", "aa")}} 66: {{trim_right("aaaHelloddd", "ad")}} 67: {{trim_space(" Hello ")}} 68: {{trim_suffix("aaHelloaa", "aa")}} 69: {{unix_time(10)}} 70: {{url_decode("https:%2F%2Fprojectdiscovery.io%3Ftest=1")}} 71: {{url_encode("https://projectdiscovery.io/test?a=1")}} 72: {{wait_for(1)}} 73: {{zlib("Hello")}} 74: {{zlib_decode(hex_decode("789cf248cdc9c907040000ffff058c01f5"))}} 75: {{hex_encode(aes_gcm("AES256Key-32Characters1234567890", "exampleplaintext"))}} 76: {{starts_with("Hello", "He")}} 77: {{ends_with("Hello", "lo")}} 78: {{line_starts_with("Hi\nHello", "He")}} 79: {{line_ends_with("Hello\nHi", "lo")}} 80: {{sort("a1b2c3d4e5")}} 81: {{uniq("abcabdaabbccd")}} 82: {{join(" ", sort("b", "a", "2", "c", "3", "1", "d", "4"))}} 83: {{join(" ", uniq("ab", "cd", "12", "34", "12", "cd"))}} 84: {{split("ab,cd,efg", ",")}} 85: {{split("ab,cd,efg", ",", 2)}} 86: {{ip_format('127.0.0.1', 3)}} 87: {{ip_format('127.0.1.0', 11)}} 88: {{jarm('scanme.sh:443')}} extractors: - type: regex name: results regex: - '\d+: [^\s]+' ================================================ FILE: integration_tests/protocols/http/dsl-matcher-variable.yaml ================================================ id: dsl-matcher-variable info: name: dsl-matcher-variable author: pd-team severity: info http: - path: - "{{BaseURL}}" payloads: VALUES: - This - is - test - matcher - text matchers: - dsl: - 'contains(body,"{{VALUES}}")' type: dsl ================================================ FILE: integration_tests/protocols/http/get-all-ips.yaml ================================================ id: get-all-ips info: name: Basic GET Request on all IPS author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" matchers: - type: word words: - "ok" ================================================ FILE: integration_tests/protocols/http/get-case-insensitive.yaml ================================================ id: basic-get-case-insensitive info: name: Basic GET Request author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" matchers: - type: word case-insensitive: true words: - "ThIS is TEsT MAtcHEr TExT" ================================================ FILE: integration_tests/protocols/http/get-headers.yaml ================================================ id: basic-get-headers info: name: Basic GET Headers Request author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" headers: test: nuclei matchers: - type: word words: - "This is test headers matcher text" ================================================ FILE: integration_tests/protocols/http/get-host-redirects.yaml ================================================ id: basic-get-host-redirects info: name: Basic GET Host Redirects Request author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" host-redirects: true max-redirects: 3 matchers: - type: dsl dsl: - "status_code==307" ================================================ FILE: integration_tests/protocols/http/get-override-sni.yaml ================================================ id: basic-raw-http-example info: name: Test RAW GET Template author: pdteam severity: info http: - raw: - | @tls-sni:request.host GET / HTTP/1.1 Host: test matchers: - type: word words: - "test-ok" ================================================ FILE: integration_tests/protocols/http/get-query-string.yaml ================================================ id: basic-get-querystring info: name: Basic GET QueryString Request author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}?test=nuclei" matchers: - type: word words: - "This is test querystring matcher text" ================================================ FILE: integration_tests/protocols/http/get-redirects-chain-headers.yaml ================================================ id: basic-get-redirects-chain-headers info: name: Basic GET Redirects Request With Chain header author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" redirects: true max-redirects: 3 matchers-condition: and matchers: - type: word part: header words: - "TestRedirectHeaderMatch" - type: status status: - 302 ================================================ FILE: integration_tests/protocols/http/get-redirects.yaml ================================================ id: basic-get-redirects info: name: Basic GET Redirects Request author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" redirects: true max-redirects: 3 matchers: - type: word words: - "This is test redirects matcher text" ================================================ FILE: integration_tests/protocols/http/get-sni-unsafe.yaml ================================================ id: basic-unsafe-get info: name: Basic Unsafe GET Request with CLI SNI author: pdteam severity: info http: - raw: - |+ GET / HTTP/1.1 Host: {{Hostname}} unsafe: true matchers: - type: word words: - "test-ok" ================================================ FILE: integration_tests/protocols/http/get-sni.yaml ================================================ id: basic-get-sni info: name: Basic GET Request with CLI SNI author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" matchers: - type: word words: - "test-ok" ================================================ FILE: integration_tests/protocols/http/get-without-scheme.yaml ================================================ id: get-without-scheme info: name: Basic GET Request without scheme author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" matchers: - type: word words: - "ok" ================================================ FILE: integration_tests/protocols/http/get.yaml ================================================ id: basic-get info: name: Basic GET Request author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" matchers: - type: word words: - "This is test matcher text" ================================================ FILE: integration_tests/protocols/http/http-matcher-extractor-dy-extractor.yaml ================================================ id: http-matcher-extractor-dy-extractor info: name: HTTP matcher and extractor & dynamic extractor description: > Edgecase to test for a combination of matchers , extractors and dynamic extractors author: pdteam severity: info http: - raw: - | GET {{BaseURL}} HTTP/1.1 - | GET {{absolutePath}} HTTP/1.1 req-condition: true extractors: - type: regex internal: true part: body_1 name: absolutePath regex: - '' group: 1 - type: regex internal: false part: body_2 name: title regex: - ']*>([^<]+)' group: 1 matchers: - type: regex part: body_2 regex: - ']*>([^<]+)' ================================================ FILE: integration_tests/protocols/http/http-paths.yaml ================================================ id: http-paths info: name: Test Http Path Edgecases author: pd-team severity: info description: > - https://github.com/projectdiscovery/nuclei/pull/3211 - https://github.com/projectdiscovery/nuclei/pull/3127 reference: # adding expected results here for context and debugging - "/1337?with=param" - "/some%0A/%0D" - "/%73%6f%6d%65%0A/%0D" - "/%00test%20" - "/text4shell/attack?search=$%7bscript:javascript:java.lang.Runtime.getRuntime().exec('nslookup%20{}.getparam')%7d" - "/test/..;/..;/" - "/xyz/%25u2s/%25invalid" - "//CFIDE/wizards/common/utils.cfc" # duplicating here because same results are expected even if http request is written in different format - "/1337?with=param" - "/some%0A/%0D" - "/%73%6f%6d%65%0A/%0D" - "/%00test%20" - "/text4shell/attack?search=$%7bscript:javascript:java.lang.Runtime.getRuntime().exec('nslookup%20{}.getparam')%7d" - "/test/..;/..;/" - "/xyz/%25u2s/%25invalid" - "//CFIDE/wizards/common/utils.cfc" # Test all templates with FullURLs http: - raw: # relative path without leading slash with param # If relative path does not have `/` prefix it is autocorrected - |+ GET 1337?with=param HTTP/1.1 Host: scanme.sh # url encoded characters in path - |+ GET /some%0A/%0D HTTP/1.1 Host: scanme.sh # percent encoded characters in path # In URL encoding only key characters are encoded # while in percent encoding all characters are url encoded (similar to burp decoder) - |+ GET /%73%6f%6d%65%0A/%0D HTTP/1.1 Host: scanme.sh # test null and % chars in path - |+ GET /%00test%20 HTTP/1.1 Host: scanme.sh # test payload integrity in parameter - |+ GET /text4shell/attack?search=$%7bscript:javascript:java.lang.Runtime.getRuntime().exec('nslookup%20{}.getparam')%7d HTTP/1.1 Host: scanme.sh # test for missing trailing slash - |+ GET /test/..;/..;/ HTTP/1.1 Host: scanme.sh Origin: {{BaseURL}} # test relative path with invalid/corrupted characters # In such case instead of error or panic nuclei escaped unsupported character (i.e /xyz/%25u2s/%25invalid) # if template requires this condition to not escape unsupported characters. It can only be done in unsafe raw requests - |+ GET /xyz/%u2s/%invalid HTTP/1.1 Host: scanme.sh # test relative path start with // - |+ GET //CFIDE/wizards/common/utils.cfc HTTP/1.1 Host: scanme.sh matchers: - type: status status: - 200 # Same testcases as mentioned above but in path based request format - method: GET path: - "{{BaseURL}}/1337?with=param" - "{{BaseURL}}/some%0A/%0D" - "{{BaseURL}}/%73%6f%6d%65%0A/%0D" - "{{BaseURL}}/%00test%20" - "{{BaseURL}}/text4shell/attack?search=$%7bscript:javascript:java.lang.Runtime.getRuntime().exec('nslookup%20{}.getparam')%7d" - "{{BaseURL}}/test/..;/..;/" - "{{BaseURL}}/xyz/%u2s/%invalid" - "{{BaseURL}}//CFIDE/wizards/common/utils.cfc" matchers: - type: status status: - 200 ================================================ FILE: integration_tests/protocols/http/http-preprocessor.yaml ================================================ id: http-preprocessor info: name: Test Http Preprocessor author: pdteam severity: info http: - raw: - | GET /?test={{randstr}} HTTP/1.1 Host: {{Hostname}} matchers: - type: status status: - 200 ================================================ FILE: integration_tests/protocols/http/interactsh-requests-mc-and.yaml ================================================ id: interactsh-requests-mc-and info: name: interactsh multi request matcher condition author: pdteam severity: info http: - raw: - | GET /api/geoping/{{interactsh-url}} HTTP/1.1 Host: {{Hostname}} - | GET / HTTP/1.1 Host: {{Hostname}} matchers-condition: and matchers: - type: word part: interactsh_protocol # Confirms the DNS Interaction words: - "dns" - type: dsl dsl: - "status_code_2 == 200" ================================================ FILE: integration_tests/protocols/http/interactsh-stop-at-first-match.yaml ================================================ id: interactsh-stop-at-first-match-integration-test info: name: Interactsh StopAtFirstMatch Integration Test author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}/?a=1" - "{{BaseURL}}/?a=2" - "{{BaseURL}}/?a=3" - "{{BaseURL}}/?a=4" - "{{BaseURL}}/?a=5" - "{{BaseURL}}/?a=6" - "{{BaseURL}}/?a=7" - "{{BaseURL}}/?a=8" - "{{BaseURL}}/?a=9" headers: url: 'http://{{interactsh-url}}' stop-at-first-match: true matchers: - type: word part: interactsh_protocol # Confirms DNS Interaction words: - "dns" ================================================ FILE: integration_tests/protocols/http/interactsh-with-payloads.yaml ================================================ id: interactsh-with-payloads info: name: Interactsh With Payloads Integration Test author: dwisiswant0 severity: info tags: test http: - method: GET path: - "{{BaseURL}}/?p={{p}}" headers: url: 'http://{{interactsh-url}}' payloads: p: - a - b - c matchers: - type: word part: interactsh_protocol words: - "dns" ================================================ FILE: integration_tests/protocols/http/interactsh.yaml ================================================ id: interactsh-integration-test info: name: Interactsh Integration Test author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" headers: url: 'http://{{interactsh-url}}' matchers: - type: word part: interactsh_protocol # Confirms the HTTP Interaction words: - "dns" ================================================ FILE: integration_tests/protocols/http/matcher-status-and-cluster.yaml ================================================ id: matcher-status-and-cluster info: name: Test Matcher Status AND Condition Cluster author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}/" stop-at-first-match: true matchers-condition: and matchers: - type: word part: body words: - "this_will_also_never_match" - type: status status: - 200 ================================================ FILE: integration_tests/protocols/http/matcher-status-and.yaml ================================================ id: matcher-status-and info: name: Test Matcher Status AND Condition author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}/" stop-at-first-match: true matchers-condition: and matchers: - type: word part: body words: - "this_will_never_match_anything" - type: status status: - 200 ================================================ FILE: integration_tests/protocols/http/matcher-status.yaml ================================================ id: matcher-status info: name: Test Matcher Status author: pdteam severity: critical variables: username: test password: admin date: 2023-05-31 http: - method: GET path: - "{{RootURL}}/login?username={{username}}&password={{password}}" - "{{BaseURL}}/admin-pannel" - method: GET path: - "{{BaseURL}}/dashboard?date={{date}}" - "{{BaseURL}}/signup" - method: POST path: - "{{BaseURL}}/filemanager/upload.php" body: "fldr=&url=file:///etc/passwd" stop-at-first-match: true matchers-condition: and matchers: - type: word part: body words: - "matcher status" - type: status status: - 200 ================================================ FILE: integration_tests/protocols/http/multi-http-var-sharing.yaml ================================================ id: multi-http-var-sharing info: name: Multi HTTP var sharing author: pdteam severity: info description: | A template which has multiple HTTP requests block and variables are shared between them http: - method: GET path: - "{{BaseURL}}" matchers: - type: word words: - "This is test matcher text" negative: true internal: true extractors: - type: dsl name: ffff dsl: - status_code internal: true - method: GET path: - "{{BaseURL}}/{{ffff}}" matchers: - type: status status: - 200 ================================================ FILE: integration_tests/protocols/http/multi-request.yaml ================================================ id: http-multi-request info: name: http multi request template author: pdteam severity: info description: template with multiple http request with combined logic reference: https://example-reference-link # requestURI is reflected back as response body here http: - raw: - | GET /ping HTTP/1.1 Host: {{Hostname}} - | GET /pong HTTP/1.1 Host: {{Hostname}} matchers: - type: dsl dsl: - 'body_1 == "ping"' - 'body_2 == "pong"' condition: and ================================================ FILE: integration_tests/protocols/http/post-body.yaml ================================================ id: basic-post-body info: name: Basic POST Body Request author: pdteam severity: info http: - method: POST path: - "{{BaseURL}}" headers: Content-Type: application/x-www-form-urlencoded Content-Length: 1 # as long as there is a value, nuclei will auto-recalculate it. body: username=test&password=nuclei matchers: - type: word words: - "This is test post-body matcher text" ================================================ FILE: integration_tests/protocols/http/post-json-body.yaml ================================================ id: basic-post-json-body info: name: Basic POST JSON Body Request author: pdteam severity: info http: - method: POST path: - "{{BaseURL}}" headers: Content-Type: application/json Content-Length: 1 body: '{"username":"test","password":"nuclei"}' matchers: - type: word words: - "This is test post-json-body matcher text" ================================================ FILE: integration_tests/protocols/http/post-multipart-body.yaml ================================================ id: basic-post-multipart-body info: name: Basic POST Multipart Request author: pdteam severity: info http: - method: POST path: - "{{BaseURL}}" headers: Content-Type: multipart/form-data; boundary=d64a5c6be2120f494d87b096fff6efe6d3248474d4de2debb1d387b3d8e8 Content-Length: 1 body: | --d64a5c6be2120f494d87b096fff6efe6d3248474d4de2debb1d387b3d8e8 Content-Disposition: form-data; name="username"; filename="username" Content-Type: application/octet-stream test --d64a5c6be2120f494d87b096fff6efe6d3248474d4de2debb1d387b3d8e8 Content-Disposition: form-data; name="password" nuclei --d64a5c6be2120f494d87b096fff6efe6d3248474d4de2debb1d387b3d8e8-- matchers: - type: word words: - "This is test post-multipart matcher text" ================================================ FILE: integration_tests/protocols/http/race-condition-with-delay.yaml ================================================ id: race-condition-with-delay info: name: Race Condition Testing with Delay author: pdteam severity: info description: | Test race condition handling with induced server delay. tags: test http: - raw: - | GET / HTTP/1.1 Host: {{Hostname}} - | GET / HTTP/1.1 Host: {{Hostname}} - | GET / HTTP/1.1 Host: {{Hostname}} threads: 2 race: true matchers: - type: status status: - 200 ================================================ FILE: integration_tests/protocols/http/race-multiple.yaml ================================================ id: race-condition-testing info: name: Race condition testing with multiple requests author: pdteam severity: info http: - raw: - | GET / HTTP/1.1 Host: {{Hostname}} id=1 - | GET / HTTP/1.1 Host: {{Hostname}} id=2 - | GET / HTTP/1.1 Host: {{Hostname}} id=3 - | GET / HTTP/1.1 Host: {{Hostname}} id=4 - | GET / HTTP/1.1 Host: {{Hostname}} id=5 threads: 5 race: true matchers: - type: status status: - 200 ================================================ FILE: integration_tests/protocols/http/race-simple.yaml ================================================ id: race-condition-testing info: name: Race Condition testing author: pdteam severity: info http: - raw: - | GET / HTTP/1.1 Host: {{Hostname}} test race: true race_count: 10 matchers: - type: status part: header status: - 200 ================================================ FILE: integration_tests/protocols/http/race-with-variables.yaml ================================================ id: race-with-variables info: name: Race Condition with Variables author: pdteam severity: info description: | Test that variables and constants are properly resolved in race mode. variables: random_id: "{{rand_base(8)}}" constants: api_key: "racekey123" http: - raw: - | GET /race HTTP/1.1 Host: {{Hostname}} X-Request-Id: {{random_id}} X-API-Key: {{api_key}} race: true race_count: 3 matchers: - type: word words: - "racekey123" ================================================ FILE: integration_tests/protocols/http/raw-cookie-reuse.yaml ================================================ id: cookiereuse-raw-example info: name: Test Cookie Reuse RAW Template author: pdteam severity: info http: - raw: - | POST / HTTP/1.1 Host: {{Hostname}} Origin: {{BaseURL}} Connection: close Content-Type: application/x-www-form-urlencoded Content-Length: 1 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 testing=parameter - | 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-cookie-reuse matcher text" ================================================ FILE: integration_tests/protocols/http/raw-dynamic-extractor.yaml ================================================ id: dynamic-extractor-raw-example info: name: Test Dynamic Extractor RAW Template author: pdteam severity: info http: - raw: - | POST / HTTP/1.1 Host: {{Hostname}} Origin: {{BaseURL}} Connection: close Content-Type: application/x-www-form-urlencoded Content-Length: 1 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 testing=parameter - | GET /?username={{randkey}} 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 extractors: - type: regex name: randkey part: body group: 1 internal: true regex: - "Token: '([A-Za-z0-9]+)'" matchers: - type: word words: - "Test is test-dynamic-extractor-raw matcher text" ================================================ FILE: integration_tests/protocols/http/raw-get-query.yaml ================================================ id: basic-raw-query-example info: name: Test RAW GET Query Template author: pdteam severity: info http: - raw: - | GET ?test=nuclei HTTP/1.1 Host: {{Hostname}} Origin: {{BaseURL}} matchers: - type: word words: - "Test is test raw-get-query-matcher text" ================================================ FILE: integration_tests/protocols/http/raw-get.yaml ================================================ id: basic-raw-http-example info: name: Test RAW GET Template author: pdteam severity: info http: - raw: - | GET / HTTP/1.1 Host: {{Hostname}} Origin: {{BaseURL}} matchers: - type: word words: - "Test is test raw-get-matcher text" ================================================ FILE: integration_tests/protocols/http/raw-path-single-slash.yaml ================================================ id: raw-path-single-slash info: name: Test RAW HTTP Template with single slash author: pdteam severity: info requests: - raw: - | GET / HTTP/1.1 Host: {{Hostname}} Origin: {{BaseURL}} ================================================ FILE: integration_tests/protocols/http/raw-path-trailing-slash.yaml ================================================ id: raw-path-trailing-slash info: name: Test RAW HTTP Template with trailing slash author: pdteam severity: info http: - raw: - | GET /test/..;/..;/ HTTP/1.1 Host: {{Hostname}} Origin: {{BaseURL}} ================================================ FILE: integration_tests/protocols/http/raw-payload.yaml ================================================ id: payload-raw-example info: name: Test RAW With Payload Template author: pdteam severity: info http: - payloads: username: - test password: - nuclei - guest attack: clusterbomb raw: - | POST / HTTP/1.1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) Host: {{Hostname}} Content-Type: application/x-www-form-urlencoded Content-Length: 1 another_header: {{base64('§password§')}} Accept: */* username=§username§&password={{password}} matchers: - type: word words: - "Test is raw-payload matcher text" ================================================ FILE: integration_tests/protocols/http/raw-post-body.yaml ================================================ id: basic-raw-http-body-example info: name: Test RAW POST Template author: pdteam severity: info http: - raw: - | POST / HTTP/1.1 Host: {{Hostname}} Origin: {{BaseURL}} Content-Type: application/x-www-form-urlencoded Content-Length: 1 username=test&password=nuclei matchers: - type: word words: - "Test is test raw-post-body-matcher text" ================================================ FILE: integration_tests/protocols/http/raw-unsafe-path-single-slash.yaml ================================================ id: raw-unsafe-path-single-slash info: name: Test RAW Unsafe HTTP Template with single slash author: pdteam severity: info requests: - raw: - |+ GET / HTTP/1.1 Host: {{Hostname}} Origin: {{BaseURL}} unsafe: true ================================================ FILE: integration_tests/protocols/http/raw-unsafe-path.yaml ================================================ id: raw-unsafe-path info: name: Test RAW Unsafe Paths author: pd-team severity: info description: > - https://github.com/projectdiscovery/nuclei/pull/3211 - https://github.com/projectdiscovery/nuclei/pull/3127 reference: # adding expected results here for context and debugging - "1337" - "1337?with=param" - "/some%0A/%0D" - "/%20test%0a" - "/text4shell/attack?search=$%7bscript:javascript:java.lang.Runtime.getRuntime().exec('nslookup%20{}.getparam')%7d" - "/test/..;/..;/" - "/xyz/%u2s/%invalid" - "//CFIDE/wizards/common/utils.cfc" # Test all unsafe URL Handling Edgecases http: - raw: # relative path without leading slash - |+ GET 1337 HTTP/1.1 Host: scanme.sh # same but with param - |+ GET 1337?with=param HTTP/1.1 Host: scanme.sh # url encoded characters in path - |+ GET /some%0A/%0D HTTP/1.1 Host: scanme.sh # test unsupported chars in path - |+ GET /%20test%0a HTTP/1.1 Host: scanme.sh # test payload integrity params - |+ GET /text4shell/attack?search=$%7bscript:javascript:java.lang.Runtime.getRuntime().exec('nslookup%20{}.getparam')%7d HTTP/1.1 Host: scanme.sh # test for missing trailing slash - |+ GET /test/..;/..;/ HTTP/1.1 Host: scanme.sh Origin: {{BaseURL}} # test relative path with invalid/corrupted characters - |+ GET /xyz/%u2s/%invalid HTTP/1.1 Host: scanme.sh # test relative path start with // (should not be removed) - |+ GET //CFIDE/wizards/common/utils.cfc HTTP/1.1 Host: scanme.sh unsafe: true matchers: - type: status status: - 200 ================================================ FILE: integration_tests/protocols/http/raw-unsafe-request.yaml ================================================ id: basic-raw-unsafe-request-example info: name: Test RAW Unsafe Request Template author: pd-team severity: info http: - raw: - |+ GET / HTTP/1.1 Host: Content-Length: 4 unsafe: true matchers-condition: and matchers: - type: word words: - "This is test raw-unsafe-matcher test" ================================================ FILE: integration_tests/protocols/http/raw-unsafe-with-params.yaml ================================================ id: raw-unsafe-with-params info: name: Test RAW unsafe with params author: pdteam severity: info # this test is used to check automerge of params in both unsafe & safe requests # key1=value1 is added from inputURL http: - raw: - |+ GET /?key2=value2 HTTP/1.1 Host: {{Hostname}} unsafe: true matchers: - type: word words: - "Test is test raw-params-matcher text" ================================================ FILE: integration_tests/protocols/http/raw-with-params.yaml ================================================ id: raw-with-params info: name: Test RAW Params Template author: pdteam severity: info # this test is used to check automerge of params in both unsafe & safe requests # key1=value1 is added from inputURL http: - raw: - | GET /?key2=value2 HTTP/1.1 Host: {{Hostname}} Origin: {{BaseURL}} matchers: - type: word words: - "Test is test raw-params-matcher text" ================================================ FILE: integration_tests/protocols/http/redirect-match-url.yaml ================================================ id: redirect-match-url info: name: Redirect Match URL author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" stop-at-first-match: true # Confirm stop-at-first-match redirects: true # Confirm redirected URL matched value max-redirects: 3 matchers: - type: word words: - "This is test redirects matcher text" ================================================ FILE: integration_tests/protocols/http/request-condition-new.yaml ================================================ id: request-condition-new info: name: request-condition-new author: pd-team severity: info http: - method: GET id: first path: - "{{BaseURL}}/200" - method: GET path: - "{{BaseURL}}/400" matchers: - type: dsl dsl: - "first_status_code==200 && status_code==400" ================================================ FILE: integration_tests/protocols/http/request-condition.yaml ================================================ id: request-condition info: name: request-condition author: pd-team severity: info http: - method: GET path: - "{{BaseURL}}/200" - "{{BaseURL}}/400" matchers: - type: dsl dsl: - "status_code_1==200 && status_code_2==400" ================================================ FILE: integration_tests/protocols/http/response-data-literal-reuse.yaml ================================================ id: response-data-literal-reuse info: name: Response data literal reuse author: dwisiswant0 severity: info description: | Make sure response-derived {{...}} content stays literal when reused in a later request. tags: test,http http: - raw: - | GET / HTTP/1.1 Host: {{Hostname}} extractors: - type: regex name: extracted_body part: body regex: - '(?s)(.*)' internal: true - raw: - | GET /echo?x={{extracted_body}} HTTP/1.1 Host: {{Hostname}} matchers: - type: status status: - 200 ================================================ FILE: integration_tests/protocols/http/self-contained-file-input.yaml ================================================ id: self-contained-file-input info: name: Test Self Contained Template With File Input author: pdteam severity: info self-contained: true http: - method: GET path: - "http://127.0.0.1:5431/{{test}}" matchers: - type: word words: - This is self-contained response - raw: - | GET http://127.0.0.1:5431/{{test}} HTTP/1.1 Host: {{Hostname}} matchers: - type: word words: - This is self-contained response ================================================ FILE: integration_tests/protocols/http/self-contained-with-params.yaml ================================================ id: self-contained-with-params info: name: self contained with params author: pd-team severity: info self-contained: true http: - raw: - | GET http://127.0.0.1:5431/?something=here&key=value HTTP/1.1 Host: {{Hostname}} matchers: - type: word words: - This is self-contained response ================================================ FILE: integration_tests/protocols/http/self-contained-with-path.yaml ================================================ id: self-contained-with-path info: name: self-contained-with-path author: pd-team severity: info self-contained: true http: - raw: - | GET / HTTP/1.1 Host: 127.0.0.1:5431 matchers: - type: word words: - This is self-contained response ================================================ FILE: integration_tests/protocols/http/self-contained.yaml ================================================ id: example-self-contained-input info: name: example-self-contained author: pd-team severity: info self-contained: true http: - raw: - | GET http://127.0.0.1:5431/ HTTP/1.1 Host: {{Hostname}} matchers: - type: word words: - This is self-contained response ================================================ FILE: integration_tests/protocols/http/stop-at-first-match-with-extractors.yaml ================================================ id: stop-at-first-match-with-extractors info: name: Stop at first match Request with extractors author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}?a=1" - "{{BaseURL}}?a=2" stop-at-first-match: true extractors: - type: kval part: header kval: - "date" ================================================ FILE: integration_tests/protocols/http/stop-at-first-match.yaml ================================================ id: stop-at-first-match info: name: Stop at first match Request author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}?a=1" - "{{BaseURL}}?a=2" matchers: - type: word words: - "This is test" stop-at-first-match: true ================================================ FILE: integration_tests/protocols/http/variable-dsl-function.yaml ================================================ id: basic-example info: name: Test HTTP Template author: pdteam severity: info variables: a1: "{{to_lower(rand_base(5))}}" http: - method: GET path: - "{{BaseURL}}/?x={{a1}}" - "{{BaseURL}}/?x={{a1}}" extractors: - type: dsl dsl: - a1 ================================================ FILE: integration_tests/protocols/http/variables-threads-previous.yaml ================================================ id: variables-threads-previous info: name: Variables with Threads and Previous Request Data author: pdteam severity: info description: | Test that variables can reference data extracted from previous requests when using threads mode (parallel execution). variables: auth_header: "Bearer {{extracted_token}}" http: - method: GET path: - "{{BaseURL}}/login" extractors: - type: regex name: extracted_token part: body regex: - 'token=([a-z0-9]+)' group: 1 internal: true - method: GET path: - "{{BaseURL}}/api" threads: 5 headers: Authorization: "{{auth_header}}" matchers: - type: word words: - "Bearer secret123" ================================================ FILE: integration_tests/protocols/http/variables.yaml ================================================ id: variables-example info: name: Variables Example author: pdteam severity: info variables: a1: "value" a2: "{{base64('{{Host}}')}}" http: - raw: - | GET / HTTP/1.1 Host: {{FQDN}} Test: {{a1}} Another: {{a2}} Email: {{ username }} payloads: username: - jon.doe@{{ FQDN }} stop-at-first-match: true matchers-condition: or matchers: - type: word condition: and words: - "value" - "MTI3LjAuMC4x" # 127.0.0.1 - "jon.doe@127.0.0.1" ================================================ FILE: integration_tests/protocols/javascript/multi-ports.yaml ================================================ id: multi-ports info: name: Multi Ports - Detection author: pdteam severity: info description: | Multi Ports template for testing metadata: max-request: 1 tags: js,detect,multi-ports,enum,network javascript: - pre-condition: | isPortOpen(Host,Port); code: | var m = require("nuclei/ssh"); var c = m.SSHClient(); var response = c.ConnectSSHInfoMode(Host, Port); Export(response); args: Host: "{{Host}}" Port: "2222,22" # Port 22 should match extractors: - type: json json: - '.UserAuth' ================================================ FILE: integration_tests/protocols/javascript/mysql-connect.yaml ================================================ id: mysql-connect info: name: MySQL Connect Test author: pdteam severity: high javascript: - pre-condition: | isPortOpen(Host, Port) code: | const mysql = require('nuclei/mysql'); const client = new mysql.MySQLClient; success = client.Connect(Host, Port, User, Pass); args: Host: "{{Host}}" Port: "3306" User: "root" Pass: "secret" matchers: - type: dsl dsl: - "success == true" ================================================ FILE: integration_tests/protocols/javascript/net-https.yaml ================================================ id: net-https info: name: net-https author: pdteam severity: info description: send and receive https data using net module javascript: - code: | let m = require('nuclei/net'); let name=Host+':'+Port; let conn = m.OpenTLS('tcp', name); conn.Send('GET / HTTP/1.1\r\nHost:'+name+'\r\nConnection: close\r\n\r\n'); resp = conn.RecvString(); args: Host: "{{Host}}" Port: "443" matchers: - type: word words: - "HTTP/1.1 200 OK" ================================================ FILE: integration_tests/protocols/javascript/net-multi-step.yaml ================================================ id: network-multi-step info: name: network multi-step author: tarunKoyalwar severity: high description: | Network multi-step template for testing javascript: - code: | var m = require("nuclei/net"); var conn = m.Open("tcp",address); conn.SetTimeout(timeout); // optional timeout conn.Send("FIRST") conn.RecvString(4) // READ 4 bytes i.e PING conn.Send("SECOND") conn.RecvString(4) // READ 4 bytes i.e PONG conn.RecvString(6) // READ 6 bytes i.e NUCLEI args: address: "{{Host}}:{{Port}}" Host: "{{Host}}" Port: 5431 timeout: 3 # in sec matchers: - type: dsl dsl: - success == true - response == "NUCLEI" condition: and ================================================ FILE: integration_tests/protocols/javascript/no-port-args.yaml ================================================ id: javascript-no-port-args info: name: JavaScript Template Without Port Args author: dwisiswant0 severity: info description: | Test backwards compatibility for JavaScript templates without Port in args. Templates should execute even when Port arg is not explicitly specified. javascript: - code: | // Simple JavaScript code that does not require Port let result = "executed"; Export(result); args: TestArg: "test-value" matchers: - type: dsl dsl: - success == true ================================================ FILE: integration_tests/protocols/javascript/postgres-pass-brute.yaml ================================================ id: postgres-pass-brute info: name: PostgreSQL Password Bruteforce author: pdteam severity: high description: | This template bruteforces passwords for protected PostgreSQL instances. If PostgreSQL is not protected with password, it is also matched. metadata: shodan-query: product:"PostgreSQL" tags: js,network,postgresql,authentication javascript: - pre-condition: | isPortOpen(Host,Port) code: | const postgres = require('nuclei/postgres'); const client = new postgres.PGClient; success = client.Connect(Host, Port, User, Pass); args: Host: "{{Host}}" Port: "5432" User: "{{usernames}}" Pass: "{{passwords}}" attack: clusterbomb payloads: usernames: - postgres - admin - root passwords: - "" - postgres - password - admin - root stop-at-first-match: true matchers: - type: dsl dsl: - "success == true" ================================================ FILE: integration_tests/protocols/javascript/redis-pass-brute.yaml ================================================ id: redis-pass-brute info: name: redis password bruteforce author: tarunKoyalwar severity: high description: | This template bruteforces passwords for protected redis instances. If redis is not protected with password. it is also matched metadata: shodan-query: product:"redis" javascript: - pre-condition: | isPortOpen(Host,Port) code: | var m = require("nuclei/redis"); m.GetServerInfoAuth(Host,Port,Password); args: Host: "{{Host}}" Port: "6379" Password: "{{passwords}}" payloads: passwords: - "" - root - password - admin - iamadmin stop-at-first-match: true matchers-condition: and matchers: - type: word words: - "redis_version" - type: word negative: true words: - "redis_mode:sentinel" ================================================ FILE: integration_tests/protocols/javascript/rsync-test.yaml ================================================ id: rsync-test info: name: Rsync Test author: pdteam severity: info javascript: - code: | const rsync = require('nuclei/rsync'); rsync.IsRsync(Host, Port); args: Host: "{{Host}}" Port: "873" matchers: - type: dsl dsl: - "success == true" ================================================ FILE: integration_tests/protocols/javascript/ssh-server-fingerprint.yaml ================================================ id: ssh-server-fingerprint info: name: Fingerprint SSH Server Software author: Ice3man543,tarunKoyalwar severity: info metadata: shodan-query: port:22 javascript: - code: | var m = require("nuclei/ssh"); var c = m.SSHClient(); var response = c.ConnectSSHInfoMode(Host, Port); to_json(response); args: Host: "{{Host}}" Port: "22" extractors: - type: json name: server json: - '.ServerID.Raw' part: response ================================================ FILE: integration_tests/protocols/javascript/telnet-auth-test.yaml ================================================ id: telnet-auth-test info: name: Telnet Authentication Test author: pdteam severity: info metadata: shodan-query: port:23 javascript: - code: | var m = require("nuclei/telnet"); var c = m.TelnetClient(); c.Connect(Host, Port, User, Password); args: Host: "{{Host}}" Port: "23" User: "dev" Password: "mysecret" matchers: - type: dsl dsl: - "response == true" - "success == true" condition: and ================================================ FILE: integration_tests/protocols/javascript/vnc-pass-brute.yaml ================================================ id: vnc-password-test info: name: VNC Password Authentication Test author: pdteam severity: high description: | Tests VNC authentication with correct and incorrect passwords. metadata: shodan-query: product:"vnc" tags: js,network,vnc,authentication javascript: - pre-condition: | isPortOpen(Host,Port) code: | let vnc = require('nuclei/vnc'); let client = new vnc.VNCClient(); client.Connect(Host, Port, Password); args: Host: "{{Host}}" Port: "5900" Password: "{{passwords}}" payloads: passwords: - "" - root - password - admin - mysecret stop-at-first-match: true matchers: - type: dsl dsl: - "success == true" ================================================ FILE: integration_tests/protocols/keys/README.md ================================================ ## keys the keys stored here especially `ci-private-key.pem` and `ci.crt` are used in integration tests to test template signing and verification functionality introduced in nuclei v3 ================================================ FILE: integration_tests/protocols/keys/ci-private-key.pem ================================================ -----BEGIN PD NUCLEI USER PRIVATE KEY----- MHcCAQEEIEywlBGZ94ARrBT+1fTu/Ii7HGfJc4y7kK4aGYvDMYm5oAoGCCqGSM49 AwEHoUQDQgAEnyVUkFKJx92/8doQ//VAPCrzB4dqvNgwLRZPC/oAieVpNG8HDGNw PJ7qB7ovIfGwDOW98vQwsRG4TmgFlZr0rQ== -----END PD NUCLEI USER PRIVATE KEY----- ================================================ FILE: integration_tests/protocols/keys/ci.crt ================================================ -----BEGIN PD NUCLEI USER CERTIFICATE----- MIIBPzCB56ADAgECAgRlHGgmMAoGCCqGSM49BAMCMA0xCzAJBgNVBAMTAkNJMB4X DTIzMTAwMzE5MTQ0NloXDTI3MTAwMjE5MTQ0NlowDTELMAkGA1UEAxMCQ0kwWTAT BgcqhkjOPQIBBggqhkjOPQMBBwNCAASfJVSQUonH3b/x2hD/9UA8KvMHh2q82DAt Fk8L+gCJ5Wk0bwcMY3A8nuoHui8h8bAM5b3y9DCxEbhOaAWVmvStozUwMzAOBgNV HQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAK BggqhkjOPQQDAgNHADBEAiBgUdbAcSbDpkNNQscZog/pAuaRV4sk7fbOlTRcjZTL qQIgdtvG1w7l9VAtk6gx+HJa3BP9IFhSfT+a3UCuJy2p2iA= -----END PD NUCLEI USER CERTIFICATE----- ================================================ FILE: integration_tests/protocols/multi/dynamic-values.yaml ================================================ id: dns-http-dynamic-values info: name: multi protocol request with dynamic values author: pdteam severity: info dns: - name: "{{FQDN}}" # DNS Request type: cname extractors: - type: dsl name: blogid dsl: - trim_suffix(cname,'.vercel-dns.com') internal: true http: - method: GET # http request path: - "{{BaseURL}}" matchers: - type: dsl dsl: - contains(body,'home') # check for http string - blogid == 'cname' # check for cname (extracted information from dns response) condition: and ================================================ FILE: integration_tests/protocols/multi/evaluate-variables.yaml ================================================ id: dns-ssl-http-with-variables info: name: multi protocol request with dynamic values author: pdteam severity: info variables: cname_filtered: '{{trim_suffix(dns_cname,".vercel-dns.com")}}' dns: - name: "{{FQDN}}" # DNS Request type: cname ssl: - address: "{{Hostname}}" # ssl request http: - method: GET # http request path: - "{{BaseURL}}" matchers: - type: dsl dsl: - contains(http_body,'home') # check for http string - cname_filtered == 'cname' # check for cname (extracted information from dns response) - ssl_subject_cn == 'docs.projectdiscovery.io' condition: and ================================================ FILE: integration_tests/protocols/multi/exported-response-vars.yaml ================================================ id: dns-ssl-http-proto-prefix info: name: multi protocol request with dynamic values author: pdteam severity: info dns: - name: "{{FQDN}}" # DNS Request type: cname ssl: - address: "{{Hostname}}" # ssl request http: - method: GET # http request path: - "{{BaseURL}}" matchers: - type: dsl dsl: - contains(http_body,'home') # check for http string - trim_suffix(dns_cname,'.vercel-dns.com') == 'cname' # check for cname (extracted information from dns response) - ssl_subject_cn == 'docs.projectdiscovery.io' condition: and ================================================ FILE: integration_tests/protocols/network/basic.yaml ================================================ id: basic-network-request info: name: Basic Network Request author: pdteam severity: info network: - host: - "{{Hostname}}" inputs: - data: "PING\r\n" read-size: 4 matchers: - type: word part: data words: - "PONG" ================================================ FILE: integration_tests/protocols/network/hex.yaml ================================================ id: hex-network-request info: name: Hex Input Network Request author: pdteam severity: info network: - host: - "{{Hostname}}" inputs: - data: "50494e47" type: hex - data: "\r\n" read-size: 4 matchers: - type: word part: data encoding: hex words: - "504f4e47" ================================================ FILE: integration_tests/protocols/network/multi-step.yaml ================================================ id: multi-step info: name: Multi-Step Network Template author: pd-team severity: info network: - inputs: - data: "FIRST" read: 4 name: first - data: "SECOND" read: 4 name: second host: - "{{Hostname}}" read-size: 6 matchers: - type: word part: first words: - "PING" - type: word part: second words: - "PONG" - type: word part: data words: - "NUCLEI" matchers-condition: and ================================================ FILE: integration_tests/protocols/network/net-https-timeout.yaml ================================================ id: net-https-timeout info: name: Example Network template which times out author: pdteam severity: high description: Example Network template to send HTTPS request which times out tcp: - host: - "tls://{{Hostname}}" port: 443 inputs: # noticable difference between this and net-https.yaml is that here we don't send the Connection: close header # and hence connection will remain open until server closes it. This can be a DOS vector in nuclei # as it waits for server to close the connection. now we have set a default timeout of 5 seconds and if server responds but doesn't close the connection # then nuclei will close connection but doesn't fail the request since we already have response data from server # this feature is only required for `read-all: true` to work properly - data: "GET / HTTP/1.1\r\nHost: {{Hostname}}\r\n\r\n" read-all: true extractors: - type: dsl dsl: - "len(data)" ================================================ FILE: integration_tests/protocols/network/net-https.yaml ================================================ id: net-https info: name: Example Network template to send HTTPS request author: pdteam severity: high description: Example Network template to send HTTPS request tcp: - host: - "tls://{{Hostname}}" port: 443 inputs: - data: "GET / HTTP/1.1\r\nHost: {{Hostname}}\r\nConnection: close\r\n\r\n" read-all: true extractors: - type: dsl dsl: - "len(data)" ================================================ FILE: integration_tests/protocols/network/network-port.yaml ================================================ id: network-port-example info: name: Example Template with Network Port author: pdteam severity: high description: This is an updated description for the network port example. reference: https://updated-reference-link tcp: - host: - "{{Hostname}}" port: 23846 inputs: - data: "PING\r\n" read-size: 4 matchers: - type: word part: data words: - "PONG" ================================================ FILE: integration_tests/protocols/network/same-address.yaml ================================================ id: same-target info: name: same-target author: pdteam severity: info description: Riak is a distributed NoSQL key-value data store that offers high availability, fault tolerance, operational simplicity, and scalability. network: - host: - "{{Hostname}}" - "{{Hostname}}" - "{{Hostname}}" - "{{Hostname}}" - "{{Hostname}}" - "{{Hostname}}" - "{{Hostname}}" - "{{Hostname}}" - "{{Hostname}}" - "{{Hostname}}" - "{{Hostname}}" inputs: - data: "PING\r\n" read-size: 4 matchers: - type: word part: data words: - "PONG" ================================================ FILE: integration_tests/protocols/network/self-contained.yaml ================================================ id: example-self-contained-input info: name: example-self-contained author: pd-team severity: info self-contained: true network: - host: - "127.0.0.1:5431" matchers: - type: word words: - "Authentication successful" ================================================ FILE: integration_tests/protocols/network/variables.yaml ================================================ id: variables-example info: name: Variables Example author: pdteam severity: info variables: a1: "PING" a2: "{{base64('hello')}}" network: - host: - "{{Hostname}}" inputs: - data: "{{a1}}" read-size: 8 matchers: - type: word part: data words: - "{{a2}}" ================================================ FILE: integration_tests/protocols/offlinehttp/data/req-resp-with-http-keywords.txt ================================================ GET / HTTP/1.1 Host: pastebin.com User-Agent: curl/7.79.1 Accept: */* Connection: close HTTP/1.1 200 OK Date: Tue, 21 Jun 2022 09:32:01 GMT Content-Type: text/plain; charset=utf-8 Connection: close x-frame-options: DENY x-content-type-options: nosniff x-xss-protection: 1;mode=block cache-control: public, max-age=1801 CF-Cache-Status: HIT Age: 1585 Last-Modified: Tue, 21 Jun 2022 09:05:36 GMT Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" Server: cloudflare CF-RAY: 71ebbc0a7ea83b8b-CDG 54 line1 this is a line containing HTTP/1.1 FOO BAR line3 0 ================================================ FILE: integration_tests/protocols/offlinehttp/offline-allowed-paths.yaml ================================================ id: offline-allowed-paths info: name: offline-allowed-paths author: pdteam severity: info description: offline-allowed-paths http: - path: - "{{BaseURL}}" - "{{BaseURL}}/" - "/" matchers: - type: status status: - 200 ================================================ FILE: integration_tests/protocols/offlinehttp/offline-raw.yaml ================================================ id: offline-raw info: name: Test Offline raw Template author: pdteam severity: info http: - raw: - | GET / HTTP/1.1 Host: {{Hostname}} matchers: - type: status status: - 200 ================================================ FILE: integration_tests/protocols/offlinehttp/rfc-req-resp.yaml ================================================ id: rfc-req-resp info: name: Basic GET Request author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" matchers: - type: word words: - "this is a line containing HTTP/1.1 FOO BAR" ================================================ FILE: integration_tests/protocols/ssl/basic-ztls.yaml ================================================ id: basic-ssl-tls info: name: Basic SSL Request with ztls author: pdteam severity: info ssl: - address: "{{Host}}:{{Port}}" min_version: ssl30 max_version: tls12 matchers: - type: dsl dsl: - "tls_connection == 'ztls'" ================================================ FILE: integration_tests/protocols/ssl/basic.yaml ================================================ id: basic-ssl info: name: Basic SSL Request author: pdteam severity: info ssl: - address: "{{Host}}:{{Port}}" matchers: - type: dsl dsl: - "probe_status == true" ================================================ FILE: integration_tests/protocols/ssl/custom-cipher.yaml ================================================ id: custom-cipher info: name: Basic SSL Request author: pdteam severity: info ssl: - address: "{{Host}}:{{Port}}" cipher_suites: - TLS_AES_128_GCM_SHA256 matchers: - type: word part: response words: - "TLS_AES_128_GCM_SHA256" ================================================ FILE: integration_tests/protocols/ssl/custom-version.yaml ================================================ id: custom-version info: name: Basic SSL Request author: pdteam severity: info ssl: - address: "{{Host}}:{{Port}}" min_version: tls12 max_version: tls12 matchers: - type: word part: response words: - 'tls12' ================================================ FILE: integration_tests/protocols/ssl/multi-req.yaml ================================================ id: multi-req info: name: Multi-Request author: pdteam severity: info ssl: - address: "{{Host}}:{{Port}}" min_version: ssl30 max_version: ssl30 extractors: - type: json json: - " .tls_version" - address: "{{Host}}:{{Port}}" min_version: tls10 max_version: tls10 extractors: - type: json json: - " .tls_version" - address: "{{Host}}:{{Port}}" min_version: tls11 max_version: tls11 extractors: - type: json json: - " .tls_version" ================================================ FILE: integration_tests/protocols/ssl/ssl-with-vars.yaml ================================================ id: ssl-with-vars info: name: SSL with variables author: pdteam severity: info tags: ssl ssl: - address: "{{Host}}:{{Port}}" matchers: - type: dsl dsl: - "print_debug(test)" ================================================ FILE: integration_tests/protocols/websocket/basic.yaml ================================================ id: basic-request info: name: Basic Request author: pdteam severity: info websocket: - address: '{{Scheme}}://{{Hostname}}' inputs: - data: hello matchers: - type: word words: - world part: response ================================================ FILE: integration_tests/protocols/websocket/cswsh.yaml ================================================ id: basic-cswsh-request info: name: Basic cswsh Request author: pdteam severity: info websocket: - address: '{{Scheme}}://{{Hostname}}' headers: Origin: 'http://evil.com' matchers: - type: word words: - true part: success ================================================ FILE: integration_tests/protocols/websocket/no-cswsh.yaml ================================================ id: basic-nocswsh-request info: name: Basic Non-Vulnerable cswsh Request author: pdteam severity: info websocket: - address: '{{Scheme}}://{{Hostname}}' headers: Origin: 'http://evil.com' matchers: - type: word words: - true part: success ================================================ FILE: integration_tests/protocols/websocket/path.yaml ================================================ id: basic-request-path info: name: Basic Request Path author: pdteam severity: info websocket: - address: '{{Scheme}}://{{Hostname}}' inputs: - data: hello matchers: - type: word words: - world part: response ================================================ FILE: integration_tests/protocols/whois/basic.yaml ================================================ id: basic-whois-example info: name: test template for WHOIS author: pdteam severity: info whois: - query: "{{Host}}" extractors: - type: kval kval: - "expiration date" - "registrar" ================================================ FILE: integration_tests/run.sh ================================================ #!/bin/bash echo "::group::Build nuclei" rm integration-test fuzzplayground nuclei 2>/dev/null cd ../cmd/nuclei go build -race . mv nuclei ../../integration_tests/nuclei echo "::endgroup::" echo "::group::Build nuclei integration-test" cd ../integration-test go build mv integration-test ../../integration_tests/integration-test cd ../../integration_tests echo "::endgroup::" echo "::group::Installing nuclei templates" ./nuclei -update-templates echo "::endgroup::" ./integration-test if [ $? -eq 0 ] then exit 0 else exit 1 fi ================================================ FILE: integration_tests/subdomains.txt ================================================ one docs drive play ================================================ FILE: integration_tests/test-issue-tracker-config1.yaml ================================================ 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 # 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 # 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 # 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 issue-type: bug # 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 ================================================ FILE: integration_tests/test-issue-tracker-config2.yaml ================================================ 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 # 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 # duplicate-issue-check flag to enable duplicate tracking issue check. duplicate-issue-check: true # 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 # 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 issue-type: bug # 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 ================================================ FILE: integration_tests/workflow/basic.yaml ================================================ id: workflow-example info: name: Test Workflow Template author: pdteam severity: info workflows: - template: workflow/match-1.yaml - template: workflow/match-2.yaml ================================================ FILE: integration_tests/workflow/code-template-1.yaml ================================================ id: code-template-1 info: name: code-template-1 author: tovask severity: info tags: code code: - engine: - py - python3 - python source: | print("hello from first") extractors: - type: regex name: extracted regex: - 'hello from (.*)' group: 1 # digest: 490a0046304402206b3648e8d393ac4df82c7d59b1a6ee3731c66c249dbd4d9bf31f0b7f176b37ec02203184d36373e516757c7d708b5799bc16edb1cebc0a64f3442d13ded4b33c42fb:4a3eb6b4988d95847d4203be25ed1d46 ================================================ FILE: integration_tests/workflow/code-template-2.yaml ================================================ id: code-template-2 info: name: code-template-2 author: tovask severity: info tags: code code: - engine: - py - python3 - python source: | import os print("hello from " + os.getenv("extracted")) matchers: - type: word words: - "hello from first" # digest: 490a0046304402204cbb1bdf8370e49bb930b17460fb35e15f285a3b48b165736ac0e7ba2f9bc0fb022067c134790c4a2cf646b195aa4488e2c222266436e6bda47931908a28807bdb81:4a3eb6b4988d95847d4203be25ed1d46 ================================================ FILE: integration_tests/workflow/code-value-share-workflow.yaml ================================================ id: code-value-sharing-workflow info: name: Code Value Sharing Workflow author: tovask severity: info tags: code workflows: - template: workflow/code-template-1.yaml subtemplates: - template: workflow/code-template-2.yaml ================================================ FILE: integration_tests/workflow/complex-conditions.yaml ================================================ id: complex-conditions-workflow info: name: Complex Conditions Workflow author: tovask severity: info description: Workflow to test a complex scenario, e.g. race conditions when evaluating the results of the templates workflows: - template: workflow/match-1.yaml subtemplates: - template: workflow/nomatch-1.yaml subtemplates: - template: workflow/match-2.yaml - template: workflow/match-3.yaml - template: workflow/match-2.yaml matchers: - name: test-matcher subtemplates: - template: workflow/nomatch-1.yaml subtemplates: - template: workflow/match-1.yaml - template: workflow/match-3.yaml ================================================ FILE: integration_tests/workflow/condition-matched.yaml ================================================ id: condition-matched-workflow info: name: Condition Matched Workflow author: pdteam severity: info workflows: - template: workflow/match-1.yaml subtemplates: - template: workflow/match-2.yaml ================================================ FILE: integration_tests/workflow/condition-unmatched.yaml ================================================ id: condition-unmatched-workflow info: name: Condition UnMatched Workflow author: pdteam severity: info workflows: - template: workflow/nomatch-1.yaml subtemplates: - template: workflow/match-2.yaml ================================================ FILE: integration_tests/workflow/dns-value-share-template-1.yaml ================================================ id: dns-value-sharing-template1 info: name: dns-value-sharing-template1 author: pdteam severity: info dns: - name: "{{FQDN}}" type: A extractors: - type: regex name: extracted group: 1 regex: - "IN\tA\t(.+)" ================================================ FILE: integration_tests/workflow/dns-value-share-template-2.yaml ================================================ id: dns-value-sharing-template2 info: name: dns-value-sharing-template2 author: pdteam severity: info dns: - name: "{{extracted}}" type: PTR ================================================ FILE: integration_tests/workflow/dns-value-share-template-3.yaml ================================================ id: value-sharing-template2 info: name: value-sharing-template2 author: pdteam severity: info http: - raw: - | GET / HTTP/1.1 Host: {{Hostname}} {{extracted}} matchers: - type: word words: - "ok" ================================================ FILE: integration_tests/workflow/dns-value-share-workflow.yaml ================================================ id: dns-value-sharing-workflow info: name: DNS Value Sharing Test author: pdteam severity: info workflows: - template: workflow/dns-value-share-template-1.yaml subtemplates: - template: workflow/dns-value-share-template-2.yaml - template: workflow/dns-value-share-template-3.yaml ================================================ FILE: integration_tests/workflow/headless-1.yaml ================================================ id: headless-1 info: name: Headless 1 author: pdteam severity: info tags: headless headless: - steps: - action: navigate args: url: "{{BaseURL}}/headless1" - action: waitload ================================================ FILE: integration_tests/workflow/http-1.yaml ================================================ id: http1 info: name: http1 author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}/http1" ================================================ FILE: integration_tests/workflow/http-2.yaml ================================================ id: http2 info: name: http2 author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}/http2" ================================================ FILE: integration_tests/workflow/http-3.yaml ================================================ id: http3 info: name: http3 author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}/http3" ================================================ FILE: integration_tests/workflow/http-value-share-template-1.yaml ================================================ id: value-sharing-template1 info: name: value-sharing-template1 author: pdteam severity: info http: - path: - "{{BaseURL}}/path1" extractors: - type: regex part: body name: extracted regex: - 'href="(.*)"' group: 1 ================================================ FILE: integration_tests/workflow/http-value-share-template-2.yaml ================================================ id: value-sharing-template2 info: name: value-sharing-template2 author: pdteam severity: info http: - raw: - | GET /path2 HTTP/1.1 Host: {{Hostname}} {{extracted}} matchers: - type: word words: - "test-value" ================================================ FILE: integration_tests/workflow/http-value-share-workflow.yaml ================================================ id: http-value-sharing-workflow info: name: HTTP Value Sharing Test author: pdteam severity: info workflows: - template: workflow/http-value-share-template-1.yaml subtemplates: - template: workflow/http-value-share-template-2.yaml ================================================ FILE: integration_tests/workflow/match-1.yaml ================================================ id: basic-get info: name: Basic GET Request author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" matchers: - type: word words: - "This is test matcher text" ================================================ FILE: integration_tests/workflow/match-2.yaml ================================================ id: basic-get-another info: name: Basic Another GET Request author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" matchers: - type: word name: test-matcher words: - "This is test matcher text" ================================================ FILE: integration_tests/workflow/match-3.yaml ================================================ id: basic-get-third info: name: Basic 3rd GET Request author: tovask severity: info http: - method: GET path: - "{{BaseURL}}" matchers: - type: word name: test-matcher-3 words: - "This is test matcher text" ================================================ FILE: integration_tests/workflow/matcher-name.yaml ================================================ id: matcher-name-workflow info: name: Matcher Name Workflow author: pdteam severity: info workflows: - template: workflow/match-2.yaml matchers: - name: test-matcher subtemplates: - template: workflow/match-1.yaml ================================================ FILE: integration_tests/workflow/multimatch-value-share-template.yaml ================================================ id: multimatch-value-share-template info: name: MultiMatch Value Share Template author: tovask severity: info http: - path: - "{{BaseURL}}/path1?v=1" - "{{BaseURL}}/path1?v=2" matchers: - type: word name: test-matcher words: - "href" extractors: - type: regex part: body name: extracted regex: - 'href="(.*)"' group: 1 ================================================ FILE: integration_tests/workflow/multimatch-value-share-workflow.yaml ================================================ id: multimatch-value-share-workflow info: name: MultiMatch Value Share Workflow author: tovask severity: info description: Workflow to test value sharing when multiple matches occur in the extractor template workflows: - template: workflow/multimatch-value-share-template.yaml subtemplates: - template: workflow/match-1.yaml subtemplates: - template: workflow/http-value-share-template-2.yaml - template: workflow/multimatch-value-share-template.yaml matchers: - name: test-matcher subtemplates: - template: workflow/match-1.yaml subtemplates: - template: workflow/http-value-share-template-2.yaml ================================================ FILE: integration_tests/workflow/multiprotocol-value-share-template.yaml ================================================ id: multiprotocol-value-sharing-template info: name: MultiProtocol Value Sharing Template author: tovask severity: info dns: - name: "{{extracted}}" type: PTR matchers: - type: word words: - "blog.projectdiscovery.io" http: - path: - "{{BaseURL}}/path2?extracted={{extracted}}" matchers: - type: word words: - "blog.projectdiscovery.io" ================================================ FILE: integration_tests/workflow/multiprotocol-value-share-workflow.yaml ================================================ id: multiprotocol-value-sharing-workflow info: name: MultiProtocol Value Sharing Workflow author: tovask severity: info workflows: - template: workflow/http-value-share-template-1.yaml subtemplates: - template: workflow/multiprotocol-value-share-template.yaml ================================================ FILE: integration_tests/workflow/nomatch-1.yaml ================================================ id: basic-get-nomatch info: name: Basic GET Request NoMatch author: pdteam severity: info http: - method: GET path: - "{{BaseURL}}" matchers: - type: word words: - "Random" ================================================ FILE: integration_tests/workflow/shared-cookie.yaml ================================================ id: workflow-shared-cookies info: name: Test Workflow Shared Cookies author: pdteam severity: info workflows: # store cookies to standard http client cookie-jar - template: workflow/http-1.yaml - template: workflow/http-2.yaml # store cookie in native browser context - template: workflow/headless-1.yaml # retrieve 2 standard library cookies + headless cookie - template: workflow/http-3.yaml ================================================ FILE: internal/colorizer/colorizer.go ================================================ package colorizer import ( "fmt" "github.com/logrusorgru/aurora" "github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity" ) const ( fgOrange uint8 = 208 ) func GetColor(colorizer aurora.Aurora, templateSeverity fmt.Stringer) string { var method func(arg interface{}) aurora.Value switch templateSeverity { case severity.Info: method = colorizer.Blue case severity.Low: method = colorizer.Green case severity.Medium: method = colorizer.Yellow case severity.High: method = func(stringValue interface{}) aurora.Value { return colorizer.Index(fgOrange, stringValue) } case severity.Critical: method = colorizer.Red default: method = colorizer.White } return method(templateSeverity.String()).String() } func New(colorizer aurora.Aurora) func(severity.Severity) string { return func(severity severity.Severity) string { return GetColor(colorizer, severity) } } ================================================ FILE: internal/httpapi/apiendpoint.go ================================================ package httpapi import ( "net/http" "time" "github.com/projectdiscovery/nuclei/v3/pkg/js/compiler" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/nuclei/v3/pkg/utils/json" ) type Concurrency struct { BulkSize int `json:"bulk_size"` Threads int `json:"threads"` RateLimit int `json:"rate_limit"` RateLimitDuration string `json:"rate_limit_duration"` PayloadConcurrency int `json:"payload_concurrency"` ProbeConcurrency int `json:"probe_concurrency"` JavascriptConcurrency int `json:"javascript_concurrency"` } // Server represents the HTTP server that handles the concurrency settings endpoints. type Server struct { addr string config *types.Options } // New creates a new instance of Server. func New(addr string, config *types.Options) *Server { return &Server{ addr: addr, config: config, } } // Start initializes the server and its routes, then starts listening on the specified address. func (s *Server) Start() error { http.HandleFunc("/api/concurrency", s.handleConcurrency) if err := http.ListenAndServe(s.addr, nil); err != nil { return err } return nil } // handleConcurrency routes the request based on its method to the appropriate handler. func (s *Server) handleConcurrency(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: s.getSettings(w, r) case http.MethodPut: s.updateSettings(w, r) default: http.Error(w, "Unsupported HTTP method", http.StatusMethodNotAllowed) } } // GetSettings handles GET requests and returns the current concurrency settings func (s *Server) getSettings(w http.ResponseWriter, _ *http.Request) { concurrencySettings := Concurrency{ BulkSize: s.config.BulkSize, Threads: s.config.TemplateThreads, RateLimit: s.config.RateLimit, RateLimitDuration: s.config.RateLimitDuration.String(), PayloadConcurrency: s.config.PayloadConcurrency, ProbeConcurrency: s.config.ProbeConcurrency, JavascriptConcurrency: compiler.PoolingJsVmConcurrency, } w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(concurrencySettings); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } // UpdateSettings handles PUT requests to update the concurrency settings func (s *Server) updateSettings(w http.ResponseWriter, r *http.Request) { var newSettings Concurrency if err := json.NewDecoder(r.Body).Decode(&newSettings); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if newSettings.RateLimitDuration != "" { if duration, err := time.ParseDuration(newSettings.RateLimitDuration); err == nil { s.config.RateLimitDuration = duration } else { http.Error(w, "Invalid duration format", http.StatusBadRequest) return } } if newSettings.BulkSize > 0 { s.config.BulkSize = newSettings.BulkSize } if newSettings.Threads > 0 { s.config.TemplateThreads = newSettings.Threads } if newSettings.RateLimit > 0 { s.config.RateLimit = newSettings.RateLimit } if newSettings.PayloadConcurrency > 0 { s.config.PayloadConcurrency = newSettings.PayloadConcurrency } if newSettings.ProbeConcurrency > 0 { s.config.ProbeConcurrency = newSettings.ProbeConcurrency } if newSettings.JavascriptConcurrency > 0 { compiler.PoolingJsVmConcurrency = newSettings.JavascriptConcurrency s.config.JsConcurrency = newSettings.JavascriptConcurrency // no-op on speed change } w.WriteHeader(http.StatusOK) } ================================================ FILE: internal/pdcp/utils.go ================================================ package pdcp import ( pdcpauth "github.com/projectdiscovery/utils/auth/pdcp" urlutil "github.com/projectdiscovery/utils/url" ) func getScanDashBoardURL(id string, teamID string) string { ux, _ := urlutil.Parse(pdcpauth.DashBoardURL) ux.Path = "/scans/" + id if ux.Params == nil { ux.Params = urlutil.NewOrderedParams() } if teamID != "" { ux.Params.Add("team_id", teamID) } else { ux.Params.Add("team_id", NoneTeamID) } ux.Update() return ux.String() } type uploadResponse struct { ID string `json:"id"` Message string `json:"message"` } ================================================ FILE: internal/pdcp/writer.go ================================================ package pdcp import ( "bufio" "bytes" "context" "fmt" "io" "net/http" "net/url" "regexp" "sync/atomic" "time" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/utils/json" "github.com/projectdiscovery/retryablehttp-go" pdcpauth "github.com/projectdiscovery/utils/auth/pdcp" "github.com/projectdiscovery/utils/env" "github.com/projectdiscovery/utils/errkit" unitutils "github.com/projectdiscovery/utils/unit" updateutils "github.com/projectdiscovery/utils/update" urlutil "github.com/projectdiscovery/utils/url" ) const ( uploadEndpoint = "/v1/scans/import" appendEndpoint = "/v1/scans/%s/import" flushTimer = time.Minute MaxChunkSize = 4 * unitutils.Mega // 4 MB xidRe = `^[a-z0-9]{20}$` teamIDHeader = "X-Team-Id" NoneTeamID = "none" ) var ( xidRegex = regexp.MustCompile(xidRe) _ output.Writer = &UploadWriter{} // teamID if given TeamIDEnv = env.GetEnvOrDefault("PDCP_TEAM_ID", NoneTeamID) ) // UploadWriter is a writer that uploads its output to pdcp // server to enable web dashboard and more type UploadWriter struct { *output.StandardWriter creds *pdcpauth.PDCPCredentials uploadURL *url.URL client *retryablehttp.Client cancel context.CancelFunc done chan struct{} scanID string scanName string counter atomic.Int32 TeamID string Logger *gologger.Logger } // NewUploadWriter creates a new upload writer func NewUploadWriter(ctx context.Context, logger *gologger.Logger, creds *pdcpauth.PDCPCredentials) (*UploadWriter, error) { if creds == nil { return nil, fmt.Errorf("no credentials provided") } u := &UploadWriter{ creds: creds, done: make(chan struct{}, 1), TeamID: NoneTeamID, Logger: logger, } var err error reader, writer := io.Pipe() // create standard writer u.StandardWriter, err = output.NewWriter( output.WithWriter(writer), output.WithJson(true, true), ) if err != nil { return nil, errkit.Wrap(err, "could not create output writer") } tmp, err := urlutil.Parse(creds.Server) if err != nil { return nil, errkit.Wrap(err, "could not parse server url") } tmp.Path = uploadEndpoint tmp.Update() u.uploadURL = tmp.URL // create http client opts := retryablehttp.DefaultOptionsSingle opts.NoAdjustTimeout = true opts.Timeout = time.Duration(3) * time.Minute u.client = retryablehttp.NewClient(opts) // create context ctx, u.cancel = context.WithCancel(ctx) // start auto commit // upload every 1 minute or when buffer is full go u.autoCommit(ctx, reader) return u, nil } // SetScanID sets the scan id for the upload writer func (u *UploadWriter) SetScanID(id string) error { if !xidRegex.MatchString(id) { gologger.Warning().Msgf("invalid asset id provided (unknown xid format): %s", id) } u.scanID = id return nil } // SetScanName sets the scan name for the upload writer func (u *UploadWriter) SetScanName(name string) { u.scanName = name } func (u *UploadWriter) SetTeamID(id string) { if id == "" { u.TeamID = NoneTeamID } else { u.TeamID = id } } func (u *UploadWriter) autoCommit(ctx context.Context, r *io.PipeReader) { reader := bufio.NewReader(r) ch := make(chan string, 4) // continuously read from the reader and send to channel go func() { defer func() { _ = r.Close() }() defer close(ch) for { data, err := reader.ReadString('\n') if err != nil { return } u.counter.Add(1) ch <- data } }() // wait for context to be done defer func() { u.done <- struct{}{} close(u.done) // if no scanid is generated no results were uploaded if u.scanID == "" { u.Logger.Verbose().Msgf("Scan results upload to cloud skipped, no results found to upload") } else { u.Logger.Info().Msgf("%v Scan results uploaded to cloud, you can view scan results at %v", u.counter.Load(), getScanDashBoardURL(u.scanID, u.TeamID)) } }() // temporary buffer to store the results buff := &bytes.Buffer{} ticker := time.NewTicker(flushTimer) defer ticker.Stop() for { select { case <-ctx.Done(): // flush before exit if buff.Len() > 0 { if err := u.uploadChunk(buff); err != nil { u.Logger.Error().Msgf("Failed to upload scan results on cloud: %v", err) } } return case <-ticker.C: // flush the buffer if buff.Len() > 0 { if err := u.uploadChunk(buff); err != nil { u.Logger.Error().Msgf("Failed to upload scan results on cloud: %v", err) } } case line, ok := <-ch: if !ok { if buff.Len() > 0 { if err := u.uploadChunk(buff); err != nil { u.Logger.Error().Msgf("Failed to upload scan results on cloud: %v", err) } } return } if buff.Len()+len(line) > MaxChunkSize { // flush existing buffer if err := u.uploadChunk(buff); err != nil { u.Logger.Error().Msgf("Failed to upload scan results on cloud: %v", err) } } else { buff.WriteString(line) } } } } // uploadChunk uploads a chunk of data to the server func (u *UploadWriter) uploadChunk(buff *bytes.Buffer) error { if err := u.upload(buff.Bytes()); err != nil { return errkit.Wrap(err, "could not upload chunk") } // if successful, reset the buffer buff.Reset() // log in verbose mode u.Logger.Warning().Msgf("Uploaded results chunk, you can view scan results at %v", getScanDashBoardURL(u.scanID, u.TeamID)) return nil } func (u *UploadWriter) upload(data []byte) error { req, err := u.getRequest(data) if err != nil { return errkit.Wrap(err, "could not create upload request") } resp, err := u.client.Do(req) if err != nil { return errkit.Wrap(err, "could not upload results") } defer func() { _ = resp.Body.Close() }() bin, err := io.ReadAll(resp.Body) if err != nil { return errkit.Wrap(err, "could not get id from response") } if resp.StatusCode != http.StatusOK { return fmt.Errorf("could not upload results got status code %v on %v", resp.StatusCode, resp.Request.URL.String()) } var uploadResp uploadResponse if err := json.Unmarshal(bin, &uploadResp); err != nil { return errkit.Wrap(err, fmt.Sprintf("could not unmarshal response got %v", string(bin))) } if uploadResp.ID != "" && u.scanID == "" { u.scanID = uploadResp.ID } return nil } // getRequest returns a new request for upload // if scanID is not provided create new scan by uploading the data // if scanID is provided append the data to existing scan func (u *UploadWriter) getRequest(bin []byte) (*retryablehttp.Request, error) { var method, url string if u.scanID == "" { u.uploadURL.Path = uploadEndpoint method = http.MethodPost url = u.uploadURL.String() } else { u.uploadURL.Path = fmt.Sprintf(appendEndpoint, u.scanID) method = http.MethodPatch url = u.uploadURL.String() } req, err := retryablehttp.NewRequest(method, url, bytes.NewReader(bin)) if err != nil { return nil, errkit.Wrap(err, "could not create cloud upload request") } // add pdtm meta params req.Params.Merge(updateutils.GetpdtmParams(config.Version)) // if it is upload endpoint also include name if it exists if u.scanName != "" && req.Path == uploadEndpoint { req.Params.Add("name", u.scanName) } req.Update() req.Header.Set(pdcpauth.ApiKeyHeaderName, u.creds.APIKey) if u.TeamID != NoneTeamID && u.TeamID != "" { req.Header.Set(teamIDHeader, u.TeamID) } req.Header.Set("Content-Type", "application/octet-stream") req.Header.Set("Accept", "application/json") return req, nil } // Close closes the upload writer func (u *UploadWriter) Close() { u.cancel() <-u.done u.StandardWriter.Close() } ================================================ FILE: internal/runner/banner.go ================================================ // Package runner executes the enumeration process. package runner import ( "fmt" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" pdcpauth "github.com/projectdiscovery/utils/auth/pdcp" updateutils "github.com/projectdiscovery/utils/update" ) var banner = fmt.Sprintf(` __ _ ____ __ _______/ /__ (_) / __ \/ / / / ___/ / _ \/ / / / / / /_/ / /__/ / __/ / /_/ /_/\__,_/\___/_/\___/_/ %s `, config.Version) // showBanner is used to show the banner to the user func showBanner() { gologger.Print().Msgf("%s\n", banner) gologger.Print().Msgf("\t\tprojectdiscovery.io\n\n") } // NucleiToolUpdateCallback updates nuclei binary/tool to latest version func NucleiToolUpdateCallback() { showBanner() updateutils.GetUpdateToolCallback(config.BinaryName, config.Version)() } // AuthWithPDCP is used to authenticate with PDCP func AuthWithPDCP() { showBanner() pdcpauth.CheckNValidateCredentials(config.BinaryName) } ================================================ FILE: internal/runner/healthcheck.go ================================================ package runner import ( "fmt" "net" "runtime" "strings" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/types" fileutil "github.com/projectdiscovery/utils/file" ) // DoHealthCheck performs self-diagnostic checks func DoHealthCheck(options *types.Options) string { // RW permissions on config file var test strings.Builder fmt.Fprintf(&test, "Version: %s\n", config.Version) fmt.Fprintf(&test, "Operating System: %s\n", runtime.GOOS) fmt.Fprintf(&test, "Architecture: %s\n", runtime.GOARCH) fmt.Fprintf(&test, "Go Version: %s\n", runtime.Version()) fmt.Fprintf(&test, "Compiler: %s\n", runtime.Compiler) var testResult string cfg := config.DefaultConfig for _, filename := range []string{cfg.GetFlagsConfigFilePath(), cfg.GetIgnoreFilePath(), cfg.GetChecksumFilePath()} { ok, err := fileutil.IsReadable(filename) if ok { testResult = "Ok" } else { testResult = "Ko" } if err != nil { testResult += fmt.Sprintf(" (%s)", err) } fmt.Fprintf(&test, "File \"%s\" Read => %s\n", filename, testResult) ok, err = fileutil.IsWriteable(filename) if ok { testResult = "Ok" } else { testResult = "Ko" } if err != nil { testResult += fmt.Sprintf(" (%s)", err) } fmt.Fprintf(&test, "File \"%s\" Write => %s\n", filename, testResult) } c4, err := net.Dial("tcp4", "scanme.sh:80") if err == nil && c4 != nil { _ = c4.Close() } testResult = "Ok" if err != nil { testResult = fmt.Sprintf("Ko (%s)", err) } fmt.Fprintf(&test, "IPv4 connectivity to scanme.sh:80 => %s\n", testResult) c6, err := net.Dial("tcp6", "scanme.sh:80") if err == nil && c6 != nil { _ = c6.Close() } testResult = "Ok" if err != nil { testResult = fmt.Sprintf("Ko (%s)", err) } fmt.Fprintf(&test, "IPv6 connectivity to scanme.sh:80 => %s\n", testResult) u4, err := net.Dial("udp4", "scanme.sh:53") if err == nil && u4 != nil { _ = u4.Close() } testResult = "Ok" if err != nil { testResult = fmt.Sprintf("Ko (%s)", err) } fmt.Fprintf(&test, "IPv4 UDP connectivity to scanme.sh:53 => %s\n", testResult) return test.String() } ================================================ FILE: internal/runner/inputs.go ================================================ package runner import ( "context" "fmt" "sync/atomic" "time" "github.com/pkg/errors" "github.com/projectdiscovery/hmap/store/hybrid" "github.com/projectdiscovery/httpx/common/httpx" "github.com/projectdiscovery/nuclei/v3/pkg/input/provider" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" "github.com/projectdiscovery/nuclei/v3/pkg/utils" stringsutil "github.com/projectdiscovery/utils/strings" syncutil "github.com/projectdiscovery/utils/sync" ) // initializeTemplatesHTTPInput initializes the http form of input // for any loaded http templates if input is in non-standard format. func (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) { hm, err := hybrid.New(hybrid.DefaultDiskOptions) if err != nil { return nil, errors.Wrap(err, "could not create temporary input file") } if r.inputProvider.InputType() == provider.MultiFormatInputProvider { // currently http probing for input mode types is not supported return hm, nil } r.Logger.Info().Msgf("Running httpx on input host") httpxOptions := httpx.DefaultOptions if r.options.AliveHttpProxy != "" { httpxOptions.Proxy = r.options.AliveHttpProxy } else if r.options.AliveSocksProxy != "" { httpxOptions.Proxy = r.options.AliveSocksProxy } httpxOptions.RetryMax = r.options.Retries httpxOptions.Timeout = time.Duration(r.options.Timeout) * time.Second dialers := protocolstate.GetDialersWithId(r.options.ExecutionId) if dialers == nil { return nil, fmt.Errorf("dialers not initialized for %s", r.options.ExecutionId) } httpxOptions.NetworkPolicy = dialers.NetworkPolicy httpxClient, err := httpx.New(&httpxOptions) if err != nil { return nil, errors.Wrap(err, "could not create httpx client") } // Probe the non-standard URLs and store them in cache swg, err := syncutil.New(syncutil.WithSize(r.options.BulkSize)) if err != nil { return nil, errors.Wrap(err, "could not create adaptive group") } var count atomic.Int32 r.inputProvider.Iterate(func(value *contextargs.MetaInput) bool { if stringsutil.HasPrefixAny(value.Input, "http://", "https://") { return true } if r.options.ProbeConcurrency > 0 && swg.Size != r.options.ProbeConcurrency { if err := swg.Resize(context.Background(), r.options.ProbeConcurrency); err != nil { r.Logger.Error().Msgf("Could not resize workpool: %s\n", err) } } swg.Add() go func(input *contextargs.MetaInput) { defer swg.Done() if result := utils.ProbeURL(input.Input, httpxClient); result != "" { count.Add(1) _ = hm.Set(input.Input, []byte(result)) } }(value) return true }) swg.Wait() r.Logger.Info().Msgf("Found %d URL from httpx", count.Load()) return hm, nil } ================================================ FILE: internal/runner/lazy.go ================================================ package runner import ( "context" "fmt" "strings" "github.com/projectdiscovery/nuclei/v3/pkg/authprovider/authx" "github.com/projectdiscovery/nuclei/v3/pkg/catalog" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/protocols" "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/helpers/writer" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/replacer" "github.com/projectdiscovery/nuclei/v3/pkg/scan" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/utils/env" "github.com/projectdiscovery/utils/errkit" ) type AuthLazyFetchOptions struct { TemplateStore *loader.Store ExecOpts *protocols.ExecutorOptions OnError func(error) } // GetAuthTmplStore create new loader for loading auth templates func GetAuthTmplStore(opts *types.Options, catalog catalog.Catalog, execOpts *protocols.ExecutorOptions) (*loader.Store, error) { tmpls := []string{} for _, file := range opts.SecretsFile { data, err := authx.GetTemplatePathsFromSecretFile(file) if err != nil { return nil, errkit.Wrap(err, "failed to get template paths from secrets file") } tmpls = append(tmpls, data...) } opts.Templates = tmpls opts.Workflows = nil opts.RemoteTemplateDomainList = nil opts.TemplateURLs = nil opts.WorkflowURLs = nil opts.ExcludedTemplates = nil opts.Tags = nil opts.ExcludeTags = nil opts.IncludeTemplates = nil opts.Authors = nil opts.Severities = nil opts.ExcludeSeverities = nil opts.IncludeTags = nil opts.IncludeIds = nil opts.ExcludeIds = nil opts.Protocols = nil opts.ExcludeProtocols = nil opts.IncludeConditions = nil cfg := loader.NewConfig(opts, catalog, execOpts) cfg.StoreId = loader.AuthStoreId store, err := loader.New(cfg) if err != nil { return nil, errkit.Wrap(err, "failed to initialize dynamic auth templates store") } return store, nil } // GetLazyAuthFetchCallback returns a lazy fetch callback for auth secrets func GetLazyAuthFetchCallback(opts *AuthLazyFetchOptions) authx.LazyFetchSecret { return func(d *authx.Dynamic) error { tmpls, err := opts.TemplateStore.LoadTemplates([]string{d.TemplatePath}) if err != nil { return fmt.Errorf("failed to load templates: %w", err) } if len(tmpls) == 0 { return fmt.Errorf("%w for path: %s", disk.ErrNoTemplatesFound, d.TemplatePath) } if len(tmpls) > 1 { return fmt.Errorf("multiple templates found for path: %s", d.TemplatePath) } data := map[string]interface{}{} tmpl := tmpls[0] // add args to tmpl here vars := map[string]interface{}{} mainCtx := context.Background() ctx := scan.NewScanContext(mainCtx, contextargs.NewWithInput(mainCtx, d.Input)) cliVars := map[string]interface{}{} if opts.ExecOpts.Options != nil { // gets variables passed from cli -v and -env-vars cliVars = generators.BuildPayloadFromOptions(opts.ExecOpts.Options) } for _, v := range d.Variables { // Check if the template has any env variables and expand them if strings.HasPrefix(v.Value, "$") { env.ExpandWithEnv(&v.Value) } if strings.Contains(v.Value, "{{") { // if variables had value like {{username}}, then replace it with the value from cliVars // variables: // - key: username // value: {{username}} v.Value = replacer.Replace(v.Value, cliVars) } vars[v.Key] = v.Value ctx.Input.Add(v.Key, v.Value) } var finalErr error ctx.OnResult = func(e *output.InternalWrappedEvent) { if e == nil { finalErr = fmt.Errorf("no result found for template: %s", d.TemplatePath) return } if !e.HasOperatorResult() { finalErr = fmt.Errorf("no result found for template: %s", d.TemplatePath) return } // dynamic values for k, v := range e.OperatorsResult.DynamicValues { // Iterate through all the values and choose the // largest value as the extracted value for _, value := range v { oldVal, ok := data[k] if !ok || len(value) > len(oldVal.(string)) { data[k] = value } } } // named extractors for k, v := range e.OperatorsResult.Extracts { if len(v) > 0 { data[k] = v[0] } } if len(data) == 0 { if e.OperatorsResult.Matched { finalErr = fmt.Errorf("match found but no (dynamic/extracted) values found for template: %s", d.TemplatePath) } else { finalErr = fmt.Errorf("no match or (dynamic/extracted) values found for template: %s", d.TemplatePath) } } // log result of template in result file/screen _ = writer.WriteResult(e, opts.ExecOpts.Output, opts.ExecOpts.Progress, opts.ExecOpts.IssuesClient) } _, execErr := tmpl.Executer.ExecuteWithResults(ctx) if execErr != nil { finalErr = execErr } // store extracted result in auth context d.Extracted = data if finalErr != nil && opts.OnError != nil { opts.OnError(finalErr) } return finalErr } } ================================================ FILE: internal/runner/options.go ================================================ package runner import ( "bufio" "fmt" "io/fs" "os" "path/filepath" "strconv" "strings" "github.com/pkg/errors" "github.com/go-playground/validator/v10" "github.com/projectdiscovery/goflags" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/gologger/formatter" "github.com/projectdiscovery/gologger/levels" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine" "github.com/projectdiscovery/nuclei/v3/pkg/reporting" "github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/jsonexporter" "github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/jsonl" "github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown" "github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/pdf" "github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/sarif" "github.com/projectdiscovery/nuclei/v3/pkg/templates/extensions" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml" fileutil "github.com/projectdiscovery/utils/file" "github.com/projectdiscovery/utils/generic" stringsutil "github.com/projectdiscovery/utils/strings" ) const ( // Default directory used to save protocols traffic DefaultDumpTrafficOutputFolder = "output" ) var validateOptions = validator.New() func ConfigureOptions() error { // with FileStringSliceOptions, FileNormalizedStringSliceOptions, FileCommaSeparatedStringSliceOptions // if file has the extension `.yaml` or `.json` we consider those as strings and not files to be read isFromFileFunc := func(s string) bool { return !config.IsTemplate(s) } goflags.FileNormalizedStringSliceOptions.IsFromFile = isFromFileFunc goflags.FileStringSliceOptions.IsFromFile = isFromFileFunc goflags.FileCommaSeparatedStringSliceOptions.IsFromFile = isFromFileFunc return nil } // ParseOptions parses the command line flags provided by a user func ParseOptions(options *types.Options) { // Check if stdin pipe was given options.Stdin = !options.DisableStdin && fileutil.HasStdin() // Read the inputs from env variables that not passed by flag. readEnvInputVars(options) // Read the inputs and configure the logging configureOutput(options) // Show the user the banner showBanner() if options.ShowVarDump { vardump.EnableVarDump = true vardump.Limit = options.VarDumpLimit } if options.ShowActions { options.Logger.Info().Msgf("Showing available headless actions: ") for action := range engine.ActionStringToAction { options.Logger.Print().Msgf("\t%s", action) } os.Exit(0) } defaultProfilesPath := filepath.Join(config.DefaultConfig.GetTemplateDir(), "profiles") if options.ListTemplateProfiles { options.Logger.Print().Msgf( "Listing available %v nuclei template profiles for %v", config.DefaultConfig.TemplateVersion, config.DefaultConfig.TemplatesDirectory, ) templatesRootDir := config.DefaultConfig.GetTemplateDir() err := filepath.WalkDir(defaultProfilesPath, 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 { return nil } if profileRelPath, err := filepath.Rel(templatesRootDir, iterItem); err == nil { options.Logger.Print().Msgf("%s (%s)\n", profileRelPath, strings.TrimSuffix(filepath.Base(iterItem), ext)) } return nil }) if err != nil { options.Logger.Error().Msgf("%s\n", err) } os.Exit(0) } if options.StoreResponseDir != DefaultDumpTrafficOutputFolder && !options.StoreResponse { options.Logger.Debug().Msgf("Store response directory specified, enabling \"store-resp\" flag automatically\n") options.StoreResponse = true } // Validate the options passed by the user and if any // invalid options have been used, exit. if err := ValidateOptions(options); err != nil { options.Logger.Fatal().Msgf("Program exiting: %s\n", err) } // Load the resolvers if user asked for them loadResolvers(options) err := protocolinit.Init(options) if err != nil { options.Logger.Fatal().Msgf("Could not initialize protocols: %s\n", err) } // Set GitHub token in env variable. runner.getGHClientWithToken() reads token from env if options.GitHubToken != "" && os.Getenv("GITHUB_TOKEN") != options.GitHubToken { _ = os.Setenv("GITHUB_TOKEN", options.GitHubToken) } if options.UncoverQuery != nil { options.Uncover = true if len(options.UncoverEngine) == 0 { options.UncoverEngine = append(options.UncoverEngine, "shodan") } } if options.OfflineHTTP { options.DisableHTTPProbe = true } } // validateOptions validates the configuration options passed func ValidateOptions(options *types.Options) error { if err := validateOptions.Struct(options); err != nil { if _, ok := err.(*validator.InvalidValidationError); ok { return err } errs := []string{} for _, err := range err.(validator.ValidationErrors) { errs = append(errs, err.Namespace()+": "+err.Tag()) } return errors.Wrap(errors.New(strings.Join(errs, ", ")), "validation failed for these fields") } if options.Verbose && options.Silent { return errors.New("both verbose and silent mode specified") } if (options.HeadlessOptionalArguments != nil || options.ShowBrowser || options.UseInstalledChrome) && !options.Headless { return errors.New("headless mode (-headless) is required if -ho, -sb, -sc or -lha are set") } if options.FollowHostRedirects && options.FollowRedirects { return errors.New("both follow host redirects and follow redirects specified") } if options.ShouldFollowHTTPRedirects() && options.DisableRedirects { return errors.New("both follow redirects and disable redirects specified") } // loading the proxy server list from file or cli and test the connectivity if err := loadProxyServers(options); err != nil { return err } if options.Validate { validateTemplatePaths(options.Logger, config.DefaultConfig.TemplatesDirectory, options.Templates, options.Workflows) } if options.DAST { if err := validateDASTOptions(options); err != nil { return err } } // Verify if any of the client certificate options were set since it requires all three to work properly if options.HasClientCertificates() { if generic.EqualsAny("", options.ClientCertFile, options.ClientKeyFile, options.ClientCAFile) { return errors.New("if a client certification option is provided, then all three must be provided") } validateCertificatePaths(options.Logger, options.ClientCertFile, options.ClientKeyFile, options.ClientCAFile) } // Verify AWS secrets are passed if a S3 template bucket is passed if options.AwsBucketName != "" && options.UpdateTemplates && !options.AwsTemplateDisableDownload { missing := validateMissingS3Options(options) if missing != nil { return fmt.Errorf("aws s3 bucket details are missing. Please provide %s", strings.Join(missing, ",")) } } // Verify Azure connection configuration is passed if the Azure template bucket is passed if options.AzureContainerName != "" && options.UpdateTemplates && !options.AzureTemplateDisableDownload { missing := validateMissingAzureOptions(options) if missing != nil { return fmt.Errorf("azure connection details are missing. Please provide %s", strings.Join(missing, ",")) } } // Verify that all GitLab options are provided if the GitLab server or token is provided if len(options.GitLabTemplateRepositoryIDs) != 0 && options.UpdateTemplates && !options.GitLabTemplateDisableDownload { missing := validateMissingGitLabOptions(options) if missing != nil { return fmt.Errorf("gitlab server details are missing. Please provide %s", strings.Join(missing, ",")) } } // verify that a valid ip version type was selected (4, 6) if len(options.IPVersion) == 0 { // add ipv4 as default options.IPVersion = append(options.IPVersion, "4") } var useIPV4, useIPV6 bool for _, ipv := range options.IPVersion { switch ipv { case "4": useIPV4 = true case "6": useIPV6 = true default: return fmt.Errorf("unsupported ip version: %s", ipv) } } if !useIPV4 && !useIPV6 { return errors.New("ipv4 and/or ipv6 must be selected") } return nil } func validateMissingS3Options(options *types.Options) []string { var missing []string if options.AwsBucketName == "" { missing = append(missing, "AWS_TEMPLATE_BUCKET") } if options.AwsProfile == "" { var missingCreds []string if options.AwsAccessKey == "" { missingCreds = append(missingCreds, "AWS_ACCESS_KEY") } if options.AwsSecretKey == "" { missingCreds = append(missingCreds, "AWS_SECRET_KEY") } if options.AwsRegion == "" { missingCreds = append(missingCreds, "AWS_REGION") } missing = append(missing, missingCreds...) if len(missingCreds) > 0 { missing = append(missing, "AWS_PROFILE") } } return missing } func validateMissingAzureOptions(options *types.Options) []string { var missing []string if options.AzureTenantID == "" { missing = append(missing, "AZURE_TENANT_ID") } if options.AzureClientID == "" { missing = append(missing, "AZURE_CLIENT_ID") } if options.AzureClientSecret == "" { missing = append(missing, "AZURE_CLIENT_SECRET") } if options.AzureServiceURL == "" { missing = append(missing, "AZURE_SERVICE_URL") } if options.AzureContainerName == "" { missing = append(missing, "AZURE_CONTAINER_NAME") } return missing } func validateMissingGitLabOptions(options *types.Options) []string { var missing []string if options.GitLabToken == "" { missing = append(missing, "GITLAB_TOKEN") } if len(options.GitLabTemplateRepositoryIDs) == 0 { missing = append(missing, "GITLAB_REPOSITORY_IDS") } return missing } func validateDASTOptions(options *types.Options) error { // Ensure the DAST server token meets minimum length requirement if len(options.DASTServerToken) > 0 && len(options.DASTServerToken) < 16 { return fmt.Errorf("DAST server token must be at least 16 characters long") } return nil } func createReportingOptions(options *types.Options) (*reporting.Options, error) { var reportingOptions = &reporting.Options{} if options.ReportingConfig != "" { file, err := os.Open(options.ReportingConfig) if err != nil { return nil, errors.Wrap(err, "could not open reporting config file") } defer func() { _ = file.Close() }() if err := yaml.DecodeAndValidate(file, reportingOptions); err != nil { return nil, errors.Wrap(err, "could not parse reporting config file") } Walk(reportingOptions, expandEndVars) } if options.MarkdownExportDirectory != "" { reportingOptions.MarkdownExporter = &markdown.Options{ Directory: options.MarkdownExportDirectory, OmitRaw: options.OmitRawRequests, SortMode: options.MarkdownExportSortMode, } } if options.SarifExport != "" { reportingOptions.SarifExporter = &sarif.Options{File: options.SarifExport} } if options.JSONExport != "" { reportingOptions.JSONExporter = &jsonexporter.Options{ File: options.JSONExport, OmitRaw: options.OmitRawRequests, } } // Combine options. if options.JSONLExport != "" { // Combine the CLI options with the config file options with the CLI options taking precedence if reportingOptions.JSONLExporter != nil { reportingOptions.JSONLExporter.File = options.JSONLExport reportingOptions.JSONLExporter.OmitRaw = options.OmitRawRequests } else { reportingOptions.JSONLExporter = &jsonl.Options{ File: options.JSONLExport, OmitRaw: options.OmitRawRequests, } } } if options.PDFExport != "" { if reportingOptions.PDFExporter != nil { reportingOptions.PDFExporter.File = options.PDFExport reportingOptions.PDFExporter.OmitRaw = options.OmitRawRequests } else { reportingOptions.PDFExporter = &pdf.Options{ File: options.PDFExport, OmitRaw: options.OmitRawRequests, } } } reportingOptions.OmitRaw = options.OmitRawRequests reportingOptions.ExecutionId = options.ExecutionId return reportingOptions, nil } // configureOutput configures the output logging levels to be displayed on the screen func configureOutput(options *types.Options) { if options.NoColor { options.Logger.SetFormatter(formatter.NewCLI(true)) } // If the user desires verbose output, show verbose output if options.Debug || options.DebugRequests || options.DebugResponse { options.Logger.SetMaxLevel(levels.LevelDebug) } // Debug takes precedence before verbose // because debug is a lower logging level. if options.Verbose || options.Validate { options.Logger.SetMaxLevel(levels.LevelVerbose) } if options.NoColor { options.Logger.SetFormatter(formatter.NewCLI(true)) } if options.Silent { options.Logger.SetMaxLevel(levels.LevelSilent) } // disable standard logger (ref: https://github.com/golang/go/issues/19895) // logutil.DisableDefaultLogger() } // loadResolvers loads resolvers from both user-provided flags and file func loadResolvers(options *types.Options) { if options.ResolversFile == "" { return } file, err := os.Open(options.ResolversFile) if err != nil { options.Logger.Fatal().Msgf("Could not open resolvers file: %s\n", err) } defer func() { _ = file.Close() }() scanner := bufio.NewScanner(file) for scanner.Scan() { part := scanner.Text() if part == "" { continue } if strings.Contains(part, ":") { options.InternalResolversList = append(options.InternalResolversList, part) } else { options.InternalResolversList = append(options.InternalResolversList, part+":53") } } } func validateTemplatePaths(logger *gologger.Logger, templatesDirectory string, templatePaths, workflowPaths []string) { allGivenTemplatePaths := append(templatePaths, workflowPaths...) for _, templatePath := range allGivenTemplatePaths { if templatesDirectory != templatePath && filepath.IsAbs(templatePath) { fileInfo, err := os.Stat(templatePath) if err == nil && fileInfo.IsDir() { relativizedPath, err2 := filepath.Rel(templatesDirectory, templatePath) if err2 != nil || (len(relativizedPath) >= 2 && relativizedPath[:2] == "..") { logger.Warning().Msgf("The given path (%s) is outside the default template directory path (%s)! "+ "Referenced sub-templates with relative paths in workflows will be resolved against the default template directory.", templatePath, templatesDirectory) break } } } } } func validateCertificatePaths(logger *gologger.Logger, certificatePaths ...string) { for _, certificatePath := range certificatePaths { if !fileutil.FileExists(certificatePath) { // The provided path to the PEM certificate does not exist for the client authentication. As this is // required for successful authentication, log and return an error logger.Fatal().Msgf("The given path (%s) to the certificate does not exist!", certificatePath) break } } } // Read the input from env and set options func readEnvInputVars(options *types.Options) { options.GitHubToken = os.Getenv("GITHUB_TOKEN") repolist := os.Getenv("GITHUB_TEMPLATE_REPO") if repolist != "" { options.GitHubTemplateRepo = append(options.GitHubTemplateRepo, stringsutil.SplitAny(repolist, ",")...) } // GitLab options for downloading templates from a repository options.GitLabServerURL = os.Getenv("GITLAB_SERVER_URL") if options.GitLabServerURL == "" { options.GitLabServerURL = "https://gitlab.com" } options.GitLabToken = os.Getenv("GITLAB_TOKEN") repolist = os.Getenv("GITLAB_REPOSITORY_IDS") // Convert the comma separated list of repository IDs to a list of integers if repolist != "" { for _, repoID := range stringsutil.SplitAny(repolist, ",") { // Attempt to convert the repo ID to an integer repoIDInt, err := strconv.Atoi(repoID) if err != nil { options.Logger.Warning().Msgf("Invalid GitLab template repository ID: %s", repoID) continue } // Add the int repository ID to the list options.GitLabTemplateRepositoryIDs = append(options.GitLabTemplateRepositoryIDs, repoIDInt) } } // AWS options for downloading templates from an S3 bucket options.AwsAccessKey = os.Getenv("AWS_ACCESS_KEY") options.AwsSecretKey = os.Getenv("AWS_SECRET_KEY") options.AwsBucketName = os.Getenv("AWS_TEMPLATE_BUCKET") options.AwsRegion = os.Getenv("AWS_REGION") options.AwsProfile = os.Getenv("AWS_PROFILE") // Azure options for downloading templates from an Azure Blob Storage container options.AzureContainerName = os.Getenv("AZURE_CONTAINER_NAME") options.AzureTenantID = os.Getenv("AZURE_TENANT_ID") options.AzureClientID = os.Getenv("AZURE_CLIENT_ID") options.AzureClientSecret = os.Getenv("AZURE_CLIENT_SECRET") options.AzureServiceURL = os.Getenv("AZURE_SERVICE_URL") // Custom public keys for template verification options.CodeTemplateSignaturePublicKey = os.Getenv("NUCLEI_SIGNATURE_PUBLIC_KEY") options.CodeTemplateSignatureAlgorithm = os.Getenv("NUCLEI_SIGNATURE_ALGORITHM") // General options to disable the template download locations from being used. // This will override the default behavior of downloading templates from the default locations as well as the // custom locations. // The primary use-case is when the user wants to use custom templates only and does not want to download any // templates from the default locations or is unable to connect to the public internet. options.PublicTemplateDisableDownload = getBoolEnvValue("DISABLE_NUCLEI_TEMPLATES_PUBLIC_DOWNLOAD") options.GitHubTemplateDisableDownload = getBoolEnvValue("DISABLE_NUCLEI_TEMPLATES_GITHUB_DOWNLOAD") options.GitLabTemplateDisableDownload = getBoolEnvValue("DISABLE_NUCLEI_TEMPLATES_GITLAB_DOWNLOAD") options.AwsTemplateDisableDownload = getBoolEnvValue("DISABLE_NUCLEI_TEMPLATES_AWS_DOWNLOAD") options.AzureTemplateDisableDownload = getBoolEnvValue("DISABLE_NUCLEI_TEMPLATES_AZURE_DOWNLOAD") // Options to modify the behavior of exporters options.MarkdownExportSortMode = strings.ToLower(os.Getenv("MARKDOWN_EXPORT_SORT_MODE")) // If the user has not specified a valid sort mode, use the default if options.MarkdownExportSortMode != "template" && options.MarkdownExportSortMode != "severity" && options.MarkdownExportSortMode != "host" { options.MarkdownExportSortMode = "" } } func getBoolEnvValue(key string) bool { value := os.Getenv(key) return strings.EqualFold(value, "true") } ================================================ FILE: internal/runner/options_test.go ================================================ package runner import ( "strings" "testing" "github.com/projectdiscovery/goflags" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/stretchr/testify/require" ) func TestParseHeadlessOptionalArguments(t *testing.T) { tests := []struct { name string input string want map[string]string }{ { name: "single value", input: "a=b", want: map[string]string{"a": "b"}, }, { name: "empty string", input: "", want: map[string]string{}, }, { name: "empty key", input: "=b", want: map[string]string{}, }, { name: "empty value", input: "a=", want: map[string]string{}, }, { name: "double input", input: "a=b,c=d", want: map[string]string{"a": "b", "c": "d"}, }, { name: "duplicated input", input: "a=b,a=b", want: map[string]string{"a": "b"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { strsl := goflags.StringSlice{} for _, v := range strings.Split(tt.input, ",") { //nolint strsl.Set(v) } opt := types.Options{HeadlessOptionalArguments: strsl} got := opt.ParseHeadlessOptionalArguments() require.Equal(t, tt.want, got) }) } } ================================================ FILE: internal/runner/proxy.go ================================================ package runner import ( "bufio" "fmt" "net/url" "os" "strings" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/utils/errkit" fileutil "github.com/projectdiscovery/utils/file" proxyutils "github.com/projectdiscovery/utils/proxy" ) const ( HTTP_PROXY_ENV = "HTTP_PROXY" ) // loadProxyServers load list of proxy servers from file or comma separated func loadProxyServers(options *types.Options) error { if len(options.Proxy) == 0 { return nil } proxyList := []string{} for _, p := range options.Proxy { if fileutil.FileExists(p) { file, err := os.Open(p) if err != nil { return fmt.Errorf("could not open proxy file: %w", err) } defer func() { _ = file.Close() }() scanner := bufio.NewScanner(file) for scanner.Scan() { proxy := scanner.Text() if strings.TrimSpace(proxy) == "" { continue } proxyList = append(proxyList, proxy) } } else { proxyList = append(proxyList, p) } } aliveProxy, err := proxyutils.GetAnyAliveProxy(options.Timeout, proxyList...) if err != nil { return err } proxyURL, err := url.Parse(aliveProxy) if err != nil { return errkit.Wrapf(err, "failed to parse proxy got %v", err) } if options.ProxyInternal { _ = os.Setenv(HTTP_PROXY_ENV, proxyURL.String()) } switch proxyURL.Scheme { case proxyutils.HTTP, proxyutils.HTTPS: options.Logger.Verbose().Msgf("Using %s as proxy server", proxyURL.String()) options.AliveHttpProxy = proxyURL.String() case proxyutils.SOCKS5: options.AliveSocksProxy = proxyURL.String() options.Logger.Verbose().Msgf("Using %s as socket proxy server", proxyURL.String()) } return nil } ================================================ FILE: internal/runner/runner.go ================================================ package runner import ( "context" "fmt" "os" "path/filepath" "reflect" "strings" "sync/atomic" "time" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/internal/pdcp" "github.com/projectdiscovery/nuclei/v3/internal/server" "github.com/projectdiscovery/nuclei/v3/pkg/authprovider" "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/frequency" "github.com/projectdiscovery/nuclei/v3/pkg/input/provider" "github.com/projectdiscovery/nuclei/v3/pkg/installer" "github.com/projectdiscovery/nuclei/v3/pkg/loader/parser" outputstats "github.com/projectdiscovery/nuclei/v3/pkg/output/stats" "github.com/projectdiscovery/nuclei/v3/pkg/scan/events" "github.com/projectdiscovery/nuclei/v3/pkg/utils/json" uncoverlib "github.com/projectdiscovery/uncover" pdcpauth "github.com/projectdiscovery/utils/auth/pdcp" "github.com/projectdiscovery/utils/env" fileutil "github.com/projectdiscovery/utils/file" permissionutil "github.com/projectdiscovery/utils/permission" pprofutil "github.com/projectdiscovery/utils/pprof" updateutils "github.com/projectdiscovery/utils/update" "github.com/logrusorgru/aurora" "github.com/pkg/errors" "github.com/projectdiscovery/ratelimit" "github.com/projectdiscovery/nuclei/v3/internal/colorizer" "github.com/projectdiscovery/nuclei/v3/internal/httpapi" "github.com/projectdiscovery/nuclei/v3/pkg/catalog" "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/external/customtemplates" fuzzStats "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/stats" "github.com/projectdiscovery/nuclei/v3/pkg/input" parsers "github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/progress" "github.com/projectdiscovery/nuclei/v3/pkg/projectfile" "github.com/projectdiscovery/nuclei/v3/pkg/protocols" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/automaticscan" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/globalmatchers" "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/uncover" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/excludematchers" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine" httpProtocol "github.com/projectdiscovery/nuclei/v3/pkg/protocols/http" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool" "github.com/projectdiscovery/nuclei/v3/pkg/reporting" "github.com/projectdiscovery/nuclei/v3/pkg/templates" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/nuclei/v3/pkg/utils" "github.com/projectdiscovery/nuclei/v3/pkg/utils/stats" "github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml" "github.com/projectdiscovery/retryablehttp-go" ptrutil "github.com/projectdiscovery/utils/ptr" ) var ( // HideAutoSaveMsg is a global variable to hide the auto-save message HideAutoSaveMsg = false // EnableCloudUpload is global variable to enable cloud upload EnableCloudUpload = false ) // Runner is a client for running the enumeration process. type Runner struct { output output.Writer interactsh *interactsh.Client options *types.Options projectFile *projectfile.ProjectFile catalog catalog.Catalog progress progress.Progress colorizer aurora.Aurora issuesClient reporting.Client browser *engine.Browser rateLimiter *ratelimit.Limiter hostErrors hosterrorscache.CacheInterface resumeCfg *types.ResumeCfg pprofServer *pprofutil.PprofServer pdcpUploadErrMsg string inputProvider provider.InputProvider fuzzFrequencyCache *frequency.Tracker httpStats *outputstats.Tracker Logger *gologger.Logger //general purpose temporary directory tmpDir string parser parser.Parser httpApiEndpoint *httpapi.Server fuzzStats *fuzzStats.Tracker dastServer *server.DASTServer } // New creates a new client for running the enumeration process. func New(options *types.Options) (*Runner, error) { runner := &Runner{ options: options, Logger: options.Logger, } if options.HealthCheck { runner.Logger.Print().Msgf("%s\n", DoHealthCheck(options)) os.Exit(0) } // Version check by default if config.DefaultConfig.CanCheckForUpdates() { if err := installer.NucleiVersionCheck(); err != nil { if options.Verbose || options.Debug { runner.Logger.Error().Msgf("nuclei version check failed got: %s\n", err) } } // check for custom template updates and update if available ctm, err := customtemplates.NewCustomTemplatesManager(options) if err != nil { runner.Logger.Error().Label("custom-templates").Msgf("Failed to create custom templates manager: %s\n", err) } // Check for template updates and update if available. // If the custom templates manager is not nil, we will install custom templates if there is a fresh installation tm := &installer.TemplateManager{ CustomTemplates: ctm, DisablePublicTemplates: options.PublicTemplateDisableDownload, } if err := tm.FreshInstallIfNotExists(); err != nil { runner.Logger.Warning().Msgf("failed to install nuclei templates: %s\n", err) } if err := tm.UpdateIfOutdated(); err != nil { runner.Logger.Warning().Msgf("failed to update nuclei templates: %s\n", err) } if config.DefaultConfig.NeedsIgnoreFileUpdate() { if err := installer.UpdateIgnoreFile(); err != nil { runner.Logger.Warning().Msgf("failed to update nuclei ignore file: %s\n", err) } } if options.UpdateTemplates { // we automatically check for updates unless explicitly disabled // this print statement is only to inform the user that there are no updates if !config.DefaultConfig.NeedsTemplateUpdate() { runner.Logger.Info().Msgf("No new updates found for nuclei templates") } // manually trigger update of custom templates if ctm != nil { ctm.Update(context.TODO()) } } } if op, ok := options.Parser.(*templates.Parser); ok { // Enable passing in an existing parser instance // This uses a type assertion to avoid an import loop runner.parser = op } else { parser := templates.NewParser() if options.Validate { parser.ShouldValidate = true } // TODO: refactor to pass options reference globally without cycles parser.NoStrictSyntax = options.NoStrictSyntax runner.parser = parser } yaml.StrictSyntax = !options.NoStrictSyntax if options.Headless { if engine.MustDisableSandbox() { runner.Logger.Warning().Msgf("The current platform and privileged user will run the browser without sandbox\n") } browser, err := engine.New(options) if err != nil { return nil, err } runner.browser = browser } runner.catalog = disk.NewCatalog(config.DefaultConfig.TemplatesDirectory) var httpclient *retryablehttp.Client if options.ProxyInternal && options.AliveHttpProxy != "" || options.AliveSocksProxy != "" { var err error httpclient, err = httpclientpool.Get(options, &httpclientpool.Configuration{}) if err != nil { return nil, err } } if err := reporting.CreateConfigIfNotExists(); err != nil { return nil, err } reportingOptions, err := createReportingOptions(options) if err != nil { return nil, err } if reportingOptions != nil && httpclient != nil { reportingOptions.HttpClient = httpclient } if reportingOptions != nil { client, err := reporting.New(reportingOptions, options.ReportingDB, false) if err != nil { return nil, errors.Wrap(err, "could not create issue reporting client") } runner.issuesClient = client } // output coloring useColor := !options.NoColor runner.colorizer = aurora.NewAurora(useColor) templates.Colorizer = runner.colorizer templates.SeverityColorizer = colorizer.New(runner.colorizer) if options.EnablePprof { runner.pprofServer = pprofutil.NewPprofServer() runner.pprofServer.Start() } if options.HttpApiEndpoint != "" { apiServer := httpapi.New(options.HttpApiEndpoint, options) runner.Logger.Info().Msgf("Listening api endpoint on: %s", options.HttpApiEndpoint) runner.httpApiEndpoint = apiServer go func() { if err := apiServer.Start(); err != nil { runner.Logger.Error().Msgf("Failed to start API server: %s", err) } }() } if (len(options.Templates) == 0 || !options.NewTemplates || (options.TargetsFilePath == "" && !options.Stdin && len(options.Targets) == 0)) && options.UpdateTemplates { os.Exit(0) } tmpDir, err := os.MkdirTemp("", "nuclei-tmp-*") if err != nil { return nil, errors.Wrap(err, "could not create temporary directory") } runner.tmpDir = tmpDir // Cleanup tmpDir only if initialization fails // On successful initialization, Close() method will handle cleanup cleanupOnError := true defer func() { if cleanupOnError && runner.tmpDir != "" { _ = os.RemoveAll(runner.tmpDir) } }() // create the input provider and load the inputs inputProvider, err := provider.NewInputProvider(provider.InputOptions{Options: options, TempDir: runner.tmpDir}) if err != nil { return nil, errors.Wrap(err, "could not create input provider") } runner.inputProvider = inputProvider // Create the output file if asked outputWriter, err := output.NewStandardWriter(options) if err != nil { return nil, errors.Wrap(err, "could not create output file") } // setup a proxy writer to automatically upload results to PDCP runner.output = runner.setupPDCPUpload(outputWriter) if options.HTTPStats { runner.httpStats = outputstats.NewTracker() runner.output = output.NewMultiWriter(runner.output, output.NewTrackerWriter(runner.httpStats)) } if options.JSONL && options.EnableProgressBar { options.StatsJSON = true } if options.StatsJSON { options.EnableProgressBar = true } // Creates the progress tracking object var progressErr error statsInterval := options.StatsInterval runner.progress, progressErr = progress.NewStatsTicker(statsInterval, options.EnableProgressBar, options.StatsJSON, false, options.MetricsPort) if progressErr != nil { return nil, progressErr } // create project file if requested or load the existing one if options.Project { var projectFileErr error runner.projectFile, projectFileErr = projectfile.New(&projectfile.Options{Path: options.ProjectPath, Cleanup: utils.IsBlank(options.ProjectPath)}) if projectFileErr != nil { return nil, projectFileErr } } // create the resume configuration structure resumeCfg := types.NewResumeCfg() if runner.options.ShouldLoadResume() { runner.Logger.Info().Msg("Resuming from save checkpoint") file, err := os.ReadFile(runner.options.Resume) if err != nil { return nil, err } err = json.Unmarshal(file, &resumeCfg) if err != nil { return nil, err } resumeCfg.Compile() } runner.resumeCfg = resumeCfg if options.DASTReport || options.DASTServer { var err error runner.fuzzStats, err = fuzzStats.NewTracker() if err != nil { return nil, errors.Wrap(err, "could not create fuzz stats db") } if !options.DASTServer { dastServer, err := server.NewStatsServer(runner.fuzzStats) if err != nil { return nil, errors.Wrap(err, "could not create dast server") } runner.dastServer = dastServer } } if runner.fuzzStats != nil { outputWriter.JSONLogRequestHook = func(request *output.JSONLogRequest) { if request.Error == "none" || request.Error == "" { return } runner.fuzzStats.RecordErrorEvent(fuzzStats.ErrorEvent{ TemplateID: request.Template, URL: request.Input, Error: request.Error, }) } } opts := interactsh.DefaultOptions(runner.output, runner.issuesClient, runner.progress) opts.Logger = runner.Logger opts.Debug = runner.options.Debug opts.NoColor = runner.options.NoColor if options.InteractshURL != "" { opts.ServerURL = options.InteractshURL } opts.Authorization = options.InteractshToken opts.CacheSize = options.InteractionsCacheSize opts.Eviction = time.Duration(options.InteractionsEviction) * time.Second opts.CooldownPeriod = time.Duration(options.InteractionsCoolDownPeriod) * time.Second opts.PollDuration = time.Duration(options.InteractionsPollDuration) * time.Second opts.NoInteractsh = runner.options.NoInteractsh opts.StopAtFirstMatch = runner.options.StopAtFirstMatch opts.Debug = runner.options.Debug opts.DebugRequest = runner.options.DebugRequests opts.DebugResponse = runner.options.DebugResponse if httpclient != nil { opts.HTTPClient = httpclient } if opts.HTTPClient == nil { httpOpts := retryablehttp.DefaultOptionsSingle httpOpts.Timeout = 20 * time.Second // for stability reasons if options.Timeout > 20 { httpOpts.Timeout = time.Duration(options.Timeout) * time.Second } // in testing it was found most of times when interactsh failed, it was due to failure in registering /polling requests opts.HTTPClient = retryablehttp.NewClient(retryablehttp.DefaultOptionsSingle) } interactshClient, err := interactsh.New(opts) if err != nil { runner.Logger.Error().Msgf("Could not create interactsh client: %s", err) } else { runner.interactsh = interactshClient } if options.RateLimitMinute > 0 { runner.Logger.Print().Msgf("[%v] %v", aurora.BrightYellow("WRN"), "rate limit per minute is deprecated - use rate-limit-duration") options.RateLimit = options.RateLimitMinute options.RateLimitDuration = time.Minute } if options.RateLimit > 0 && options.RateLimitDuration == 0 { options.RateLimitDuration = time.Second } runner.rateLimiter = utils.GetRateLimiter(context.Background(), options.RateLimit, options.RateLimitDuration) // Initialization successful, disable cleanup on error cleanupOnError = false return runner, nil } // runStandardEnumeration runs standard enumeration func (r *Runner) runStandardEnumeration(executerOpts *protocols.ExecutorOptions, store *loader.Store, engine *core.Engine) (*atomic.Bool, error) { if r.options.AutomaticScan { return r.executeSmartWorkflowInput(executerOpts, store, engine) } return r.executeTemplatesInput(store, engine) } // Close releases all the resources and cleans up func (r *Runner) Close() { if r.dastServer != nil { r.dastServer.Close() } if r.httpStats != nil { r.httpStats.DisplayTopStats(r.options.NoColor) } // dump hosterrors cache if r.hostErrors != nil { r.hostErrors.Close() } if r.output != nil { r.output.Close() } if r.issuesClient != nil { r.issuesClient.Close() } if r.projectFile != nil { r.projectFile.Close() } if r.inputProvider != nil { r.inputProvider.Close() } protocolinit.Close(r.options.ExecutionId) if r.pprofServer != nil { r.pprofServer.Stop() } if r.rateLimiter != nil { r.rateLimiter.Stop() } r.progress.Stop() if r.browser != nil { r.browser.Close() } if r.tmpDir != "" { _ = os.RemoveAll(r.tmpDir) } //this is no-op unless nuclei is built with stats build tag events.Close() } // setupPDCPUpload sets up the PDCP upload writer // by creating a new writer and returning it func (r *Runner) setupPDCPUpload(writer output.Writer) output.Writer { // if scanid is given implicitly consider that scan upload is enabled if r.options.ScanID != "" { r.options.EnableCloudUpload = true } if !r.options.EnableCloudUpload && !EnableCloudUpload { r.pdcpUploadErrMsg = "Scan results upload to cloud is disabled." return writer } h := &pdcpauth.PDCPCredHandler{} creds, err := h.GetCreds() if err != nil { if err != pdcpauth.ErrNoCreds && !HideAutoSaveMsg { r.Logger.Verbose().Msgf("Could not get credentials for cloud upload: %s\n", err) } r.pdcpUploadErrMsg = fmt.Sprintf("To view results on Cloud Dashboard, configure API key from %v", pdcpauth.DashBoardURL) return writer } uploadWriter, err := pdcp.NewUploadWriter(context.Background(), r.Logger, creds) if err != nil { r.pdcpUploadErrMsg = fmt.Sprintf("PDCP (%v) Auto-Save Failed: %s\n", pdcpauth.DashBoardURL, err) return writer } if r.options.ScanID != "" { // ignore and use empty scan id if invalid _ = uploadWriter.SetScanID(r.options.ScanID) } if r.options.ScanName != "" { uploadWriter.SetScanName(r.options.ScanName) } if r.options.TeamID != "" { uploadWriter.SetTeamID(r.options.TeamID) } return output.NewMultiWriter(writer, uploadWriter) } // RunEnumeration sets up the input layer for giving input nuclei. // binary and runs the actual enumeration func (r *Runner) RunEnumeration() error { // If the user has asked for DAST server mode, run the live // DAST fuzzing server. if r.options.DASTServer { execurOpts := &server.NucleiExecutorOptions{ Options: r.options, Output: r.output, Progress: r.progress, Catalog: r.catalog, IssuesClient: r.issuesClient, RateLimiter: r.rateLimiter, Interactsh: r.interactsh, ProjectFile: r.projectFile, Browser: r.browser, Colorizer: r.colorizer, Parser: r.parser, TemporaryDirectory: r.tmpDir, FuzzStatsDB: r.fuzzStats, Logger: r.Logger, } dastServer, err := server.New(&server.Options{ Address: r.options.DASTServerAddress, Templates: r.options.Templates, OutputWriter: r.output, Verbose: r.options.Verbose, Token: r.options.DASTServerToken, InScope: r.options.Scope, OutScope: r.options.OutOfScope, NucleiExecutorOptions: execurOpts, }) if err != nil { return err } r.dastServer = dastServer return dastServer.Start() } // If user asked for new templates to be executed, collect the list from the templates' directory. if r.options.NewTemplates { if arr := config.DefaultConfig.GetNewAdditions(); len(arr) > 0 { r.options.Templates = append(r.options.Templates, arr...) } } if len(r.options.NewTemplatesWithVersion) > 0 { if arr := installer.GetNewTemplatesInVersions(r.options.NewTemplatesWithVersion...); len(arr) > 0 { r.options.Templates = append(r.options.Templates, arr...) } } // Exclude ignored file for validation if !r.options.Validate { ignoreFile := config.ReadIgnoreFile() r.options.ExcludeTags = append(r.options.ExcludeTags, ignoreFile.Tags...) r.options.ExcludedTemplates = append(r.options.ExcludedTemplates, ignoreFile.Files...) } fuzzFreqCache := frequency.New(frequency.DefaultMaxTrackCount, r.options.FuzzParamFrequency) r.fuzzFrequencyCache = fuzzFreqCache // Create the executor options which will be used throughout the execution // stage by the nuclei engine modules. executorOpts := &protocols.ExecutorOptions{ Output: r.output, Options: r.options, Progress: r.progress, Catalog: r.catalog, IssuesClient: r.issuesClient, RateLimiter: r.rateLimiter, Interactsh: r.interactsh, ProjectFile: r.projectFile, Browser: r.browser, Colorizer: r.colorizer, ResumeCfg: r.resumeCfg, ExcludeMatchers: excludematchers.New(r.options.ExcludeMatchers), InputHelper: input.NewHelper(), TemporaryDirectory: r.tmpDir, Parser: r.parser, FuzzParamsFrequency: fuzzFreqCache, GlobalMatchers: globalmatchers.New(), DoNotCache: r.options.DoNotCacheTemplates, Logger: r.Logger, } if config.DefaultConfig.IsDebugArgEnabled(config.DebugExportURLPattern) { // Go StdLib style experimental/debug feature switch executorOpts.ExportReqURLPattern = true } if len(r.options.SecretsFile) > 0 && !r.options.Validate { // Clone options so GetAuthTmplStore can modify them without affecting the original authOptions := r.options.Copy() authTmplStore, err := GetAuthTmplStore(authOptions, r.catalog, executorOpts) if err != nil { return errors.Wrap(err, "failed to load dynamic auth templates") } authOpts := &authprovider.AuthProviderOptions{SecretsFiles: r.options.SecretsFile} authOpts.LazyFetchSecret = GetLazyAuthFetchCallback(&AuthLazyFetchOptions{ TemplateStore: authTmplStore, ExecOpts: executorOpts, }) // initialize auth provider provider, err := authprovider.NewAuthProvider(authOpts) if err != nil { return errors.Wrap(err, "could not create auth provider") } executorOpts.AuthProvider = provider } if r.options.ShouldUseHostError() { maxHostError := r.options.MaxHostError if r.options.TemplateThreads > maxHostError { r.Logger.Print().Msgf("[%v] The concurrency value is higher than max-host-error", r.colorizer.BrightYellow("WRN")) r.Logger.Info().Msgf("Adjusting max-host-error to the concurrency value: %d", r.options.TemplateThreads) maxHostError = r.options.TemplateThreads } cache := hosterrorscache.New(maxHostError, hosterrorscache.DefaultMaxHostsCount, r.options.TrackError) cache.SetVerbose(r.options.Verbose) r.hostErrors = cache executorOpts.HostErrorsCache = cache } executorEngine := core.New(r.options) executorEngine.SetExecuterOptions(executorOpts) workflowLoader, err := parsers.NewLoader(executorOpts) if err != nil { return errors.Wrap(err, "Could not create loader.") } executorOpts.WorkflowLoader = workflowLoader // If using input-file flags, only load http fuzzing based templates. loaderConfig := loader.NewConfig(r.options, r.catalog, executorOpts) if !strings.EqualFold(r.options.InputFileMode, "list") || r.options.DAST { // if input type is not list (implicitly enable fuzzing) r.options.DAST = true } store, err := loader.New(loaderConfig) if err != nil { return errors.Wrap(err, "Could not create loader.") } // list all templates or tags as specified by user. // This uses a separate parser to reduce time taken as // normally nuclei does a lot of compilation and stuff // for templates, which we don't want for these simp if r.options.TagList { tagsMap, err := store.LoadTemplateTags() if err != nil { return err } r.listAvailableTags(tagsMap) os.Exit(0) } if r.options.TemplateList || r.options.TemplateDisplay { if err := store.LoadTemplatesOnlyMetadata(); err != nil { return err } r.listAvailableStoreTemplates(store) os.Exit(0) } if r.options.Validate { if err := store.ValidateTemplates(); err != nil { return err } if stats.GetValue(templates.SyntaxErrorStats) == 0 && stats.GetValue(templates.SyntaxWarningStats) == 0 && stats.GetValue(templates.RuntimeWarningsStats) == 0 { r.Logger.Info().Msgf("All templates validated successfully") } else { return errors.New("encountered errors while performing template validation") } return nil // exit } if err := store.Load(); err != nil { return err } // TODO: remove below functions after v3 or update warning messages templates.PrintDeprecatedProtocolNameMsgIfApplicable(r.options.Silent, r.options.Verbose) // add the hosts from the metadata queries of loaded templates into input provider if r.options.Uncover && len(r.options.UncoverQuery) == 0 { uncoverOpts := &uncoverlib.Options{ Limit: r.options.UncoverLimit, MaxRetry: r.options.Retries, Timeout: r.options.Timeout, RateLimit: uint(r.options.UncoverRateLimit), RateLimitUnit: time.Minute, // default unit is minute } ret := uncover.GetUncoverTargetsFromMetadata(context.TODO(), store.Templates(), r.options.UncoverField, uncoverOpts) for host := range ret { _ = r.inputProvider.SetWithExclusions(r.options.ExecutionId, host) } } // display execution info like version , templates used etc r.displayExecutionInfo(store) // prefetch secrets to ensure authentication completes before scanning starts if executorOpts.AuthProvider != nil { r.Logger.Info().Msgf("Pre-fetching secrets from authprovider[s]") if err := executorOpts.AuthProvider.PreFetchSecrets(); err != nil { return errors.Wrap(err, "could not pre-fetch secrets") } } // If not explicitly disabled, check if http based protocols // are used, and if inputs are non-http to pre-perform probing // of urls and storing them for execution. if !r.options.DisableHTTPProbe && loader.IsHTTPBasedProtocolUsed(store) && r.isInputNonHTTP() { inputHelpers, err := r.initializeTemplatesHTTPInput() if err != nil { return errors.Wrap(err, "could not probe http input") } executorOpts.InputHelper.InputsHTTP = inputHelpers } // initialize stats worker ( this is no-op unless nuclei is built with stats build tag) // during execution a directory with 2 files will be created in the current directory // config.json - containing below info // events.jsonl - containing all start and end times of all templates events.InitWithConfig(&events.ScanConfig{ Name: "nuclei-stats", // make this configurable TargetCount: int(r.inputProvider.Count()), TemplatesCount: len(store.Templates()) + len(store.Workflows()), TemplateConcurrency: r.options.TemplateThreads, PayloadConcurrency: r.options.PayloadConcurrency, JsConcurrency: r.options.JsConcurrency, Retries: r.options.Retries, }, "") if r.dastServer != nil { go func() { if err := r.dastServer.Start(); err != nil { r.Logger.Error().Msgf("could not start dast server: %v", err) } }() } now := time.Now() enumeration := false var results *atomic.Bool results, err = r.runStandardEnumeration(executorOpts, store, executorEngine) enumeration = true if !enumeration { return err } if executorOpts.FuzzStatsDB != nil { executorOpts.FuzzStatsDB.Close() } if r.interactsh != nil { matched := r.interactsh.Close() if matched { results.CompareAndSwap(false, true) } } if executorOpts.InputHelper != nil { _ = executorOpts.InputHelper.Close() } r.fuzzFrequencyCache.Close() r.progress.Stop() timeTaken := time.Since(now) // todo: error propagation without canonical straight error check is required by cloud? // use safe dereferencing to avoid potential panics in case of previous unchecked errors if v := ptrutil.Safe(results); !v.Load() { r.Logger.Info().Msgf("Scan completed in %s. No results found.", shortDur(timeTaken)) } else { matchCount := r.output.ResultCount() r.Logger.Info().Msgf("Scan completed in %s. %d matches found.", shortDur(timeTaken), matchCount) } // check if a passive scan was requested but no target was provided if r.options.OfflineHTTP && len(r.options.Targets) == 0 && r.options.TargetsFilePath == "" { return errors.Wrap(err, "missing required input (http response) to run passive templates") } return err } func shortDur(d time.Duration) string { if d < time.Minute { return d.String() } // Truncate to the nearest minute d = d.Truncate(time.Minute) s := d.String() if strings.HasSuffix(s, "m0s") { s = s[:len(s)-2] } if strings.HasSuffix(s, "h0m") { s = s[:len(s)-2] } return s } func (r *Runner) isInputNonHTTP() bool { var nonURLInput bool r.inputProvider.Iterate(func(value *contextargs.MetaInput) bool { if !strings.Contains(value.Input, "://") { nonURLInput = true return false } return true }) return nonURLInput } func (r *Runner) executeSmartWorkflowInput(executorOpts *protocols.ExecutorOptions, store *loader.Store, engine *core.Engine) (*atomic.Bool, error) { r.progress.Init(r.inputProvider.Count(), 0, 0) service, err := automaticscan.New(automaticscan.Options{ ExecuterOpts: executorOpts, Store: store, Engine: engine, Target: r.inputProvider, }) if err != nil { return nil, errors.Wrap(err, "could not create automatic scan service") } if err := service.Execute(); err != nil { return nil, errors.Wrap(err, "could not execute automatic scan") } result := &atomic.Bool{} result.Store(service.Close()) return result, nil } func (r *Runner) executeTemplatesInput(store *loader.Store, engine *core.Engine) (*atomic.Bool, error) { if r.options.VerboseVerbose { for _, template := range store.Templates() { r.logAvailableTemplate(template.Path) } for _, template := range store.Workflows() { r.logAvailableTemplate(template.Path) } } finalTemplates := []*templates.Template{} finalTemplates = append(finalTemplates, store.Templates()...) finalTemplates = append(finalTemplates, store.Workflows()...) if len(finalTemplates) == 0 { return nil, errors.New("no templates provided for scan") } // pass input provider to engine // TODO: this should be not necessary after r.hmapInputProvider is removed + refactored if r.inputProvider == nil { return nil, errors.New("no input provider found") } results := engine.ExecuteScanWithOpts(context.Background(), finalTemplates, r.inputProvider, r.options.DisableClustering) return results, nil } // displayExecutionInfo displays misc info about the nuclei engine execution func (r *Runner) displayExecutionInfo(store *loader.Store) { // Display stats for any loaded templates' syntax warnings or errors stats.Display(templates.SyntaxWarningStats) stats.Display(templates.SyntaxErrorStats) stats.Display(templates.RuntimeWarningsStats) tmplCount := len(store.Templates()) workflowCount := len(store.Workflows()) if r.options.Verbose || (tmplCount == 0 && workflowCount == 0) { // only print these stats in verbose mode stats.ForceDisplayWarning(templates.ExcludedHeadlessTmplStats) stats.ForceDisplayWarning(templates.ExcludedCodeTmplStats) stats.ForceDisplayWarning(templates.ExcludedDastTmplStats) stats.ForceDisplayWarning(templates.TemplatesExcludedStats) stats.ForceDisplayWarning(templates.ExcludedFileStats) stats.ForceDisplayWarning(templates.ExcludedSelfContainedStats) } if tmplCount == 0 && workflowCount == 0 { // if dast flag is used print explicit warning if r.options.DAST { r.Logger.Print().Msgf("[%v] No DAST templates found", aurora.BrightYellow("WRN")) } stats.ForceDisplayWarning(templates.SkippedCodeTmplTamperedStats) } else { stats.DisplayAsWarning(templates.SkippedCodeTmplTamperedStats) } stats.DisplayAsWarning(httpProtocol.SetThreadToCountZero) stats.ForceDisplayWarning(templates.SkippedUnsignedStats) stats.ForceDisplayWarning(templates.SkippedRequestSignatureStats) cfg := config.DefaultConfig updateutils.Aurora = r.colorizer versionInfo := func(version, latestVersion, versionType string) string { if !cfg.CanCheckForUpdates() { return fmt.Sprintf("Current %s version: %v (%s) - remove '-duc' flag to enable update checks", versionType, version, r.colorizer.BrightYellow("unknown")) } return fmt.Sprintf("Current %s version: %v %v", versionType, version, updateutils.GetVersionDescription(version, latestVersion)) } gologger.Info().Msg(versionInfo(config.Version, cfg.LatestNucleiVersion, "nuclei")) gologger.Info().Msg(versionInfo(cfg.TemplateVersion, cfg.LatestNucleiTemplatesVersion, "nuclei-templates")) if !HideAutoSaveMsg { if r.pdcpUploadErrMsg != "" { r.Logger.Warning().Msgf("%s", r.pdcpUploadErrMsg) } else { r.Logger.Info().Msgf("To view results on cloud dashboard, visit %v/scans upon scan completion.", pdcpauth.DashBoardURL) } } if tmplCount > 0 || workflowCount > 0 { if len(store.Templates()) > 0 { r.Logger.Info().Msgf("New templates added in latest release: %d", len(config.DefaultConfig.GetNewAdditions())) r.Logger.Info().Msgf("Templates loaded for current scan: %d", len(store.Templates())) } if len(store.Workflows()) > 0 { r.Logger.Info().Msgf("Workflows loaded for current scan: %d", len(store.Workflows())) } for k, v := range templates.SignatureStats { value := v.Load() if value > 0 { if k == templates.Unsigned && !r.options.Silent && !config.DefaultConfig.HideTemplateSigWarning { r.Logger.Print().Msgf("[%v] Loading %d unsigned templates for scan. Use with caution.", r.colorizer.BrightYellow("WRN"), value) } else { r.Logger.Info().Msgf("Executing %d signed templates from %s", value, k) } } } } if r.inputProvider.Count() > 0 { r.Logger.Info().Msgf("Targets loaded for current scan: %d", r.inputProvider.Count()) } } // SaveResumeConfig to file func (r *Runner) SaveResumeConfig(path string) error { dir := filepath.Dir(path) if !fileutil.FolderExists(dir) { if err := os.MkdirAll(dir, os.ModePerm); err != nil { return err } } resumeCfgClone := r.resumeCfg.Clone() resumeCfgClone.ResumeFrom = resumeCfgClone.Current data, _ := json.MarshalIndent(resumeCfgClone, "", "\t") return os.WriteFile(path, data, permissionutil.ConfigFilePermission) } // upload existing scan results to cloud with progress func UploadResultsToCloud(options *types.Options) error { h := &pdcpauth.PDCPCredHandler{} creds, err := h.GetCreds() if err != nil { return errors.Wrap(err, "could not get credentials for cloud upload") } ctx := context.TODO() uploadWriter, err := pdcp.NewUploadWriter(ctx, options.Logger, creds) if err != nil { return errors.Wrap(err, "could not create upload writer") } if options.ScanID != "" { _ = uploadWriter.SetScanID(options.ScanID) } if options.ScanName != "" { uploadWriter.SetScanName(options.ScanName) } if options.TeamID != "" { uploadWriter.SetTeamID(options.TeamID) } // Open file to count the number of results first file, err := os.Open(options.ScanUploadFile) if err != nil { return errors.Wrap(err, "could not open scan upload file") } defer func() { _ = file.Close() }() options.Logger.Info().Msgf("Uploading scan results to cloud dashboard from %s", options.ScanUploadFile) dec := json.NewDecoder(file) for dec.More() { var r output.ResultEvent err := dec.Decode(&r) if err != nil { options.Logger.Warning().Msgf("Could not decode jsonl: %s\n", err) continue } if err = uploadWriter.Write(&r); err != nil { options.Logger.Warning().Msgf("[%s] failed to upload: %s\n", r.TemplateID, err) } } uploadWriter.Close() return nil } type WalkFunc func(reflect.Value, reflect.StructField) // Walk traverses a struct and executes a callback function on each value in the struct. // The interface{} passed to the function should be a pointer to a struct or a struct. // WalkFunc is the callback function used for each value in the struct. It is passed the // reflect.Value and reflect.Type properties of the value in the struct. func Walk(s interface{}, callback WalkFunc) { structValue := reflect.ValueOf(s) if structValue.Kind() == reflect.Ptr { structValue = structValue.Elem() } if structValue.Kind() != reflect.Struct { return } for i := 0; i < structValue.NumField(); i++ { field := structValue.Field(i) fieldType := structValue.Type().Field(i) if !fieldType.IsExported() { continue } if field.Kind() == reflect.Struct { Walk(field.Addr().Interface(), callback) } else if field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct { Walk(field.Interface(), callback) } else { callback(field, fieldType) } } } // expandEndVars looks for values in a struct tagged with "yaml" and checks if they are prefixed with '$'. // If they are, it will try to retrieve the value from the environment and if it exists, it will set the // value of the field to that of the environment variable. func expandEndVars(f reflect.Value, fieldType reflect.StructField) { if _, ok := fieldType.Tag.Lookup("yaml"); !ok { return } if f.Kind() == reflect.String { str := f.String() if strings.HasPrefix(str, "$") { env := strings.TrimPrefix(str, "$") retrievedEnv := os.Getenv(env) if retrievedEnv != "" { f.SetString(os.Getenv(env)) } } } } func init() { HideAutoSaveMsg = env.GetEnvOrDefault("DISABLE_CLOUD_UPLOAD_WRN", false) EnableCloudUpload = env.GetEnvOrDefault("ENABLE_CLOUD_UPLOAD", false) } ================================================ FILE: internal/runner/runner_test.go ================================================ package runner import ( "os" "testing" "github.com/stretchr/testify/require" "github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity" "github.com/projectdiscovery/nuclei/v3/pkg/types" ) func TestCreateReportingOptions(t *testing.T) { var options types.Options options.ReportingConfig = "../../integration_tests/test-issue-tracker-config1.yaml" resultOptions, err := createReportingOptions(&options) require.Nil(t, err) require.Equal(t, resultOptions.AllowList.Severities, severity.Severities{severity.High, severity.Critical}) require.Equal(t, resultOptions.DenyList.Severities, severity.Severities{severity.Low}) options.ReportingConfig = "../../integration_tests/test-issue-tracker-config2.yaml" resultOptions2, err := createReportingOptions(&options) require.Nil(t, err) require.Equal(t, resultOptions2.AllowList.Severities, resultOptions.AllowList.Severities) require.Equal(t, resultOptions2.DenyList.Severities, resultOptions.DenyList.Severities) } type TestStruct1 struct { A string `yaml:"a"` Struct *TestStruct2 `yaml:"b"` } type TestStruct2 struct { B string `yaml:"b"` } type TestStruct3 struct { A string `yaml:"a"` B string `yaml:"b"` C string `yaml:"c"` } type TestStruct4 struct { A string `yaml:"a"` Struct *TestStruct3 `yaml:"b"` } type TestStruct5 struct { A []string `yaml:"a"` B [2]string `yaml:"b"` } type TestStruct6 struct { A string `yaml:"a"` B *TestStruct2 `yaml:"b"` C string } func TestWalkReflectStructAssignsEnvVars(t *testing.T) { testStruct := &TestStruct1{ A: "$VAR_EXAMPLE", Struct: &TestStruct2{ B: "$VAR_TWO", }, } _ = os.Setenv("VAR_EXAMPLE", "value") _ = os.Setenv("VAR_TWO", "value2") Walk(testStruct, expandEndVars) require.Equal(t, "value", testStruct.A) require.Equal(t, "value2", testStruct.Struct.B) } func TestWalkReflectStructHandlesDifferentTypes(t *testing.T) { testStruct := &TestStruct3{ A: "$VAR_EXAMPLE", B: "$VAR_TWO", C: "$VAR_THREE", } _ = os.Setenv("VAR_EXAMPLE", "value") _ = os.Setenv("VAR_TWO", "2") _ = os.Setenv("VAR_THREE", "true") Walk(testStruct, expandEndVars) require.Equal(t, "value", testStruct.A) require.Equal(t, "2", testStruct.B) require.Equal(t, "true", testStruct.C) } func TestWalkReflectStructEmpty(t *testing.T) { testStruct := &TestStruct3{ A: "$VAR_EXAMPLE", B: "", C: "$VAR_THREE", } _ = os.Setenv("VAR_EXAMPLE", "value") _ = os.Setenv("VAR_TWO", "2") _ = os.Setenv("VAR_THREE", "true") Walk(testStruct, expandEndVars) require.Equal(t, "value", testStruct.A) require.Equal(t, "", testStruct.B) require.Equal(t, "true", testStruct.C) } func TestWalkReflectStructWithNoYamlTag(t *testing.T) { test := &TestStruct6{ A: "$GITHUB_USER", B: &TestStruct2{ B: "$GITHUB_USER", }, C: "$GITHUB_USER", } _ = os.Setenv("GITHUB_USER", "testuser") Walk(test, expandEndVars) require.Equal(t, "testuser", test.A) require.Equal(t, "testuser", test.B.B, test.B) require.Equal(t, "$GITHUB_USER", test.C) } func TestWalkReflectStructHandlesNestedStructs(t *testing.T) { testStruct := &TestStruct4{ A: "$VAR_EXAMPLE", Struct: &TestStruct3{ B: "$VAR_TWO", C: "$VAR_THREE", }, } _ = os.Setenv("VAR_EXAMPLE", "value") _ = os.Setenv("VAR_TWO", "2") _ = os.Setenv("VAR_THREE", "true") Walk(testStruct, expandEndVars) require.Equal(t, "value", testStruct.A) require.Equal(t, "2", testStruct.Struct.B) require.Equal(t, "true", testStruct.Struct.C) } ================================================ FILE: internal/runner/templates.go ================================================ package runner import ( "bytes" "path/filepath" "sort" "strings" "github.com/alecthomas/chroma/quick" jsoniter "github.com/json-iterator/go" "github.com/logrusorgru/aurora" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader" "github.com/projectdiscovery/nuclei/v3/pkg/templates" "github.com/projectdiscovery/nuclei/v3/pkg/types" ) // log available templates for verbose (-vv) func (r *Runner) logAvailableTemplate(tplPath string) { t, err := r.parser.ParseTemplate(tplPath, r.catalog) tpl, ok := t.(*templates.Template) if !ok { panic("not a template") } if err != nil { r.Logger.Error().Msgf("Could not parse file '%s': %s\n", tplPath, err) } else { r.verboseTemplate(tpl) } } // log available templates for verbose (-vv) func (r *Runner) verboseTemplate(tpl *templates.Template) { r.Logger.Print().Msgf("%s\n", templates.TemplateLogMessage(tpl.ID, types.ToString(tpl.Info.Name), tpl.Info.Authors.ToSlice(), tpl.Info.SeverityHolder.Severity)) } func (r *Runner) listAvailableStoreTemplates(store *loader.Store) { r.Logger.Print().Msgf( "\nListing available %v nuclei templates for %v", config.DefaultConfig.TemplateVersion, config.DefaultConfig.TemplatesDirectory, ) // order templates alphabetically by path templates := store.Templates() sort.Slice(templates, func(i, j int) bool { return templates[i].Path < templates[j].Path }) for _, tpl := range templates { if hasExtraFlags(r.options) { if r.options.TemplateDisplay { colorize := !r.options.NoColor path := tpl.Path tplBody, err := store.ReadTemplateFromURI(path, true) if err != nil { r.Logger.Error().Msgf("Could not read the template %s: %s", path, err) continue } if colorize { path = aurora.Cyan(tpl.Path).String() tplBody, err = r.highlightTemplate(&tplBody) if err != nil { r.Logger.Error().Msgf("Could not highlight the template %s: %s", tpl.Path, err) continue } } r.Logger.Print().Msgf("Template: %s\n\n%s", path, tplBody) } else { r.Logger.Print().Msgf("%s\n", strings.TrimPrefix(tpl.Path, config.DefaultConfig.TemplatesDirectory+string(filepath.Separator))) } } else { r.verboseTemplate(tpl) } } } func (r *Runner) listAvailableTags(tagsMap map[string]int) { r.Logger.Print().Msgf( "\nListing available %v nuclei tags for %v", config.DefaultConfig.TemplateVersion, config.DefaultConfig.TemplatesDirectory, ) type kv struct { Key string `json:"tag"` Value int `json:"count"` } var tagsList []kv for k, v := range tagsMap { tagsList = append(tagsList, kv{k, v}) } sort.Slice(tagsList, func(i, j int) bool { return tagsList[i].Value > tagsList[j].Value }) for _, tag := range tagsList { if r.options.JSONL { marshalled, _ := jsoniter.Marshal(tag) r.Logger.Print().Msgf("%s", string(marshalled)) } else { r.Logger.Print().Msgf("%s (%d)", tag.Key, tag.Value) } } } func (r *Runner) highlightTemplate(body *[]byte) ([]byte, error) { var buf bytes.Buffer // YAML lexer, true color terminal formatter and monokai style err := quick.Highlight(&buf, string(*body), "yaml", "terminal16m", "monokai") if err != nil { return nil, err } return buf.Bytes(), nil } func hasExtraFlags(options *types.Options) bool { return options.Templates != nil || options.Authors != nil || options.Tags != nil || len(options.ExcludeTags) > 3 || options.IncludeTags != nil || options.IncludeIds != nil || options.ExcludeIds != nil || options.IncludeTemplates != nil || options.ExcludedTemplates != nil || options.ExcludeMatchers != nil || options.Severities != nil || options.ExcludeSeverities != nil || options.Protocols != nil || options.ExcludeProtocols != nil || options.IncludeConditions != nil || options.TemplateList } ================================================ FILE: internal/server/dedupe.go ================================================ package server import ( "crypto/sha256" "encoding/hex" "net/url" "sort" "strings" "sync" "github.com/projectdiscovery/nuclei/v3/pkg/input/types" mapsutil "github.com/projectdiscovery/utils/maps" ) var dynamicHeaders = map[string]bool{ "date": true, "if-modified-since": true, "if-unmodified-since": true, "cache-control": true, "if-none-match": true, "if-match": true, "authorization": true, "cookie": true, "x-csrf-token": true, "content-length": true, "content-md5": true, "host": true, "x-request-id": true, "x-correlation-id": true, "user-agent": true, "referer": true, } type requestDeduplicator struct { hashes map[string]struct{} lock *sync.RWMutex } func newRequestDeduplicator() *requestDeduplicator { return &requestDeduplicator{ hashes: make(map[string]struct{}), lock: &sync.RWMutex{}, } } func (r *requestDeduplicator) isDuplicate(req *types.RequestResponse) bool { hash, err := hashRequest(req) if err != nil { return false } r.lock.RLock() _, ok := r.hashes[hash] r.lock.RUnlock() if ok { return true } r.lock.Lock() r.hashes[hash] = struct{}{} r.lock.Unlock() return false } func hashRequest(req *types.RequestResponse) (string, error) { normalizedURL, err := normalizeURL(req.URL.URL) if err != nil { return "", err } var hashContent strings.Builder hashContent.WriteString(req.Request.Method) hashContent.WriteString(normalizedURL) headers := sortedNonDynamicHeaders(req.Request.Headers) for _, header := range headers { hashContent.WriteString(header.Key) hashContent.WriteString(header.Value) } if len(req.Request.Body) > 0 { hashContent.Write([]byte(req.Request.Body)) } // Calculate the SHA256 hash hash := sha256.Sum256([]byte(hashContent.String())) return hex.EncodeToString(hash[:]), nil } func normalizeURL(u *url.URL) (string, error) { query := u.Query() sortedQuery := make(url.Values) for k, v := range query { sort.Strings(v) sortedQuery[k] = v } u.RawQuery = sortedQuery.Encode() if u.Path == "" { u.Path = "/" } return u.String(), nil } type header struct { Key string Value string } func sortedNonDynamicHeaders(headers mapsutil.OrderedMap[string, string]) []header { var result []header headers.Iterate(func(k, v string) bool { if !dynamicHeaders[strings.ToLower(k)] { result = append(result, header{Key: k, Value: v}) } return true }) sort.Slice(result, func(i, j int) bool { return result[i].Key < result[j].Key }) return result } ================================================ FILE: internal/server/nuclei_sdk.go ================================================ package server import ( "context" "fmt" _ "net/http/pprof" "strings" "github.com/logrusorgru/aurora" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/frequency" "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/stats" "github.com/projectdiscovery/nuclei/v3/pkg/input/formats" "github.com/projectdiscovery/nuclei/v3/pkg/input/provider/http" "github.com/projectdiscovery/nuclei/v3/pkg/projectfile" "gopkg.in/yaml.v3" "github.com/pkg/errors" "github.com/projectdiscovery/ratelimit" "github.com/projectdiscovery/nuclei/v3/pkg/catalog" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader" "github.com/projectdiscovery/nuclei/v3/pkg/core" "github.com/projectdiscovery/nuclei/v3/pkg/input" "github.com/projectdiscovery/nuclei/v3/pkg/loader/parser" parsers "github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/progress" "github.com/projectdiscovery/nuclei/v3/pkg/protocols" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/globalmatchers" "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/utils/excludematchers" browserEngine "github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine" "github.com/projectdiscovery/nuclei/v3/pkg/reporting" "github.com/projectdiscovery/nuclei/v3/pkg/templates" "github.com/projectdiscovery/nuclei/v3/pkg/types" ) type nucleiExecutor struct { engine *core.Engine store *loader.Store options *NucleiExecutorOptions executorOpts *protocols.ExecutorOptions } type NucleiExecutorOptions struct { Options *types.Options Output output.Writer Progress progress.Progress Catalog catalog.Catalog IssuesClient reporting.Client RateLimiter *ratelimit.Limiter Interactsh *interactsh.Client ProjectFile *projectfile.ProjectFile Browser *browserEngine.Browser FuzzStatsDB *stats.Tracker Colorizer aurora.Aurora Parser parser.Parser TemporaryDirectory string Logger *gologger.Logger } func newNucleiExecutor(opts *NucleiExecutorOptions) (*nucleiExecutor, error) { fuzzFreqCache := frequency.New(frequency.DefaultMaxTrackCount, opts.Options.FuzzParamFrequency) resumeCfg := types.NewResumeCfg() // Create the executor options which will be used throughout the execution // stage by the nuclei engine modules. executorOpts := &protocols.ExecutorOptions{ Output: opts.Output, Options: opts.Options, Progress: opts.Progress, Catalog: opts.Catalog, IssuesClient: opts.IssuesClient, RateLimiter: opts.RateLimiter, Interactsh: opts.Interactsh, ProjectFile: opts.ProjectFile, Browser: opts.Browser, Colorizer: opts.Colorizer, ResumeCfg: resumeCfg, ExcludeMatchers: excludematchers.New(opts.Options.ExcludeMatchers), InputHelper: input.NewHelper(), TemporaryDirectory: opts.TemporaryDirectory, Parser: opts.Parser, FuzzParamsFrequency: fuzzFreqCache, GlobalMatchers: globalmatchers.New(), FuzzStatsDB: opts.FuzzStatsDB, Logger: opts.Logger, } if opts.Options.ShouldUseHostError() { maxHostError := opts.Options.MaxHostError if maxHostError == 30 { maxHostError = 100 // auto adjust for fuzzings } if opts.Options.TemplateThreads > maxHostError { opts.Logger.Info().Msgf("Adjusting max-host-error to the concurrency value: %d", opts.Options.TemplateThreads) maxHostError = opts.Options.TemplateThreads } cache := hosterrorscache.New(maxHostError, hosterrorscache.DefaultMaxHostsCount, opts.Options.TrackError) cache.SetVerbose(opts.Options.Verbose) executorOpts.HostErrorsCache = cache } executorEngine := core.New(opts.Options) executorEngine.SetExecuterOptions(executorOpts) workflowLoader, err := parsers.NewLoader(executorOpts) if err != nil { return nil, errors.Wrap(err, "Could not create loader options.") } executorOpts.WorkflowLoader = workflowLoader // If using input-file flags, only load http fuzzing based templates. loaderConfig := loader.NewConfig(opts.Options, opts.Catalog, executorOpts) if !strings.EqualFold(opts.Options.InputFileMode, "list") || opts.Options.DAST || opts.Options.DASTServer { // if input type is not list (implicitly enable fuzzing) opts.Options.DAST = true } store, err := loader.New(loaderConfig) if err != nil { return nil, errors.Wrap(err, "Could not create loader options.") } if err := store.Load(); err != nil { return nil, errors.Wrap(err, "Could not load templates.") } return &nucleiExecutor{ engine: executorEngine, store: store, options: opts, executorOpts: executorOpts, }, nil } // 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"` } func (n *nucleiExecutor) ExecuteScan(target PostRequestsHandlerRequest) error { finalTemplates := []*templates.Template{} finalTemplates = append(finalTemplates, n.store.Templates()...) finalTemplates = append(finalTemplates, n.store.Workflows()...) if len(finalTemplates) == 0 { return errors.New("no templates provided for scan") } payload := proxifyRequest{ URL: target.URL, Request: struct { Header map[string]string `json:"header"` Body string `json:"body"` Raw string `json:"raw"` }{ Raw: target.RawHTTP, }, } marshalledYaml, err := yaml.Marshal(payload) if err != nil { return fmt.Errorf("error marshalling yaml: %s", err) } inputProvider, err := http.NewHttpInputProvider(&http.HttpMultiFormatOptions{ InputContents: string(marshalledYaml), InputMode: "yaml", Options: formats.InputFormatOptions{ Variables: make(map[string]interface{}), }, }) if err != nil { return errors.Wrap(err, "could not create input provider") } // We don't care about the result as its a boolean // stating whether we got matches or not _ = n.engine.ExecuteScanWithOpts(context.Background(), finalTemplates, inputProvider, true) return nil } func (n *nucleiExecutor) Close() { if n.executorOpts.FuzzStatsDB != nil { n.executorOpts.FuzzStatsDB.Close() } if n.options.Interactsh != nil { _ = n.options.Interactsh.Close() } if n.executorOpts.InputHelper != nil { _ = n.executorOpts.InputHelper.Close() } } ================================================ FILE: internal/server/requests_worker.go ================================================ package server import ( "path" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/internal/server/scope" "github.com/projectdiscovery/nuclei/v3/pkg/input/types" ) func (s *DASTServer) consumeTaskRequest(req PostRequestsHandlerRequest) { defer s.endpointsInQueue.Add(-1) parsedReq, err := types.ParseRawRequestWithURL(req.RawHTTP, req.URL) if err != nil { gologger.Warning().Msgf("Could not parse raw request: %s\n", err) return } if parsedReq.URL.Scheme != "http" && parsedReq.URL.Scheme != "https" { gologger.Warning().Msgf("Invalid scheme: %s\n", parsedReq.URL.Scheme) return } // Check filenames and don't allow non-interesting files extension := path.Base(parsedReq.URL.Path) if extension != "/" && extension != "" && scope.IsUninterestingPath(extension) { gologger.Warning().Msgf("Uninteresting path: %s\n", parsedReq.URL.Path) return } inScope, err := s.scopeManager.Validate(parsedReq.URL.URL) if err != nil { gologger.Warning().Msgf("Could not validate scope: %s\n", err) return } if !inScope { gologger.Warning().Msgf("Request is out of scope: %s %s\n", parsedReq.Request.Method, parsedReq.URL.String()) return } if s.deduplicator.isDuplicate(parsedReq) { gologger.Warning().Msgf("Duplicate request detected: %s %s\n", parsedReq.Request.Method, parsedReq.URL.String()) return } gologger.Verbose().Msgf("Fuzzing request: %s %s\n", parsedReq.Request.Method, parsedReq.URL.String()) s.endpointsBeingTested.Add(1) defer s.endpointsBeingTested.Add(-1) // Fuzz the request finally err = s.nucleiExecutor.ExecuteScan(req) if err != nil { gologger.Warning().Msgf("Could not run nuclei: %s\n", err) return } } ================================================ FILE: internal/server/scope/extensions.go ================================================ package scope import "path" func IsUninterestingPath(uriPath string) bool { extension := path.Ext(uriPath) if _, ok := excludedExtensions[extension]; ok { return true } return false } var excludedExtensions = map[string]struct{}{ ".jpg": {}, ".jpeg": {}, ".png": {}, ".gif": {}, ".bmp": {}, ".tiff": {}, ".ico": {}, ".mp4": {}, ".avi": {}, ".mov": {}, ".wmv": {}, ".flv": {}, ".mkv": {}, ".webm": {}, ".mp3": {}, ".wav": {}, ".aac": {}, ".flac": {}, ".ogg": {}, ".wma": {}, ".zip": {}, ".rar": {}, ".7z": {}, ".tar": {}, ".gz": {}, ".bz2": {}, ".exe": {}, ".bin": {}, ".iso": {}, ".img": {}, ".doc": {}, ".docx": {}, ".xls": {}, ".xlsx": {}, ".ppt": {}, ".pptx": {}, ".pdf": {}, ".psd": {}, ".ai": {}, ".eps": {}, ".indd": {}, ".swf": {}, ".fla": {}, ".css": {}, ".scss": {}, ".less": {}, ".js": {}, ".ts": {}, ".jsx": {}, ".tsx": {}, ".xml": {}, ".json": {}, ".yaml": {}, ".yml": {}, ".csv": {}, ".txt": {}, ".log": {}, ".md": {}, ".ttf": {}, ".otf": {}, ".woff": {}, ".woff2": {}, ".eot": {}, ".svg": {}, ".svgz": {}, ".webp": {}, ".tif": {}, ".mpg": {}, ".mpeg": {}, ".weba": {}, ".m4a": {}, ".m4v": {}, ".3gp": {}, ".3g2": {}, ".ogv": {}, ".ogm": {}, ".oga": {}, ".ogx": {}, ".srt": {}, ".min.js": {}, ".min.css": {}, ".js.map": {}, ".min.js.map": {}, ".chunk.css.map": {}, ".hub.js.map": {}, ".hub.css.map": {}, ".map": {}, } ================================================ FILE: internal/server/scope/scope.go ================================================ // From Katana package scope import ( "fmt" "net/url" "regexp" ) // Manager manages scope for crawling process type Manager struct { inScope []*regexp.Regexp outOfScope []*regexp.Regexp noScope bool } // NewManager returns a new scope manager for crawling func NewManager(inScope, outOfScope []string) (*Manager, error) { manager := &Manager{} for _, regex := range inScope { if compiled, err := regexp.Compile(regex); err != nil { return nil, fmt.Errorf("could not compile regex %s: %s", regex, err) } else { manager.inScope = append(manager.inScope, compiled) } } for _, regex := range outOfScope { if compiled, err := regexp.Compile(regex); err != nil { return nil, fmt.Errorf("could not compile regex %s: %s", regex, err) } else { manager.outOfScope = append(manager.outOfScope, compiled) } } if len(manager.inScope) == 0 && len(manager.outOfScope) == 0 { manager.noScope = true } return manager, nil } // Validate returns true if the URL matches scope rules func (m *Manager) Validate(URL *url.URL) (bool, error) { if m.noScope { return true, nil } urlStr := URL.String() urlValidated, err := m.validateURL(urlStr) if err != nil { return false, err } if urlValidated { return true, nil } return false, nil } func (m *Manager) validateURL(URL string) (bool, error) { for _, item := range m.outOfScope { if item.MatchString(URL) { return false, nil } } if len(m.inScope) == 0 { return true, nil } var inScopeMatched bool for _, item := range m.inScope { if item.MatchString(URL) { inScopeMatched = true break } } return inScopeMatched, nil } ================================================ FILE: internal/server/scope/scope_test.go ================================================ package scope import ( "testing" urlutil "github.com/projectdiscovery/utils/url" "github.com/stretchr/testify/require" ) func TestManagerValidate(t *testing.T) { t.Run("url", func(t *testing.T) { manager, err := NewManager([]string{`example`}, []string{`logout\.php`}) require.NoError(t, err, "could not create scope manager") parsed, _ := urlutil.Parse("https://test.com/index.php/example") validated, err := manager.Validate(parsed.URL) require.NoError(t, err, "could not validate url") require.True(t, validated, "could not get correct in-scope validation") parsed, _ = urlutil.Parse("https://test.com/logout.php") validated, err = manager.Validate(parsed.URL) require.NoError(t, err, "could not validate url") require.False(t, validated, "could not get correct out-scope validation") }) } ================================================ FILE: internal/server/server.go ================================================ package server import ( _ "embed" "fmt" "html/template" "net/http" "net/url" "strings" "sync/atomic" "time" "github.com/alitto/pond" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/internal/server/scope" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/stats" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/protocols" "github.com/projectdiscovery/utils/env" ) // DASTServer is a server that performs execution of fuzzing templates // on user input passed to the API. type DASTServer struct { echo *echo.Echo options *Options tasksPool *pond.WorkerPool deduplicator *requestDeduplicator scopeManager *scope.Manager startTime time.Time // metrics endpointsInQueue atomic.Int64 endpointsBeingTested atomic.Int64 nucleiExecutor *nucleiExecutor } // Options contains the configuration options for the server. type Options struct { // Address is the address to bind the server to Address string // Token is the token to use for authentication (optional) Token string // Templates is the list of templates to use for fuzzing Templates []string // Verbose is a flag that controls verbose output Verbose bool // Scope fields for fuzzer InScope []string OutScope []string OutputWriter output.Writer NucleiExecutorOptions *NucleiExecutorOptions } // New creates a new instance of the DAST server. func New(options *Options) (*DASTServer, error) { // If the user has specified no templates, use the default ones // for DAST only. if len(options.Templates) == 0 { options.Templates = []string{"dast/"} } // Disable bulk mode and single threaded execution // by auto adjusting in case of default values if options.NucleiExecutorOptions.Options.BulkSize == 25 && options.NucleiExecutorOptions.Options.TemplateThreads == 25 { options.NucleiExecutorOptions.Options.BulkSize = 1 options.NucleiExecutorOptions.Options.TemplateThreads = 1 } maxWorkers := env.GetEnvOrDefault[int]("FUZZ_MAX_WORKERS", 1) bufferSize := env.GetEnvOrDefault[int]("FUZZ_BUFFER_SIZE", 10000) server := &DASTServer{ options: options, tasksPool: pond.New(maxWorkers, bufferSize), deduplicator: newRequestDeduplicator(), startTime: time.Now(), } server.setupHandlers(false) executor, err := newNucleiExecutor(options.NucleiExecutorOptions) if err != nil { return nil, err } server.nucleiExecutor = executor scopeManager, err := scope.NewManager( options.InScope, options.OutScope, ) if err != nil { return nil, err } server.scopeManager = scopeManager var builder strings.Builder gologger.Debug().Msgf("Using %d parallel tasks with %d buffer", maxWorkers, bufferSize) if options.Token != "" { builder.WriteString(" (with token)") } gologger.Info().Msgf("DAST Server API: %s", server.buildURL("/fuzz")) gologger.Info().Msgf("DAST Server Stats URL: %s", server.buildURL("/stats")) return server, nil } func NewStatsServer(fuzzStatsDB *stats.Tracker) (*DASTServer, error) { server := &DASTServer{ nucleiExecutor: &nucleiExecutor{ executorOpts: &protocols.ExecutorOptions{ FuzzStatsDB: fuzzStatsDB, }, }, } server.setupHandlers(true) gologger.Info().Msgf("Stats UI URL: %s", server.buildURL("/stats")) return server, nil } func (s *DASTServer) Close() { s.nucleiExecutor.Close() _ = s.echo.Close() s.tasksPool.StopAndWaitFor(1 * time.Minute) } func (s *DASTServer) buildURL(endpoint string) string { values := make(url.Values) if s.options.Token != "" { values.Set("token", s.options.Token) } // Use url.URL struct to safely construct the URL u := &url.URL{ Scheme: "http", Host: s.options.Address, Path: endpoint, RawQuery: values.Encode(), } return u.String() } func (s *DASTServer) setupHandlers(onlyStats bool) { e := echo.New() e.Use(middleware.Recover()) if s.options.Verbose { cfg := middleware.DefaultLoggerConfig cfg.Skipper = func(c echo.Context) bool { // Skip /stats and /stats.json return c.Request().URL.Path == "/stats" || c.Request().URL.Path == "/stats.json" } e.Use(middleware.LoggerWithConfig(cfg)) } e.Use(middleware.CORS()) if s.options.Token != "" { e.Use(middleware.KeyAuthWithConfig(middleware.KeyAuthConfig{ KeyLookup: "query:token", Validator: func(key string, c echo.Context) (bool, error) { return key == s.options.Token, nil }, })) } e.HideBanner = true // POST /fuzz - Queue a request for fuzzing if !onlyStats { e.POST("/fuzz", s.handleRequest) } e.GET("/stats", s.handleStats) e.GET("/stats.json", s.handleStatsJSON) s.echo = e } func (s *DASTServer) Start() error { if err := s.echo.Start(s.options.Address); err != nil && err != http.ErrServerClosed { return err } return nil } // PostRequestsHandlerRequest is the request body for the /fuzz POST handler. type PostRequestsHandlerRequest struct { RawHTTP string `json:"raw_http"` URL string `json:"url"` } func (s *DASTServer) handleRequest(c echo.Context) error { var req PostRequestsHandlerRequest if err := c.Bind(&req); err != nil { fmt.Printf("Error binding request: %s\n", err) return err } // Validate the request if req.RawHTTP == "" || req.URL == "" { fmt.Printf("Missing required fields\n") return c.JSON(400, map[string]string{"error": "missing required fields"}) } s.endpointsInQueue.Add(1) s.tasksPool.Submit(func() { s.consumeTaskRequest(req) }) return c.NoContent(200) } type StatsResponse struct { DASTServerInfo DASTServerInfo `json:"dast_server_info"` DASTScanStatistics DASTScanStatistics `json:"dast_scan_statistics"` DASTScanStatusStatistics map[string]int64 `json:"dast_scan_status_statistics"` DASTScanSeverityBreakdown map[string]int64 `json:"dast_scan_severity_breakdown"` DASTScanErrorStatistics map[string]int64 `json:"dast_scan_error_statistics"` DASTScanStartTime time.Time `json:"dast_scan_start_time"` } type DASTServerInfo struct { NucleiVersion string `json:"nuclei_version"` NucleiTemplateVersion string `json:"nuclei_template_version"` NucleiDastServerAPI string `json:"nuclei_dast_server_api"` ServerAuthEnabled bool `json:"sever_auth_enabled"` } type DASTScanStatistics struct { EndpointsInQueue int64 `json:"endpoints_in_queue"` EndpointsBeingTested int64 `json:"endpoints_being_tested"` TotalTemplatesLoaded int64 `json:"total_dast_templates_loaded"` TotalTemplatesTested int64 `json:"total_dast_templates_tested"` TotalMatchedResults int64 `json:"total_matched_results"` TotalComponentsTested int64 `json:"total_components_tested"` TotalEndpointsTested int64 `json:"total_endpoints_tested"` TotalFuzzedRequests int64 `json:"total_fuzzed_requests"` TotalErroredRequests int64 `json:"total_errored_requests"` } func (s *DASTServer) getStats() (StatsResponse, error) { cfg := config.DefaultConfig resp := StatsResponse{ DASTServerInfo: DASTServerInfo{ NucleiVersion: config.Version, NucleiTemplateVersion: cfg.TemplateVersion, NucleiDastServerAPI: s.buildURL("/fuzz"), ServerAuthEnabled: s.options.Token != "", }, DASTScanStartTime: s.startTime, DASTScanStatistics: DASTScanStatistics{ EndpointsInQueue: s.endpointsInQueue.Load(), EndpointsBeingTested: s.endpointsBeingTested.Load(), TotalTemplatesLoaded: int64(len(s.nucleiExecutor.store.Templates())), }, } if s.nucleiExecutor.executorOpts.FuzzStatsDB != nil { fuzzStats := s.nucleiExecutor.executorOpts.FuzzStatsDB.GetStats() resp.DASTScanSeverityBreakdown = fuzzStats.SeverityCounts resp.DASTScanStatusStatistics = fuzzStats.StatusCodes resp.DASTScanStatistics.TotalMatchedResults = fuzzStats.TotalMatchedResults resp.DASTScanStatistics.TotalComponentsTested = fuzzStats.TotalComponentsTested resp.DASTScanStatistics.TotalEndpointsTested = fuzzStats.TotalEndpointsTested resp.DASTScanStatistics.TotalFuzzedRequests = fuzzStats.TotalFuzzedRequests resp.DASTScanStatistics.TotalTemplatesTested = fuzzStats.TotalTemplatesTested resp.DASTScanStatistics.TotalErroredRequests = fuzzStats.TotalErroredRequests resp.DASTScanErrorStatistics = fuzzStats.ErrorGroupedStats } return resp, nil } //go:embed templates/index.html var indexTemplate string func (s *DASTServer) handleStats(c echo.Context) error { stats, err := s.getStats() if err != nil { return c.JSON(500, map[string]string{"error": err.Error()}) } tmpl, err := template.New("index").Parse(indexTemplate) if err != nil { return c.JSON(500, map[string]string{"error": err.Error()}) } return tmpl.Execute(c.Response().Writer, stats) } func (s *DASTServer) handleStatsJSON(c echo.Context) error { resp, err := s.getStats() if err != nil { return c.JSON(500, map[string]string{"error": err.Error()}) } return c.JSONPretty(200, resp, " ") } ================================================ FILE: internal/server/templates/index.html ================================================ DAST Scan Report
__ _ ____ __ _______/ /__ (_) / __ \/ / / / ___/ / _ \/ / / / / / /_/ / /__/ / __/ / /_/ /_/\__,_/\___/_/\___/_/ {{.DASTServerInfo.NucleiVersion}} projectdiscovery.io Dynamic Application Security Testing (DAST) API Server
[+] Server started at: {{.DASTScanStartTime}}
[*] Server Configuration
Nuclei Version{{.DASTServerInfo.NucleiVersion}}
Template Version{{.DASTServerInfo.NucleiTemplateVersion}}
DAST Server API{{.DASTServerInfo.NucleiDastServerAPI}}
Auth Status{{if .DASTServerInfo.ServerAuthEnabled}}ENABLED{{else}}DISABLED{{end}}
[+] Scan Progress
Total Results{{.DASTScanStatistics.TotalMatchedResults}} findings
Endpoints In Queue{{.DASTScanStatistics.EndpointsInQueue}}
Currently Testing{{.DASTScanStatistics.EndpointsBeingTested}}
Components Tested{{.DASTScanStatistics.TotalComponentsTested}}
Endpoints Tested{{.DASTScanStatistics.TotalEndpointsTested}}
Templates Loaded{{.DASTScanStatistics.TotalTemplatesLoaded}}
Templates Tested{{.DASTScanStatistics.TotalTemplatesTested}}
Total Requests{{.DASTScanStatistics.TotalFuzzedRequests}}
Total Errors{{.DASTScanStatistics.TotalErroredRequests}}
[!] Security Findings
Critical
{{index .DASTScanSeverityBreakdown "critical"}} findings
High
{{index .DASTScanSeverityBreakdown "high"}} findings
Medium
{{index .DASTScanSeverityBreakdown "medium"}} findings
Low
{{index .DASTScanSeverityBreakdown "low"}} findings
Info
{{index .DASTScanSeverityBreakdown "info"}} findings
[-] Status Codes Breakdown
Response Codes
{{range $status, $count := .DASTScanStatusStatistics}}
  {{$status}}{{$count}} times
{{end}}
[-] Error Breakdown
{{range $error, $count := .DASTScanErrorStatistics}}
{{$error}}
{{$count}} times
{{end}}
================================================ FILE: lib/README.md ================================================ ## Using Nuclei as Library Nuclei was primarily built as a CLI tool, but with increasing choice of users wanting to use nuclei as library in their own automation, we have added a simplified Library/SDK of nuclei in v3 ### Installation To add nuclei as a library to your go project, you can use the following command: ```bash go get -u github.com/projectdiscovery/nuclei/v3/lib ``` Or add below import to your go file and let IDE handle the rest: ```go import nuclei "github.com/projectdiscovery/nuclei/v3/lib" ``` ## Basic Example of using Nuclei Library/SDK ```go // create nuclei engine with options ne, err := nuclei.NewNucleiEngine( nuclei.WithTemplateFilters(nuclei.TemplateFilters{Severity: "critical"}), // run critical severity templates only ) if err != nil { panic(err) } // load targets and optionally probe non http/https targets ne.LoadTargets([]string{"scanme.sh"}, false) err = ne.ExecuteWithCallback(nil) if err != nil { panic(err) } defer ne.Close() ``` ## Advanced Example of using Nuclei Library/SDK For Various use cases like batching etc. you might want to run nuclei in goroutines this can be done by using `nuclei.NewThreadSafeNucleiEngine` ```go // create nuclei engine with options ne, err := nuclei.NewThreadSafeNucleiEngine() if err != nil{ panic(err) } // setup waitgroup to handle concurrency wg := &sync.WaitGroup{} // scan 1 = run dns templates on scanme.sh wg.Add(1) go func() { defer wg.Done() err = ne.ExecuteNucleiWithOpts([]string{"scanme.sh"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "http"})) if err != nil { panic(err) } }() // scan 2 = run http templates on honey.scanme.sh wg.Add(1) go func() { defer wg.Done() err = ne.ExecuteNucleiWithOpts([]string{"honey.scanme.sh"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "dns"})) if err != nil { panic(err) } }() // wait for all scans to finish wg.Wait() defer ne.Close() ``` ## More Documentation For complete documentation of nuclei library, please refer to [godoc](https://pkg.go.dev/github.com/projectdiscovery/nuclei/v3/lib) which contains all available options and methods. ### Note | :exclamation: **Disclaimer** | |---------------------------------| | **This project is in active development**. Expect breaking changes with releases. Review the release changelog before updating. | | This project was 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. | ================================================ FILE: lib/config.go ================================================ package nuclei import ( "context" "errors" "os" "time" "github.com/projectdiscovery/goflags" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/utils" "github.com/projectdiscovery/utils/errkit" "github.com/projectdiscovery/nuclei/v3/pkg/authprovider" "github.com/projectdiscovery/nuclei/v3/pkg/catalog" "github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/progress" "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/utils/vardump" "github.com/projectdiscovery/nuclei/v3/pkg/templates/types" pkgtypes "github.com/projectdiscovery/nuclei/v3/pkg/types" ) // TemplateSources contains template sources // which define where to load templates from type TemplateSources struct { Templates []string // template file/directory paths Workflows []string // workflow file/directory paths RemoteTemplates []string // remote template urls RemoteWorkflows []string // remote workflow urls TrustedDomains []string // trusted domains for remote templates/workflows } // WithTemplatesOrWorkflows sets templates / workflows to use /load func WithTemplatesOrWorkflows(sources TemplateSources) NucleiSDKOptions { return func(e *NucleiEngine) error { // by default all of these values are empty e.opts.Templates = sources.Templates e.opts.Workflows = sources.Workflows e.opts.TemplateURLs = sources.RemoteTemplates e.opts.WorkflowURLs = sources.RemoteWorkflows e.opts.RemoteTemplateDomainList = append(e.opts.RemoteTemplateDomainList, sources.TrustedDomains...) return nil } } // config contains all SDK configuration options type TemplateFilters struct { Severity string // filter by severities (accepts CSV values of info, low, medium, high, critical) ExcludeSeverities string // filter by excluding severities (accepts CSV values of info, low, medium, high, critical) ProtocolTypes string // filter by protocol types ExcludeProtocolTypes string // filter by excluding protocol types Authors []string // filter by author Tags []string // filter by tags present in template ExcludeTags []string // filter by excluding tags present in template IncludeTags []string // filter by including tags present in template IDs []string // filter by template IDs ExcludeIDs []string // filter by excluding template IDs TemplateCondition []string // DSL condition/ expression } // WithTemplateFilters sets template filters and only templates matching the filters will be // loaded and executed func WithTemplateFilters(filters TemplateFilters) NucleiSDKOptions { return func(e *NucleiEngine) error { s := severity.Severities{} if err := s.Set(filters.Severity); err != nil { return err } es := severity.Severities{} if err := es.Set(filters.ExcludeSeverities); err != nil { return err } pt := types.ProtocolTypes{} if err := pt.Set(filters.ProtocolTypes); err != nil { return err } ept := types.ProtocolTypes{} if err := ept.Set(filters.ExcludeProtocolTypes); err != nil { return err } e.opts.Authors = filters.Authors e.opts.Tags = filters.Tags e.opts.ExcludeTags = filters.ExcludeTags e.opts.IncludeTags = filters.IncludeTags e.opts.IncludeIds = filters.IDs e.opts.ExcludeIds = filters.ExcludeIDs e.opts.Severities = s e.opts.ExcludeSeverities = es e.opts.Protocols = pt e.opts.ExcludeProtocols = ept e.opts.IncludeConditions = filters.TemplateCondition return nil } } // InteractshOpts contains options for interactsh type InteractshOpts interactsh.Options // WithInteractshOptions sets interactsh options func WithInteractshOptions(opts InteractshOpts) NucleiSDKOptions { return func(e *NucleiEngine) error { // WithInteractshOptions can be used when creating ThreadSafeNucleiEngine but not after it's initialized if e.mode == threadSafe && e.interactshOpts != nil { return errkit.Wrap(ErrOptionsNotSupported, "WithInteractshOptions") } optsPtr := &opts e.interactshOpts = (*interactsh.Options)(optsPtr) return nil } } // Concurrency options type Concurrency struct { TemplateConcurrency int // number of templates to run concurrently (per host in host-spray mode) HostConcurrency int // number of hosts to scan concurrently (per template in template-spray mode) HeadlessHostConcurrency int // number of hosts to scan concurrently for headless templates (per template in template-spray mode) HeadlessTemplateConcurrency int // number of templates to run concurrently for headless templates (per host in host-spray mode) JavascriptTemplateConcurrency int // number of templates to run concurrently for javascript templates (per host in host-spray mode) TemplatePayloadConcurrency int // max concurrent payloads to run for a template (a good default is 25) ProbeConcurrency int // max concurrent http probes to run (a good default is 50) } // WithConcurrency sets concurrency options func WithConcurrency(opts Concurrency) NucleiSDKOptions { return func(e *NucleiEngine) error { // minimum required is 1 if opts.TemplateConcurrency <= 0 { return errors.New("template threads must be at least 1") } if opts.HostConcurrency <= 0 { return errors.New("host concurrency must be at least 1") } if opts.HeadlessHostConcurrency <= 0 { return errors.New("headless host concurrency must be at least 1") } if opts.HeadlessTemplateConcurrency <= 0 { return errors.New("headless template threads must be at least 1") } if opts.JavascriptTemplateConcurrency <= 0 { return errors.New("js must be at least 1") } if opts.TemplatePayloadConcurrency <= 0 { return errors.New("payload concurrency must be at least 1") } if opts.ProbeConcurrency <= 0 { return errors.New("probe concurrency must be at least 1") } e.opts.TemplateThreads = opts.TemplateConcurrency e.opts.BulkSize = opts.HostConcurrency e.opts.HeadlessBulkSize = opts.HeadlessHostConcurrency e.opts.HeadlessTemplateThreads = opts.HeadlessTemplateConcurrency e.opts.JsConcurrency = opts.JavascriptTemplateConcurrency e.opts.PayloadConcurrency = opts.TemplatePayloadConcurrency e.opts.ProbeConcurrency = opts.ProbeConcurrency return nil } } // WithResponseReadSize sets the maximum size of response to read in bytes. // A value of 0 means no limit. Recommended values: 1MB (1048576) to 10MB (10485760). func WithResponseReadSize(responseReadSize int) NucleiSDKOptions { return func(e *NucleiEngine) error { if responseReadSize < 0 { return errors.New("response read size must be non-negative") } e.opts.ResponseReadSize = responseReadSize return nil } } // WithGlobalRateLimit sets global rate (i.e all hosts combined) limit options // Deprecated: will be removed in favour of WithGlobalRateLimitCtx in next release func WithGlobalRateLimit(maxTokens int, duration time.Duration) NucleiSDKOptions { return WithGlobalRateLimitCtx(context.Background(), maxTokens, duration) } // WithGlobalRateLimitCtx allows setting a global rate limit for the entire engine func WithGlobalRateLimitCtx(ctx context.Context, maxTokens int, duration time.Duration) NucleiSDKOptions { return func(e *NucleiEngine) error { e.opts.RateLimit = maxTokens e.opts.RateLimitDuration = duration e.rateLimiter = utils.GetRateLimiter(ctx, e.opts.RateLimit, e.opts.RateLimitDuration) return nil } } // HeadlessOpts contains options for headless templates type HeadlessOpts struct { PageTimeout int // timeout for page load ShowBrowser bool HeadlessOptions []string UseChrome bool } // EnableHeadless allows execution of headless templates // // Warning: enabling headless mode may open up attack surface due to browser // usage and can be prone to exploitation by custom unverified templates if not // properly configured. func EnableHeadlessWithOpts(hopts *HeadlessOpts) NucleiSDKOptions { return func(e *NucleiEngine) error { e.opts.Headless = true if hopts != nil { e.opts.HeadlessOptionalArguments = hopts.HeadlessOptions e.opts.PageTimeout = hopts.PageTimeout e.opts.ShowBrowser = hopts.ShowBrowser e.opts.UseInstalledChrome = hopts.UseChrome } return nil } } // StatsOptions type StatsOptions struct { Interval int JSON bool MetricServerPort int } // EnableStats enables Stats collection with defined interval(in sec) and callback // Note: callback is executed in a separate goroutine func EnableStatsWithOpts(opts StatsOptions) NucleiSDKOptions { return func(e *NucleiEngine) error { if e.mode == threadSafe { return errkit.Wrap(ErrOptionsNotSupported, "EnableStatsWithOpts") } if opts.Interval == 0 { opts.Interval = 5 //sec } e.opts.StatsInterval = opts.Interval e.enableStats = true e.opts.StatsJSON = opts.JSON e.opts.MetricsPort = opts.MetricServerPort return nil } } // VerbosityOptions type VerbosityOptions struct { Verbose bool // show verbose output Silent bool // show only results Debug bool // show debug output DebugRequest bool // show request in debug output DebugResponse bool // show response in debug output ShowVarDump bool // show variable dumps in output } // WithVerbosity allows setting verbosity options of (internal) nuclei engine // and does not affect SDK output func WithVerbosity(opts VerbosityOptions) NucleiSDKOptions { return func(e *NucleiEngine) error { if e.mode == threadSafe { return errkit.Wrap(ErrOptionsNotSupported, "WithVerbosity") } e.opts.Verbose = opts.Verbose e.opts.Silent = opts.Silent e.opts.Debug = opts.Debug e.opts.DebugRequests = opts.DebugRequest e.opts.DebugResponse = opts.DebugResponse if opts.ShowVarDump { vardump.EnableVarDump = true } return nil } } // NetworkConfig contains network config options // ex: retries , httpx probe , timeout etc type NetworkConfig struct { DisableMaxHostErr bool // Disable max host error optimization (Hosts are not skipped even if they are not responding) Interface string // Interface to use for network scan InternalResolversList []string // Use a list of resolver LeaveDefaultPorts bool // Leave default ports for http/https MaxHostError int // Maximum number of host errors to allow before skipping that host Retries int // Number of retries SourceIP string // SourceIP sets custom source IP address for network requests SystemResolvers bool // Use system resolvers Timeout int // Timeout in seconds TrackError []string // Adds given errors to max host error watchlist } // WithNetworkConfig allows setting network config options func WithNetworkConfig(opts NetworkConfig) NucleiSDKOptions { return func(e *NucleiEngine) error { // WithNetworkConfig can be used when creating ThreadSafeNucleiEngine but not after it's initialized if e.mode == threadSafe && e.hostErrCache != nil { return errkit.Wrap(ErrOptionsNotSupported, "WithNetworkConfig") } e.opts.NoHostErrors = opts.DisableMaxHostErr e.opts.MaxHostError = opts.MaxHostError if e.opts.ShouldUseHostError() { maxHostError := opts.MaxHostError if e.opts.TemplateThreads > maxHostError { e.Logger.Warning().Msg("The concurrency value is higher than max-host-error") e.Logger.Info().Msgf("Adjusting max-host-error to the concurrency value: %d", e.opts.TemplateThreads) maxHostError = e.opts.TemplateThreads e.opts.MaxHostError = maxHostError } cache := hosterrorscache.New(maxHostError, hosterrorscache.DefaultMaxHostsCount, e.opts.TrackError) cache.SetVerbose(e.opts.Verbose) e.hostErrCache = cache } e.opts.Timeout = opts.Timeout e.opts.Retries = opts.Retries e.opts.LeaveDefaultPorts = opts.LeaveDefaultPorts e.opts.Interface = opts.Interface e.opts.SourceIP = opts.SourceIP e.opts.SystemResolvers = opts.SystemResolvers e.opts.InternalResolversList = opts.InternalResolversList return nil } } // WithProxy allows setting proxy options func WithProxy(proxy []string, proxyInternalRequests bool) NucleiSDKOptions { return func(e *NucleiEngine) error { if e.mode == threadSafe { return errkit.Wrap(ErrOptionsNotSupported, "WithProxy") } e.opts.Proxy = proxy e.opts.ProxyInternal = proxyInternalRequests return nil } } // WithScanStrategy allows setting scan strategy options func WithScanStrategy(strategy string) NucleiSDKOptions { return func(e *NucleiEngine) error { e.opts.ScanStrategy = strategy return nil } } // OutputWriter type OutputWriter output.Writer // UseOutputWriter allows setting custom output writer // by default a mock writer is used with user defined callback // if outputWriter is used callback will be ignored func UseOutputWriter(writer OutputWriter) NucleiSDKOptions { return func(e *NucleiEngine) error { if e.mode == threadSafe { return errkit.Wrap(ErrOptionsNotSupported, "UseOutputWriter") } e.customWriter = writer return nil } } // StatsWriter type StatsWriter progress.Progress // UseStatsWriter allows setting a custom stats writer // which can be used to write stats somewhere (ex: send to webserver etc) func UseStatsWriter(writer StatsWriter) NucleiSDKOptions { return func(e *NucleiEngine) error { if e.mode == threadSafe { return errkit.Wrap(ErrOptionsNotSupported, "UseStatsWriter") } e.customProgress = writer return nil } } // WithTemplateUpdateCallback allows setting a callback which will be called // when nuclei templates are outdated // Note: Nuclei-templates are crucial part of nuclei and using outdated templates or nuclei sdk is not recommended // as it may cause unexpected results due to compatibility issues func WithTemplateUpdateCallback(disableTemplatesAutoUpgrade bool, callback func(newVersion string)) NucleiSDKOptions { return func(e *NucleiEngine) error { if e.mode == threadSafe { return errkit.Wrap(ErrOptionsNotSupported, "WithTemplateUpdateCallback") } e.disableTemplatesAutoUpgrade = disableTemplatesAutoUpgrade e.onUpdateAvailableCallback = callback return nil } } // WithSandboxOptions allows setting supported sandbox options func WithSandboxOptions(allowLocalFileAccess bool, restrictLocalNetworkAccess bool) NucleiSDKOptions { return func(e *NucleiEngine) error { if e.mode == threadSafe { return errkit.Wrap(ErrOptionsNotSupported, "WithSandboxOptions") } e.opts.AllowLocalFileAccess = allowLocalFileAccess e.opts.RestrictLocalNetworkAccess = restrictLocalNetworkAccess return nil } } // EnableCodeTemplates allows loading/executing code protocol templates func EnableCodeTemplates() NucleiSDKOptions { return func(e *NucleiEngine) error { e.opts.EnableCodeTemplates = true e.opts.EnableSelfContainedTemplates = true return nil } } // EnableSelfContainedTemplates allows loading/executing self-contained templates func EnableSelfContainedTemplates() NucleiSDKOptions { return func(e *NucleiEngine) error { e.opts.EnableSelfContainedTemplates = true return nil } } // EnableGlobalMatchersTemplates allows loading/executing global-matchers templates func EnableGlobalMatchersTemplates() NucleiSDKOptions { return func(e *NucleiEngine) error { e.opts.EnableGlobalMatchersTemplates = true return nil } } // DisableTemplateCache disables template caching func DisableTemplateCache() NucleiSDKOptions { return func(e *NucleiEngine) error { e.opts.DoNotCacheTemplates = true return nil } } // EnableFileTemplates allows loading/executing file protocol templates func EnableFileTemplates() NucleiSDKOptions { return func(e *NucleiEngine) error { e.opts.EnableFileTemplates = true return nil } } // WithHeaders allows setting custom header/cookie to include in all http request in header:value format func WithHeaders(headers []string) NucleiSDKOptions { return func(e *NucleiEngine) error { e.opts.CustomHeaders = headers return nil } } // WithVars allows setting custom variables to use in templates/workflows context func WithVars(vars []string) NucleiSDKOptions { // Create a goflags.RuntimeMap runtimeVars := goflags.RuntimeMap{} for _, v := range vars { err := runtimeVars.Set(v) if err != nil { return func(e *NucleiEngine) error { return err } } } return func(e *NucleiEngine) error { e.opts.Vars = runtimeVars return nil } } // EnablePassiveMode allows enabling passive HTTP response processing mode func EnablePassiveMode() NucleiSDKOptions { return func(e *NucleiEngine) error { e.opts.OfflineHTTP = true e.opts.DisableHTTPProbe = true return nil } } // EnableMatcherStatus allows enabling matcher status func EnableMatcherStatus() NucleiSDKOptions { return func(e *NucleiEngine) error { e.opts.MatcherStatus = true return nil } } // WithAuthProvider allows setting a custom authprovider implementation func WithAuthProvider(provider authprovider.AuthProvider) NucleiSDKOptions { return func(e *NucleiEngine) error { e.authprovider = provider return nil } } // LoadSecretsFromFile allows loading secrets from file func LoadSecretsFromFile(files []string, prefetch bool) NucleiSDKOptions { return func(e *NucleiEngine) error { e.opts.SecretsFile = goflags.StringSlice(files) e.opts.PreFetchSecrets = prefetch return nil } } // DASTMode only run DAST templates func DASTMode() NucleiSDKOptions { return func(e *NucleiEngine) error { e.opts.DAST = true return nil } } // SignedTemplatesOnly only run signed templates and disabled loading all unsigned templates func SignedTemplatesOnly() NucleiSDKOptions { return func(e *NucleiEngine) error { e.opts.DisableUnsignedTemplates = true return nil } } // WithCatalog uses a supplied catalog func WithCatalog(cat catalog.Catalog) NucleiSDKOptions { return func(e *NucleiEngine) error { e.catalog = cat return nil } } // DisableUpdateCheck disables nuclei update check func DisableUpdateCheck() NucleiSDKOptions { return func(e *NucleiEngine) error { DefaultConfig.DisableUpdateCheck() return nil } } // WithResumeFile allows setting a resume file func WithResumeFile(file string) NucleiSDKOptions { return func(e *NucleiEngine) error { e.opts.Resume = file return nil } } // WithLogger allows setting a shared gologger instance func WithLogger(logger *gologger.Logger) NucleiSDKOptions { return func(e *NucleiEngine) error { e.Logger = logger if e.opts != nil { e.opts.Logger = logger } if e.executerOpts != nil { e.executerOpts.Logger = logger } return nil } } // WithOptions sets all options at once func WithOptions(opts *pkgtypes.Options) NucleiSDKOptions { return func(e *NucleiEngine) error { e.opts = opts return nil } } // WithTemporaryDirectory allows setting a parent directory for SDK-managed temporary files. // A temporary directory will be created inside the provided directory and cleaned up on engine close. // If not set, a temporary directory will be automatically created in the system temp location. // The parent directory is assumed to exist. func WithTemporaryDirectory(parentDir string) NucleiSDKOptions { return func(e *NucleiEngine) error { tmpDir, err := os.MkdirTemp(parentDir, "nuclei-tmp-*") if err != nil { return err } e.tmpDir = tmpDir return nil } } ================================================ FILE: lib/example_test.go ================================================ //go:build !race package nuclei_test import ( "os" "testing" "github.com/kitabisa/go-ci" nuclei "github.com/projectdiscovery/nuclei/v3/lib" "github.com/remeh/sizedwaitgroup" ) // A very simple example on how to use nuclei engine func ExampleNucleiEngine() { // create nuclei engine with options ne, err := nuclei.NewNucleiEngine( nuclei.WithTemplateFilters(nuclei.TemplateFilters{IDs: []string{"self-signed-ssl"}}), // only run self-signed-ssl template ) if err != nil { panic(err) } // load targets and optionally probe non http/https targets ne.LoadTargets([]string{"scanme.sh"}, false) // when callback is nil it nuclei will print JSON output to stdout err = ne.ExecuteWithCallback(nil) if err != nil { panic(err) } defer ne.Close() // Output: // [self-signed-ssl] scanme.sh:443 } func ExampleThreadSafeNucleiEngine() { // create nuclei engine with options ne, err := nuclei.NewThreadSafeNucleiEngine() if err != nil { panic(err) } // setup sizedWaitgroup to handle concurrency // here we are using sizedWaitgroup to limit concurrency to 1 // but can be anything in general sg := sizedwaitgroup.New(1) // 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{IDs: []string{"nameserver-fingerprint"}}), // only run self-signed-ssl template ) if err != nil { panic(err) } }() // scan 2 = run dns templates on honey.scanme.sh sg.Add() go func() { defer sg.Done() err = ne.ExecuteNucleiWithOpts([]string{"honey.scanme.sh"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "dns"})) if err != nil { panic(err) } }() // wait for all scans to finish sg.Wait() defer ne.Close() // Output: // [nameserver-fingerprint] scanme.sh // [caa-fingerprint] honey.scanme.sh } func TestMain(m *testing.M) { // this file only contains testtables examples https://go.dev/blog/examples // and actual functionality test are in sdk_test.go if ci.IsCI() { // no need to run this test on github actions return } os.Exit(m.Run()) } ================================================ FILE: lib/helper.go ================================================ package nuclei import ( "context" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" uncoverNuclei "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/uncover" "github.com/projectdiscovery/nuclei/v3/pkg/templates" "github.com/projectdiscovery/uncover" ) // helper.go file proxy execution of all nuclei functions that are nested deep inside multiple packages // but are helpful / useful while using nuclei as a library // GetTargetsFromUncover returns targets from uncover in given format . // supported formats are any string with [ip,host,port,url] placeholders func GetTargetsFromUncover(ctx context.Context, outputFormat string, opts *uncover.Options) (chan string, error) { return uncoverNuclei.GetTargetsFromUncover(ctx, outputFormat, opts) } // GetTargetsFromTemplateMetadata returns all targets by querying engine metadata (ex: fofo-query,shodan-query) etc from given templates . // supported formats are any string with [ip,host,port,url] placeholders func GetTargetsFromTemplateMetadata(ctx context.Context, templates []*templates.Template, outputFormat string, opts *uncover.Options) chan string { return uncoverNuclei.GetUncoverTargetsFromMetadata(ctx, templates, outputFormat, opts) } // DefaultConfig is instance of default nuclei configs // any mutations to this config will be reflected in all nuclei instances (saves some config to disk) var DefaultConfig *config.Config func init() { DefaultConfig = config.DefaultConfig } ================================================ FILE: lib/multi.go ================================================ package nuclei import ( "context" "time" "github.com/logrusorgru/aurora" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader" "github.com/projectdiscovery/nuclei/v3/pkg/core" "github.com/projectdiscovery/nuclei/v3/pkg/input/provider" "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/types" "github.com/projectdiscovery/nuclei/v3/pkg/utils" "github.com/projectdiscovery/utils/errkit" ) // unsafeOptions are those nuclei objects/instances/types // that are required to run nuclei engine but are not thread safe // hence they are ephemeral and are created on every ExecuteNucleiWithOpts invocation // in ThreadSafeNucleiEngine type unsafeOptions struct { executerOpts *protocols.ExecutorOptions engine *core.Engine } // createEphemeralObjects creates ephemeral nuclei objects/instances/types func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types.Options) (*unsafeOptions, error) { u := &unsafeOptions{} u.executerOpts = &protocols.ExecutorOptions{ Output: base.customWriter, Options: opts, Progress: base.customProgress, Catalog: base.catalog, IssuesClient: base.rc, RateLimiter: base.rateLimiter, Interactsh: base.interactshClient, HostErrorsCache: base.hostErrCache, Colorizer: aurora.NewAurora(true), ResumeCfg: types.NewResumeCfg(), Parser: base.parser, Browser: base.browserInstance, } if opts.ShouldUseHostError() && base.hostErrCache != nil { u.executerOpts.HostErrorsCache = base.hostErrCache } if opts.RateLimitMinute > 0 { opts.RateLimit = opts.RateLimitMinute opts.RateLimitDuration = time.Minute } if opts.RateLimit > 0 && opts.RateLimitDuration == 0 { opts.RateLimitDuration = time.Second } u.executerOpts.RateLimiter = utils.GetRateLimiter(ctx, opts.RateLimit, opts.RateLimitDuration) u.engine = core.New(opts) u.engine.SetExecuterOptions(u.executerOpts) return u, nil } // closeEphemeralObjects closes all resources used by ephemeral nuclei objects/instances/types func closeEphemeralObjects(u *unsafeOptions) { if u.executerOpts.RateLimiter != nil { u.executerOpts.RateLimiter.Stop() } // dereference all objects that were inherited from base nuclei engine // since these are meant to be closed globally by base nuclei engine u.executerOpts.Output = nil u.executerOpts.IssuesClient = nil u.executerOpts.Interactsh = nil u.executerOpts.HostErrorsCache = nil u.executerOpts.Progress = nil u.executerOpts.Catalog = nil u.executerOpts.Parser = nil } // ThreadSafeNucleiEngine is a tweaked version of nuclei.Engine whose methods are thread-safe // and can be used concurrently. Non-thread-safe methods start with Global prefix type ThreadSafeNucleiEngine struct { eng *NucleiEngine } // NewThreadSafeNucleiEngine creates a new nuclei engine with given options // whose methods are thread-safe and can be used concurrently // Note: Non-thread-safe methods start with Global prefix func NewThreadSafeNucleiEngineCtx(ctx context.Context, opts ...NucleiSDKOptions) (*ThreadSafeNucleiEngine, error) { defaultOptions := types.DefaultOptions() e := &NucleiEngine{ opts: defaultOptions, mode: threadSafe, ctx: ctx, Logger: defaultOptions.Logger, } for _, option := range opts { if err := option(e); err != nil { return nil, err } } if err := e.init(ctx); err != nil { return nil, err } return &ThreadSafeNucleiEngine{eng: e}, nil } // Deprecated: use NewThreadSafeNucleiEngineCtx instead func NewThreadSafeNucleiEngine(opts ...NucleiSDKOptions) (*ThreadSafeNucleiEngine, error) { return NewThreadSafeNucleiEngineCtx(context.Background(), opts...) } // GlobalLoadAllTemplates loads all templates from nuclei-templates repo // This method will load all templates based on filters given at the time of nuclei engine creation in opts func (e *ThreadSafeNucleiEngine) GlobalLoadAllTemplates() error { return e.eng.LoadAllTemplates() } // GlobalResultCallback sets a callback function which will be called for each result func (e *ThreadSafeNucleiEngine) GlobalResultCallback(callback func(event *output.ResultEvent)) { e.eng.resultCallbacks = []func(*output.ResultEvent){callback} } // ExecuteNucleiWithOptsCtx executes templates on targets and calls callback on each result(only if results are found) // This method can be called concurrently and it will use some global resources but can be run parallelly // by invoking this method with different options and targets // Note: Not all options are thread-safe. this method will throw error if you try to use non-thread-safe options func (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOptsCtx(ctx context.Context, targets []string, opts ...NucleiSDKOptions) error { baseOpts := e.eng.opts.Copy() tmpEngine := &NucleiEngine{opts: baseOpts, mode: threadSafe} for _, option := range opts { if err := option(tmpEngine); err != nil { return err } } // create ephemeral nuclei objects/instances/types using base nuclei engine unsafeOpts, err := createEphemeralObjects(ctx, e.eng, tmpEngine.opts) if err != nil { return err } // cleanup and stop all resources defer closeEphemeralObjects(unsafeOpts) // load templates workflowLoader, err := workflow.NewLoader(unsafeOpts.executerOpts) if err != nil { return errkit.Wrapf(err, "Could not create workflow loader: %s", err) } unsafeOpts.executerOpts.WorkflowLoader = workflowLoader store, err := loader.New(loader.NewConfig(tmpEngine.opts, e.eng.catalog, unsafeOpts.executerOpts)) if err != nil { return errkit.Wrapf(err, "Could not create loader client: %s", err) } if err := store.Load(); err != nil { return errkit.Wrapf(err, "Could not load templates: %s", err) } inputProvider := provider.NewSimpleInputProviderWithUrls(e.eng.opts.ExecutionId, targets...) if len(store.Templates()) == 0 && len(store.Workflows()) == 0 { return ErrNoTemplatesAvailable } if inputProvider.Count() == 0 { return ErrNoTargetsAvailable } engine := core.New(tmpEngine.opts) engine.SetExecuterOptions(unsafeOpts.executerOpts) _ = engine.ExecuteScanWithOpts(ctx, store.Templates(), inputProvider, false) engine.WorkPool().Wait() return nil } // ExecuteNucleiWithOpts is same as ExecuteNucleiWithOptsCtx but with default context // This is a placeholder and will be deprecated in future major release func (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOpts(targets []string, opts ...NucleiSDKOptions) error { return e.ExecuteNucleiWithOptsCtx(context.Background(), targets, opts...) } // Close all resources used by nuclei engine func (e *ThreadSafeNucleiEngine) Close() { e.eng.Close() } ================================================ FILE: lib/sdk.go ================================================ package nuclei import ( "bufio" "bytes" "context" "io" "os" "sync" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/authprovider" "github.com/projectdiscovery/nuclei/v3/pkg/catalog" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader" "github.com/projectdiscovery/nuclei/v3/pkg/core" "github.com/projectdiscovery/nuclei/v3/pkg/input/provider" providerTypes "github.com/projectdiscovery/nuclei/v3/pkg/input/types" "github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/progress" "github.com/projectdiscovery/nuclei/v3/pkg/protocols" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators" "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/headless/engine" "github.com/projectdiscovery/nuclei/v3/pkg/reporting" "github.com/projectdiscovery/nuclei/v3/pkg/templates" "github.com/projectdiscovery/nuclei/v3/pkg/templates/signer" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/ratelimit" "github.com/projectdiscovery/retryablehttp-go" "github.com/projectdiscovery/utils/errkit" ) // NucleiSDKOptions contains options for nuclei SDK type NucleiSDKOptions func(e *NucleiEngine) error var ( // ErrNotImplemented is returned when a feature is not implemented ErrNotImplemented = errkit.New("Not implemented") // ErrNoTemplatesAvailable is returned when no templates are available to execute ErrNoTemplatesAvailable = errkit.New("No templates available") // ErrNoTargetsAvailable is returned when no targets are available to scan ErrNoTargetsAvailable = errkit.New("No targets available") // ErrOptionsNotSupported is returned when an option is not supported in thread safe mode ErrOptionsNotSupported = errkit.New("Option not supported in thread safe mode") ) type engineMode uint const ( singleInstance engineMode = iota threadSafe ) // NucleiEngine is the Engine/Client for nuclei which // runs scans using templates and returns results type NucleiEngine struct { // user options resultCallbacks []func(event *output.ResultEvent) onFailureCallback func(event *output.InternalEvent) disableTemplatesAutoUpgrade bool enableStats bool onUpdateAvailableCallback func(newVersion string) // ready-status fields templatesLoaded bool // unexported core fields ctx context.Context interactshClient *interactsh.Client catalog catalog.Catalog rateLimiter *ratelimit.Limiter store *loader.Store httpxClient providerTypes.InputLivenessProbe inputProvider provider.InputProvider engine *core.Engine mode engineMode browserInstance *engine.Browser httpClient *retryablehttp.Client parser *templates.Parser authprovider authprovider.AuthProvider // unexported meta options opts *types.Options interactshOpts *interactsh.Options hostErrCache *hosterrorscache.Cache customWriter output.Writer customProgress progress.Progress rc reporting.Client executerOpts *protocols.ExecutorOptions // Logger instance for the engine Logger *gologger.Logger // Temporary directory for SDK-managed template files tmpDir string } // LoadAllTemplates loads all nuclei template based on given options func (e *NucleiEngine) LoadAllTemplates() error { workflowLoader, err := workflow.NewLoader(e.executerOpts) if err != nil { return errkit.Wrapf(err, "Could not create workflow loader: %s", err) } e.executerOpts.WorkflowLoader = workflowLoader e.store, err = loader.New(loader.NewConfig(e.opts, e.catalog, e.executerOpts)) if err != nil { return errkit.Wrapf(err, "Could not create loader client: %s", err) } if err := e.store.Load(); err != nil { return errkit.Wrapf(err, "Could not load templates: %s", err) } e.templatesLoaded = true return nil } // GetTemplates returns all nuclei templates that are loaded func (e *NucleiEngine) GetTemplates() []*templates.Template { if !e.templatesLoaded { _ = e.LoadAllTemplates() } return e.store.Templates() } // GetWorkflows returns all nuclei workflows that are loaded func (e *NucleiEngine) GetWorkflows() []*templates.Template { if !e.templatesLoaded { _ = e.LoadAllTemplates() } return e.store.Workflows() } // LoadTargets(urls/domains/ips only) adds targets to the nuclei engine func (e *NucleiEngine) LoadTargets(targets []string, probeNonHttp bool) { for _, target := range targets { if probeNonHttp { _ = e.inputProvider.SetWithProbe(e.opts.ExecutionId, target, e.httpxClient) } else { e.inputProvider.Set(e.opts.ExecutionId, target) } } } // LoadTargetsFromReader adds targets(urls/domains/ips only) from reader to the nuclei engine func (e *NucleiEngine) LoadTargetsFromReader(reader io.Reader, probeNonHttp bool) { buff := bufio.NewScanner(reader) for buff.Scan() { if probeNonHttp { _ = e.inputProvider.SetWithProbe(e.opts.ExecutionId, buff.Text(), e.httpxClient) } else { e.inputProvider.Set(e.opts.ExecutionId, buff.Text()) } } } // LoadTargetsWithHttpData loads targets that contain http data from file it currently supports // multiple formats like burp xml,openapi,swagger,proxify json // Note: this is mutually exclusive with LoadTargets and LoadTargetsFromReader func (e *NucleiEngine) LoadTargetsWithHttpData(filePath string, filemode string) error { e.opts.TargetsFilePath = filePath e.opts.InputFileMode = filemode httpProvider, err := provider.NewInputProvider(provider.InputOptions{Options: e.opts}) if err != nil { e.opts.TargetsFilePath = "" e.opts.InputFileMode = "" return err } e.inputProvider = httpProvider return nil } // GetExecuterOptions returns the nuclei executor options func (e *NucleiEngine) GetExecuterOptions() *protocols.ExecutorOptions { return e.executerOpts } // ParseTemplate parses a template from given data // template verification status can be accessed from template.Verified func (e *NucleiEngine) ParseTemplate(data []byte) (*templates.Template, error) { return templates.ParseTemplateFromReader(bytes.NewReader(data), nil, e.executerOpts) } // SignTemplate signs the tempalate using given signer func (e *NucleiEngine) SignTemplate(tmplSigner *signer.TemplateSigner, data []byte) ([]byte, error) { tmpl, err := e.ParseTemplate(data) if err != nil { return data, err } if tmpl.Verified { // already signed return data, nil } if len(tmpl.Workflows) > 0 { return data, templates.ErrNotATemplate } signatureData, err := tmplSigner.Sign(data, tmpl) if err != nil { return data, err } _, content := signer.ExtractSignatureAndContent(data) buff := bytes.NewBuffer(content) buff.WriteString("\n" + signatureData) return buff.Bytes(), err } func (e *NucleiEngine) closeInternal() { if e.interactshClient != nil { e.interactshClient.Close() } if e.rc != nil { e.rc.Close() } if e.customWriter != nil { e.customWriter.Close() } if e.customProgress != nil { e.customProgress.Stop() } if e.hostErrCache != nil { e.hostErrCache.Close() } if e.executerOpts.RateLimiter != nil { e.executerOpts.RateLimiter.Stop() } if e.rateLimiter != nil { e.rateLimiter.Stop() } if e.inputProvider != nil { e.inputProvider.Close() } if e.browserInstance != nil { e.browserInstance.Close() } if e.httpxClient != nil { _ = e.httpxClient.Close() } if e.tmpDir != "" { _ = os.RemoveAll(e.tmpDir) } if e.opts != nil { generators.ClearOptionsPayloadMap(e.opts) } } // Close all resources used by nuclei engine func (e *NucleiEngine) Close() { e.closeInternal() protocolinit.Close(e.opts.ExecutionId) } // ExecuteCallbackWithCtx executes templates on targets and calls callback on each result(only if results are found) // enable matcher-status option if you expect this callback to be called for all results regardless if it matched or not func (e *NucleiEngine) ExecuteCallbackWithCtx(ctx context.Context, callback ...func(event *output.ResultEvent)) error { if !e.templatesLoaded { _ = e.LoadAllTemplates() } if len(e.store.Templates()) == 0 && len(e.store.Workflows()) == 0 { return ErrNoTemplatesAvailable } if e.inputProvider.Count() == 0 { return ErrNoTargetsAvailable } filtered := []func(event *output.ResultEvent){} for _, cb := range callback { if cb != nil { filtered = append(filtered, cb) } } e.resultCallbacks = append(e.resultCallbacks, filtered...) templatesAndWorkflows := append(e.store.Templates(), e.store.Workflows()...) if len(templatesAndWorkflows) == 0 { return ErrNoTemplatesAvailable } var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() _ = e.engine.ExecuteScanWithOpts(ctx, templatesAndWorkflows, e.inputProvider, false) }() // wait for context to be cancelled select { case <-ctx.Done(): <-wait(&wg) // wait for scan to finish return ctx.Err() case <-wait(&wg): // scan finished } return nil } // ExecuteWithCallback is same as ExecuteCallbackWithCtx but with default context // Note this is deprecated and will be removed in future major release func (e *NucleiEngine) ExecuteWithCallback(callback ...func(event *output.ResultEvent)) error { ctx := context.Background() if e.ctx != nil { ctx = e.ctx } return e.ExecuteCallbackWithCtx(ctx, callback...) } // Options return nuclei Type Options func (e *NucleiEngine) Options() *types.Options { return e.opts } // Engine returns core Executer of nuclei func (e *NucleiEngine) Engine() *core.Engine { return e.engine } // Store returns store of nuclei func (e *NucleiEngine) Store() *loader.Store { return e.store } // NewNucleiEngineCtx creates a new nuclei engine instance with given context func NewNucleiEngineCtx(ctx context.Context, options ...NucleiSDKOptions) (*NucleiEngine, error) { // default options defaultOptions := types.DefaultOptions() e := &NucleiEngine{ opts: defaultOptions, mode: singleInstance, ctx: ctx, Logger: defaultOptions.Logger, } for _, option := range options { if err := option(e); err != nil { return nil, err } } if err := e.init(ctx); err != nil { return nil, err } return e, nil } // Deprecated: use NewNucleiEngineCtx instead func NewNucleiEngine(options ...NucleiSDKOptions) (*NucleiEngine, error) { return NewNucleiEngineCtx(context.Background(), options...) } // GetParser returns the template parser with cache func (e *NucleiEngine) GetParser() *templates.Parser { return e.parser } // wait for a waitgroup to finish func wait(wg *sync.WaitGroup) <-chan struct{} { ch := make(chan struct{}) go func() { defer close(ch) wg.Wait() }() return ch } // GetClusterTemplateIDs returns the template IDs for a given cluster ID // Returns nil if the cluster ID doesn't exist or engine hasn't executed yet func (e *NucleiEngine) GetClusterTemplateIDs(clusterID string) []string { if e.executerOpts == nil || e.executerOpts.ClusterMappings == nil { return nil } templateIDs, ok := e.executerOpts.ClusterMappings.Get(clusterID) if !ok { return nil } return templateIDs } // GetAllClusterMappings returns all cluster mappings // Returns nil if engine hasn't executed yet func (e *NucleiEngine) GetAllClusterMappings() map[string][]string { if e.executerOpts == nil || e.executerOpts.ClusterMappings == nil { return nil } return e.executerOpts.ClusterMappings.GetAll() } ================================================ FILE: lib/sdk_private.go ================================================ package nuclei import ( "context" "fmt" "os" "strings" "sync" "time" "github.com/projectdiscovery/nuclei/v3/pkg/input" "github.com/projectdiscovery/nuclei/v3/pkg/reporting" "github.com/logrusorgru/aurora" "github.com/pkg/errors" "github.com/projectdiscovery/gologger/levels" "github.com/projectdiscovery/httpx/common/httpx" "github.com/projectdiscovery/nuclei/v3/internal/runner" "github.com/projectdiscovery/nuclei/v3/pkg/authprovider" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk" "github.com/projectdiscovery/nuclei/v3/pkg/core" "github.com/projectdiscovery/nuclei/v3/pkg/input/provider" "github.com/projectdiscovery/nuclei/v3/pkg/installer" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/progress" "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/protocols/headless/engine" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool" "github.com/projectdiscovery/nuclei/v3/pkg/templates" "github.com/projectdiscovery/nuclei/v3/pkg/testutils" "github.com/projectdiscovery/nuclei/v3/pkg/types" nucleiUtils "github.com/projectdiscovery/nuclei/v3/pkg/utils" "github.com/projectdiscovery/ratelimit" ) // applyRequiredDefaults to options func (e *NucleiEngine) applyRequiredDefaults(ctx context.Context) { mockoutput := testutils.NewMockOutputWriter(e.opts.OmitTemplate) mockoutput.WriteCallback = func(event *output.ResultEvent) { if len(e.resultCallbacks) > 0 { for _, callback := range e.resultCallbacks { if callback != nil { callback(event) } } return } sb := strings.Builder{} fmt.Fprintf(&sb, "[%v] ", event.TemplateID) if event.Matched != "" { sb.WriteString(event.Matched) } else { sb.WriteString(event.Host) } fmt.Println(sb.String()) } if e.onFailureCallback != nil { mockoutput.FailureCallback = e.onFailureCallback } if e.customWriter != nil { e.customWriter = output.NewMultiWriter(e.customWriter, mockoutput) } else { e.customWriter = mockoutput } if e.customProgress == nil { e.customProgress = &testutils.MockProgressClient{} } if e.hostErrCache == nil && e.opts.ShouldUseHostError() { e.hostErrCache = hosterrorscache.New(30, hosterrorscache.DefaultMaxHostsCount, nil) } // setup interactsh if e.interactshOpts != nil { e.interactshOpts.Output = e.customWriter e.interactshOpts.Progress = e.customProgress } else { e.interactshOpts = interactsh.DefaultOptions(e.customWriter, e.rc, e.customProgress) } if e.rateLimiter == nil { e.rateLimiter = ratelimit.New(ctx, 150, time.Second) } if e.opts.ExcludeTags == nil { e.opts.ExcludeTags = []string{} } // these templates are known to have weak matchers // and idea is to disable them to avoid false positives e.opts.ExcludeTags = append(e.opts.ExcludeTags, config.ReadIgnoreFile().Tags...) e.inputProvider = provider.NewSimpleInputProvider() } // init func (e *NucleiEngine) init(ctx context.Context) error { // Update logger ref (if it was changed by [WithLogger]) // (Logger is already initialized) if e.opts.Logger != e.Logger { e.Logger = e.opts.Logger } if e.opts.Verbose { e.Logger.SetMaxLevel(levels.LevelVerbose) } else if e.opts.Debug { e.Logger.SetMaxLevel(levels.LevelDebug) } else if e.opts.Silent { e.Logger.SetMaxLevel(levels.LevelSilent) } if err := runner.ValidateOptions(e.opts); err != nil { return err } if e.opts.Parser != nil { if op, ok := e.opts.Parser.(*templates.Parser); ok { e.parser = op } } if e.parser == nil { e.parser = templates.NewParser() } if protocolstate.ShouldInit(e.opts.ExecutionId) { _ = protocolinit.Init(e.opts) } if e.opts.ProxyInternal && e.opts.AliveHttpProxy != "" || e.opts.AliveSocksProxy != "" { httpclient, err := httpclientpool.Get(e.opts, &httpclientpool.Configuration{}) if err != nil { return err } e.httpClient = httpclient } e.applyRequiredDefaults(ctx) var err error // setup progressbar if e.enableStats { progressInstance, progressErr := progress.NewStatsTicker(e.opts.StatsInterval, e.enableStats, e.opts.StatsJSON, false, e.opts.MetricsPort) if progressErr != nil { return err } e.customProgress = progressInstance e.interactshOpts.Progress = progressInstance } if err := reporting.CreateConfigIfNotExists(); err != nil { return err } // we don't support reporting config in sdk mode if e.rc, err = reporting.New(&reporting.Options{}, "", false); err != nil { return err } e.interactshOpts.IssuesClient = e.rc if e.httpClient != nil { e.interactshOpts.HTTPClient = e.httpClient } if e.interactshClient, err = interactsh.New(e.interactshOpts); err != nil { return err } if e.opts.Headless { if engine.MustDisableSandbox() { e.Logger.Warning().Msgf("The current platform and privileged user will run the browser without sandbox") } browser, err := engine.New(e.opts) if err != nil { return err } e.browserInstance = browser } if e.catalog == nil { e.catalog = disk.NewCatalog(config.DefaultConfig.TemplatesDirectory) } if e.tmpDir == "" { tmpDir, err := os.MkdirTemp("", "nuclei-tmp-*") if err != nil { return err } e.tmpDir = tmpDir } e.executerOpts = &protocols.ExecutorOptions{ Output: e.customWriter, Options: e.opts, Progress: e.customProgress, Catalog: e.catalog, IssuesClient: e.rc, RateLimiter: e.rateLimiter, Interactsh: e.interactshClient, Colorizer: aurora.NewAurora(true), ResumeCfg: types.NewResumeCfg(), Browser: e.browserInstance, Parser: e.parser, InputHelper: input.NewHelper(), TemporaryDirectory: e.tmpDir, Logger: e.opts.Logger, } if e.opts.ShouldUseHostError() && e.hostErrCache != nil { e.executerOpts.HostErrorsCache = e.hostErrCache } if len(e.opts.SecretsFile) > 0 { authTmplStore, err := runner.GetAuthTmplStore(e.opts, e.catalog, e.executerOpts) if err != nil { return errors.Wrap(err, "failed to load dynamic auth templates") } authOpts := &authprovider.AuthProviderOptions{SecretsFiles: e.opts.SecretsFile} authOpts.LazyFetchSecret = runner.GetLazyAuthFetchCallback(&runner.AuthLazyFetchOptions{ TemplateStore: authTmplStore, ExecOpts: e.executerOpts, }) // initialize auth provider provider, err := authprovider.NewAuthProvider(authOpts) if err != nil { return errors.Wrap(err, "could not create auth provider") } e.executerOpts.AuthProvider = provider } if e.authprovider != nil { e.executerOpts.AuthProvider = e.authprovider } // prefetch secrets to ensure authentication completes before scanning starts if e.executerOpts.AuthProvider != nil { if err := e.executerOpts.AuthProvider.PreFetchSecrets(); err != nil { return errors.Wrap(err, "could not prefetch secrets") } } if e.executerOpts.RateLimiter == nil { if e.opts.RateLimitMinute > 0 { e.opts.RateLimit = e.opts.RateLimitMinute e.opts.RateLimitDuration = time.Minute } if e.opts.RateLimit > 0 && e.opts.RateLimitDuration == 0 { e.opts.RateLimitDuration = time.Second } if e.opts.RateLimit == 0 && e.opts.RateLimitDuration == 0 { e.executerOpts.RateLimiter = ratelimit.NewUnlimited(ctx) } else { e.executerOpts.RateLimiter = ratelimit.New(ctx, uint(e.opts.RateLimit), e.opts.RateLimitDuration) } } // Handle the case where the user passed an existing parser that we can use as a cache if e.opts.Parser != nil { if cachedParser, ok := e.opts.Parser.(*templates.Parser); ok { e.parser = cachedParser e.opts.Parser = cachedParser e.executerOpts.Parser = cachedParser e.executerOpts.Options.Parser = cachedParser } } // Create a new parser if necessary if e.parser == nil { op := templates.NewParser() e.parser = op e.opts.Parser = op e.executerOpts.Parser = op e.executerOpts.Options.Parser = op } e.engine = core.New(e.opts) e.engine.SetExecuterOptions(e.executerOpts) httpxOptions := httpx.DefaultOptions httpxOptions.Timeout = 5 * time.Second if client, err := httpx.New(&httpxOptions); err != nil { return err } else { e.httpxClient = nucleiUtils.GetInputLivenessChecker(client) } // Only Happens once regardless how many times this function is called // This will update ignore file to filter out templates with weak matchers to avoid false positives // and also upgrade templates to latest version if available installer.NucleiSDKVersionCheck() if DefaultConfig.CanCheckForUpdates() { return e.processUpdateCheckResults() } return nil } type syncOnce struct { sync.Once } var updateCheckInstance = &syncOnce{} // processUpdateCheckResults processes update check results func (e *NucleiEngine) processUpdateCheckResults() error { var err error updateCheckInstance.Do(func() { if e.onUpdateAvailableCallback != nil { e.onUpdateAvailableCallback(config.DefaultConfig.LatestNucleiTemplatesVersion) } tm := installer.TemplateManager{} err = tm.UpdateIfOutdated() }) return err } ================================================ FILE: lib/sdk_test.go ================================================ package nuclei_test import ( "context" "log" "testing" "time" nuclei "github.com/projectdiscovery/nuclei/v3/lib" "github.com/stretchr/testify/require" ) func TestContextCancelNucleiEngine(t *testing.T) { // create nuclei engine with options ctx, cancel := context.WithCancel(context.Background()) ne, err := nuclei.NewNucleiEngineCtx(ctx, nuclei.WithTemplateFilters(nuclei.TemplateFilters{Tags: []string{"oast"}}), nuclei.EnableStatsWithOpts(nuclei.StatsOptions{MetricServerPort: 0}), ) require.NoError(t, err, "could not create nuclei engine") go func() { time.Sleep(time.Second * 2) cancel() log.Println("Test: context cancelled") }() // load targets and optionally probe non http/https targets ne.LoadTargets([]string{"http://honey.scanme.sh"}, false) // when callback is nil it nuclei will print JSON output to stdout err = ne.ExecuteWithCallback(nil) if err != nil { // we expect a context cancellation error require.ErrorIs(t, err, context.Canceled, "was expecting context cancellation error") } defer ne.Close() } func TestHeadlessOptionInitialization(t *testing.T) { ne, err := nuclei.NewNucleiEngineCtx( context.Background(), nuclei.EnableHeadlessWithOpts(&nuclei.HeadlessOpts{ PageTimeout: 20, ShowBrowser: false, UseChrome: false, HeadlessOptions: []string{}, }), ) require.NoError(t, err, "could not create nuclei engine with headless options") require.NotNil(t, ne, "nuclei engine should not be nil") // Verify logger is initialized require.NotNil(t, ne.Logger, "logger should be initialized") defer ne.Close() } ================================================ FILE: lib/tests/sdk_test.go ================================================ package sdk_test import ( "context" "os" "os/exec" "testing" "time" nuclei "github.com/projectdiscovery/nuclei/v3/lib" "github.com/projectdiscovery/utils/env" "github.com/stretchr/testify/require" "github.com/tarunKoyalwar/goleak" ) var knownLeaks = []goleak.Option{ // prettyify the output and generate dependency graph and more details instead of just stack output goleak.Pretty(), // net/http transport maintains idle connections which are closed with cooldown // hence they don't count as leaks goleak.IgnoreAnyFunction("net/http.(*http2ClientConn).readLoop"), } func TestSimpleNuclei(t *testing.T) { fn := func() { defer func() { // resources like leveldb have a delay to commit in-memory resources // to disk, typically 1-2 seconds, so we wait for 2 seconds time.Sleep(2 * time.Second) goleak.VerifyNone(t, knownLeaks...) }() ne, err := nuclei.NewNucleiEngineCtx( context.TODO(), nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "dns"}), // filter dns templates nuclei.EnableStatsWithOpts(nuclei.StatsOptions{JSON: true}), ) require.Nil(t, err) ne.LoadTargets([]string{"scanme.sh"}, false) // probe non http/https target is set to false here // when callback is nil it nuclei will print JSON output to stdout err = ne.ExecuteWithCallback(nil) require.Nil(t, err) defer ne.Close() } // this is shared test so needs to be run as separate process if env.GetEnvOrDefault("TestSimpleNuclei", false) { // run as new process cmd := exec.Command(os.Args[0], "-test.run=TestSimpleNuclei") cmd.Env = append(os.Environ(), "TestSimpleNuclei=true") out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("process ran with error %s, output: %s", err, out) } } else { fn() } } func TestSimpleNucleiRemote(t *testing.T) { fn := func() { defer func() { // resources like leveldb have a delay to commit in-memory resources // to disk, typically 1-2 seconds, so we wait for 2 seconds time.Sleep(2 * time.Second) goleak.VerifyNone(t, knownLeaks...) }() ne, err := nuclei.NewNucleiEngineCtx( context.TODO(), nuclei.WithTemplatesOrWorkflows( nuclei.TemplateSources{ RemoteTemplates: []string{"https://cloud.projectdiscovery.io/public/nameserver-fingerprint.yaml"}, }, ), ) require.Nil(t, err) ne.LoadTargets([]string{"scanme.sh"}, false) // probe non http/https target is set to false here err = ne.LoadAllTemplates() require.Nil(t, err, "could not load templates") // when callback is nil it nuclei will print JSON output to stdout err = ne.ExecuteWithCallback(nil) require.Nil(t, err) defer ne.Close() } // this is shared test so needs to be run as separate process if env.GetEnvOrDefault("TestSimpleNucleiRemote", false) { cmd := exec.Command(os.Args[0], "-test.run=TestSimpleNucleiRemote") cmd.Env = append(os.Environ(), "TestSimpleNucleiRemote=true") out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("process ran with error %s, output: %s", err, out) } } else { fn() } } func TestThreadSafeNuclei(t *testing.T) { fn := func() { defer func() { // resources like leveldb have a delay to commit in-memory resources // to disk, typically 1-2 seconds, so we wait for 2 seconds time.Sleep(2 * time.Second) goleak.VerifyNone(t, knownLeaks...) }() // create nuclei engine with options ne, err := nuclei.NewThreadSafeNucleiEngineCtx(context.TODO()) require.Nil(t, err) // scan 1 = run dns templates on scanme.sh t.Run("scanme.sh", func(t *testing.T) { err = ne.ExecuteNucleiWithOpts([]string{"scanme.sh"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "dns"})) require.Nil(t, err) }) // scan 2 = run dns templates on honey.scanme.sh t.Run("honey.scanme.sh", func(t *testing.T) { err = ne.ExecuteNucleiWithOpts([]string{"honey.scanme.sh"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: "dns"})) require.Nil(t, err) }) // wait for all scans to finish defer ne.Close() } if env.GetEnvOrDefault("TestThreadSafeNuclei", false) { cmd := exec.Command(os.Args[0], "-test.run=TestThreadSafeNuclei") cmd.Env = append(os.Environ(), "TestThreadSafeNuclei=true") out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("process ran with error %s, output: %s", err, out) } } else { fn() } } func TestWithVarsNuclei(t *testing.T) { fn := func() { defer func() { // resources like leveldb have a delay to commit in-memory resources // to disk, typically 1-2 seconds, so we wait for 2 seconds time.Sleep(2 * time.Second) goleak.VerifyNone(t, knownLeaks...) }() ne, err := nuclei.NewNucleiEngineCtx( context.TODO(), nuclei.EnableSelfContainedTemplates(), nuclei.WithTemplatesOrWorkflows(nuclei.TemplateSources{Templates: []string{"http/token-spray/api-1forge.yaml"}}), nuclei.WithVars([]string{"token=foobar"}), nuclei.WithVerbosity(nuclei.VerbosityOptions{Debug: true}), ) require.Nil(t, err) ne.LoadTargets([]string{"scanme.sh"}, true) // probe http/https target is set to true here err = ne.ExecuteWithCallback(nil) require.Nil(t, err) defer ne.Close() } // this is shared test so needs to be run as separate process if env.GetEnvOrDefault("TestWithVarsNuclei", false) { cmd := exec.Command(os.Args[0], "-test.run=TestWithVarsNuclei") cmd.Env = append(os.Environ(), "TestWithVarsNuclei=true") out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("process ran with error %s, output: %s", err, out) } } else { fn() } } ================================================ FILE: nuclei-jsonschema.json ================================================ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://templates.-template", "$ref": "#/$defs/templates.Template", "$defs": { "analyzers.AnalyzerTemplate": { "properties": { "name": { "type": "string" }, "parameters": { "$ref": "#/$defs/map[string]interface {}" } }, "additionalProperties": false, "type": "object", "required": [ "name", "parameters" ] }, "code.Request": { "properties": { "matchers": { "items": { "$ref": "#/$defs/matchers.Matcher" }, "type": "array", "title": "matchers to run on response", "description": "Detection mechanism to identify whether the request was successful by doing pattern matching" }, "extractors": { "items": { "$ref": "#/$defs/extractors.Extractor" }, "type": "array", "title": "extractors to run on response", "description": "Extractors contains the extraction mechanism for the request to identify and extract parts of the response" }, "matchers-condition": { "type": "string", "enum": [ "and", "or" ], "title": "condition between the matchers", "description": "Conditions between the matchers" }, "id": { "type": "string", "title": "id of the request", "description": "ID is the optional ID of the Request" }, "engine": { "items": { "type": "string" }, "type": "array", "title": "engine", "description": "Engine" }, "sandbox": { "$ref": "#/$defs/code.Sandbox", "title": "sandbox", "description": "Sandbox" }, "pre-condition": { "type": "string", "title": "pre-condition for the request", "description": "PreCondition is a condition which is evaluated before sending the request" }, "args": { "items": { "type": "string" }, "type": "array", "title": "args", "description": "Args" }, "pattern": { "type": "string", "title": "pattern", "description": "Pattern" }, "source": { "type": "string", "title": "source file/snippet", "description": "Source snippet" } }, "additionalProperties": false, "type": "object" }, "code.Sandbox": { "properties": { "working-dir": { "type": "string", "title": "working-dir", "description": "Working directory" }, "image": { "type": "string", "title": "image", "description": "Image" } }, "additionalProperties": false, "type": "object" }, "dns.DNSRequestTypeHolder": { "type": "string", "enum": [ "A", "NS", "DS", "CNAME", "SOA", "PTR", "MX", "TXT", "AAAA", "CAA", "TLSA", "ANY", "SRV" ], "title": "type of DNS request to make", "description": "Type is the type of DNS request to make" }, "dns.Request": { "properties": { "matchers": { "items": { "$ref": "#/$defs/matchers.Matcher" }, "type": "array", "title": "matchers to run on response", "description": "Detection mechanism to identify whether the request was successful by doing pattern matching" }, "extractors": { "items": { "$ref": "#/$defs/extractors.Extractor" }, "type": "array", "title": "extractors to run on response", "description": "Extractors contains the extraction mechanism for the request to identify and extract parts of the response" }, "matchers-condition": { "type": "string", "enum": [ "and", "or" ], "title": "condition between the matchers", "description": "Conditions between the matchers" }, "id": { "type": "string", "title": "id of the dns request", "description": "ID is the optional ID of the DNS Request" }, "name": { "type": "string", "title": "hostname to make dns request for", "description": "Name is the Hostname to make DNS request for" }, "type": { "$ref": "#/$defs/dns.DNSRequestTypeHolder", "title": "type of dns request to make", "description": "Type is the type of DNS request to make" }, "class": { "type": "string", "enum": [ "inet", "csnet", "chaos", "hesiod", "none", "any" ], "title": "class of DNS request", "description": "Class is the class of the DNS request" }, "retries": { "type": "integer", "title": "retries for dns request", "description": "Retries is the number of retries for the DNS request" }, "trace": { "type": "boolean", "title": "trace operation", "description": "Trace performs a trace operation for the target." }, "trace-max-recursion": { "type": "integer", "title": "trace-max-recursion level for dns request", "description": "TraceMaxRecursion is the number of max recursion allowed for trace operations" }, "attack": { "$ref": "#/$defs/generators.AttackTypeHolder", "title": "attack is the payload combination", "description": "Attack is the type of payload combinations to perform" }, "payloads": { "$ref": "#/$defs/map[string]interface {}", "title": "payloads for the network request", "description": "Payloads contains any payloads for the current request" }, "threads": { "type": "integer", "title": "threads for sending requests", "description": "Threads specifies number of threads to use sending requests. This enables Connection Pooling" }, "recursion": { "type": "boolean", "title": "recurse all servers", "description": "Recursion determines if resolver should recurse all records to get fresh results" }, "resolvers": { "items": { "type": "string" }, "type": "array", "title": "Resolvers", "description": "Define resolvers to use within the template" } }, "additionalProperties": false, "type": "object" }, "engine.Action": { "properties": { "args": { "patternProperties": { ".*": { "oneOf": [ { "type": "string" }, { "type": "integer" }, { "type": "boolean" } ] } }, "title": "arguments for headless action", "description": "Args contain arguments for the headless action" }, "name": { "type": "string", "title": "name for headless action", "description": "Name is the name assigned to the headless action" }, "description": { "type": "string", "title": "description for headless action", "description": "Description of the headless action" }, "action": { "$ref": "#/$defs/engine.ActionTypeHolder", "title": "action to perform", "description": "Type of actions to perform" } }, "additionalProperties": false, "type": "object", "required": [ "action" ] }, "engine.ActionTypeHolder": { "type": "string", "enum": [ "navigate", "script", "click", "rightclick", "text", "screenshot", "time", "select", "files", "waitdom", "waitfcp", "waitfmp", "waitidle", "waitload", "waitstable", "getresource", "extract", "setmethod", "addheader", "setheader", "deleteheader", "setbody", "waitevent", "waitdialog", "keyboard", "debug", "sleep", "waitvisible" ], "title": "action to perform", "description": "Type of actions to perform" }, "extractors.Extractor": { "properties": { "name": { "type": "string", "title": "name of the extractor", "description": "Name of the extractor" }, "type": { "$ref": "#/$defs/extractors.ExtractorTypeHolder" }, "regex": { "items": { "type": "string" }, "type": "array", "title": "regex to extract from part", "description": "Regex to extract from part" }, "group": { "type": "integer", "title": "group to extract from regex", "description": "Group to extract from regex" }, "kval": { "items": { "type": "string" }, "type": "array", "title": "kval pairs to extract from response", "description": "Kval pairs to extract from response" }, "json": { "items": { "type": "string" }, "type": "array", "title": "json jq expressions to extract data", "description": "JSON JQ expressions to evaluate from response part" }, "xpath": { "items": { "type": "string" }, "type": "array", "title": "html xpath expressions to extract data", "description": "XPath allows using xpath expressions to extract items from html response" }, "attribute": { "type": "string", "title": "optional attribute to extract from xpath", "description": "Optional attribute to extract from response XPath" }, "dsl": { "items": { "type": "string" }, "type": "array", "title": "dsl expressions to extract", "description": "Optional attribute to extract from response dsl" }, "part": { "type": "string", "title": "part of response to extract data from", "description": "Part of the request response to extract data from" }, "internal": { "type": "boolean", "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" }, "case-insensitive": { "type": "boolean", "title": "use case insensitive extract", "description": "use case insensitive extract" } }, "additionalProperties": false, "type": "object", "required": [ "type" ] }, "extractors.ExtractorTypeHolder": { "type": "string", "enum": [ "regex", "kval", "xpath", "json", "dsl" ], "title": "type of the extractor", "description": "Type of the extractor" }, "file.Request": { "properties": { "matchers": { "items": { "$ref": "#/$defs/matchers.Matcher" }, "type": "array", "title": "matchers to run on response", "description": "Detection mechanism to identify whether the request was successful by doing pattern matching" }, "extractors": { "items": { "$ref": "#/$defs/extractors.Extractor" }, "type": "array", "title": "extractors to run on response", "description": "Extractors contains the extraction mechanism for the request to identify and extract parts of the response" }, "matchers-condition": { "type": "string", "enum": [ "and", "or" ], "title": "condition between the matchers", "description": "Conditions between the matchers" }, "extensions": { "items": { "type": "string" }, "type": "array", "title": "extensions to match", "description": "List of extensions to perform matching on" }, "denylist": { "items": { "type": "string" }, "type": "array", "title": "denylist", "description": "List of files" }, "id": { "type": "string", "title": "id of the request", "description": "ID is the optional ID for the request" }, "max-size": { "type": "string", "title": "max size data to run request on", "description": "Maximum size of the file to run request on" }, "archive": { "type": "boolean", "title": "enable archives", "description": "Process compressed archives without unpacking" }, "mime-type": { "type": "boolean", "title": "enable filtering by mime-type", "description": "Filter files by mime-type" }, "no-recursive": { "type": "boolean", "title": "do not perform recursion", "description": "Specifies whether to not do recursive checks if folders are provided" } }, "additionalProperties": false, "type": "object" }, "fuzz.Rule": { "properties": { "type": { "type": "string", "enum": [ "replace", "prefix", "postfix", "infix", "replace-regex" ], "title": "type of rule", "description": "Type of fuzzing rule to perform" }, "part": { "type": "string", "enum": [ "query", "header", "path", "body", "cookie", "request" ], "title": "part of rule", "description": "Part of request rule to fuzz" }, "parts": { "items": { "type": "string", "enum": [ "query", "header", "path", "body", "cookie", "request" ] }, "type": "array", "title": "parts of rule", "description": "Part of request rule to fuzz" }, "mode": { "type": "string", "enum": [ "single", "multiple" ], "title": "mode of rule", "description": "Mode of request rule to fuzz" }, "keys": { "items": { "type": "string" }, "type": "array", "title": "keys of parameters to fuzz", "description": "Keys of parameters to fuzz" }, "keys-regex": { "items": { "type": "string" }, "type": "array", "title": "keys regex to fuzz", "description": "Regex of parameter keys to fuzz" }, "values": { "items": { "type": "string" }, "type": "array", "title": "values regex to fuzz", "description": "Regex of parameter values to fuzz" }, "fuzz": { "$ref": "#/$defs/fuzz.SliceOrMapSlice", "title": "payloads of fuzz rule", "description": "Payloads to perform fuzzing substitutions with" }, "replace-regex": { "type": "string", "title": "replace regex of rule", "description": "Regex for regex-replace rule type" } }, "additionalProperties": false, "type": "object" }, "fuzz.SliceOrMapSlice": { "items": { "oneOf": [ { "type": "string" }, { "type": "object" } ] }, "type": "array", "title": "Payloads of Fuzz Rule", "description": "Payloads to perform fuzzing substitutions with." }, "generators.AttackTypeHolder": { "type": "string", "enum": [ "batteringram", "pitchfork", "clusterbomb" ], "title": "type of the attack", "description": "Type of the attack" }, "headless.Request": { "properties": { "id": { "type": "string", "title": "id of the request", "description": "Optional ID of the headless request" }, "attack": { "$ref": "#/$defs/generators.AttackTypeHolder", "title": "attack is the payload combination", "description": "Attack is the type of payload combinations to perform" }, "payloads": { "$ref": "#/$defs/map[string]interface {}", "title": "payloads for the headless request", "description": "Payloads contains any payloads for the current request" }, "steps": { "items": { "$ref": "#/$defs/engine.Action" }, "type": "array", "title": "list of actions for headless request", "description": "List of actions to run for headless request" }, "user_agent": { "$ref": "#/$defs/userAgent.UserAgentHolder", "title": "user agent for the headless request", "description": "User agent for the headless request" }, "custom_user_agent": { "type": "string", "title": "custom user agent for the headless request", "description": "Custom user agent for the headless request" }, "stop-at-first-match": { "type": "boolean", "title": "stop at first match", "description": "Stop the execution after a match is found" }, "matchers": { "items": { "$ref": "#/$defs/matchers.Matcher" }, "type": "array", "title": "matchers to run on response", "description": "Detection mechanism to identify whether the request was successful by doing pattern matching" }, "extractors": { "items": { "$ref": "#/$defs/extractors.Extractor" }, "type": "array", "title": "extractors to run on response", "description": "Extractors contains the extraction mechanism for the request to identify and extract parts of the response" }, "matchers-condition": { "type": "string", "enum": [ "and", "or" ], "title": "condition between the matchers", "description": "Conditions between the matchers" }, "fuzzing": { "items": { "$ref": "#/$defs/fuzz.Rule" }, "type": "array", "title": "fuzzin rules for http fuzzing", "description": "Fuzzing describes rule schema to fuzz headless requests" }, "cookie-reuse": { "type": "boolean", "title": "optional cookie reuse enable", "description": "Optional setting that enables cookie reuse" }, "disable-cookie": { "type": "boolean", "title": "optional disable cookie reuse", "description": "Optional setting that disables cookie reuse" } }, "additionalProperties": false, "type": "object" }, "http.HTTPMethodTypeHolder": { "type": "string", "enum": [ "GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH", "PURGE", "DEBUG" ], "title": "method is the HTTP request method", "description": "Method is the HTTP Request Method" }, "http.Request": { "properties": { "matchers": { "items": { "$ref": "#/$defs/matchers.Matcher" }, "type": "array", "title": "matchers to run on response", "description": "Detection mechanism to identify whether the request was successful by doing pattern matching" }, "extractors": { "items": { "$ref": "#/$defs/extractors.Extractor" }, "type": "array", "title": "extractors to run on response", "description": "Extractors contains the extraction mechanism for the request to identify and extract parts of the response" }, "matchers-condition": { "type": "string", "enum": [ "and", "or" ], "title": "condition between the matchers", "description": "Conditions between the matchers" }, "path": { "items": { "type": "string" }, "type": "array", "title": "path(s) for the http request", "description": "Path(s) to send http requests to" }, "raw": { "items": { "type": "string" }, "type": "array", "description": "HTTP Requests in Raw Format" }, "id": { "type": "string", "title": "id for the http request", "description": "ID for the HTTP Request" }, "name": { "type": "string", "title": "name for the http request", "description": "Optional name for the HTTP Request" }, "attack": { "$ref": "#/$defs/generators.AttackTypeHolder", "title": "attack is the payload combination", "description": "Attack is the type of payload combinations to perform" }, "method": { "$ref": "#/$defs/http.HTTPMethodTypeHolder", "title": "method is the http request method", "description": "Method is the HTTP Request Method" }, "body": { "type": "string", "title": "body is the http request body", "description": "Body is an optional parameter which contains HTTP Request body" }, "payloads": { "$ref": "#/$defs/map[string]interface {}", "title": "payloads for the http request", "description": "Payloads contains any payloads for the current request" }, "headers": { "patternProperties": { ".*": { "oneOf": [ { "type": "string" }, { "type": "integer" }, { "type": "boolean" } ] } }, "title": "headers to send with the http request", "description": "Headers contains HTTP Headers to send with the request" }, "race_count": { "type": "integer", "title": "number of times to repeat request in race condition", "description": "Number of times to send a request in Race Condition Attack" }, "max-redirects": { "type": "integer", "title": "maximum number of redirects to follow", "description": "Maximum number of redirects that should be followed" }, "pipeline-concurrent-connections": { "type": "integer", "title": "number of pipelining connections", "description": "Number of connections to create during pipelining" }, "pipeline-requests-per-connection": { "type": "integer", "title": "number of requests to send per pipelining connections", "description": "Number of requests to send per connection when pipelining" }, "threads": { "type": "integer", "title": "threads for sending requests", "description": "Threads specifies number of threads to use sending requests. This enables Connection Pooling" }, "max-size": { "type": "integer", "title": "maximum http response body size", "description": "Maximum size of http response body to read in bytes" }, "fuzzing": { "items": { "$ref": "#/$defs/fuzz.Rule" }, "type": "array", "title": "fuzzin rules for http fuzzing", "description": "Fuzzing describes rule schema to fuzz http requests" }, "analyzer": { "$ref": "#/$defs/analyzers.AnalyzerTemplate", "title": "analyzer for http request", "description": "Analyzer for HTTP Request" }, "self-contained": { "type": "boolean" }, "signature": { "$ref": "#/$defs/http.SignatureTypeHolder", "title": "signature is the http request signature method", "description": "Signature is the HTTP Request signature Method" }, "skip-secret-file": { "type": "boolean", "title": "bypass secret file", "description": "Skips the authentication or authorization configured in the secret file" }, "cookie-reuse": { "type": "boolean", "title": "optional cookie reuse enable", "description": "Optional setting that enables cookie reuse" }, "disable-cookie": { "type": "boolean", "title": "optional disable cookie reuse", "description": "Optional setting that disables cookie reuse" }, "read-all": { "type": "boolean", "title": "force read all body", "description": "Enables force reading of entire unsafe http request body" }, "redirects": { "type": "boolean", "title": "follow http redirects", "description": "Specifies whether redirects should be followed by the HTTP Client" }, "host-redirects": { "type": "boolean", "title": "follow same host http redirects", "description": "Specifies whether redirects to the same host should be followed by the HTTP Client" }, "pipeline": { "type": "boolean", "title": "perform HTTP 1.1 pipelining", "description": "Pipeline defines if the attack should be performed with HTTP 1.1 Pipelining" }, "unsafe": { "type": "boolean", "title": "use rawhttp non-strict-rfc client", "description": "Unsafe specifies whether to use rawhttp engine for sending Non RFC-Compliant requests" }, "race": { "type": "boolean", "title": "perform race-http request coordination attack", "description": "Race determines if all the request have to be attempted at the same time (Race Condition)" }, "req-condition": { "type": "boolean", "title": "preserve request history", "description": "Automatically assigns numbers to requests and preserves their history" }, "stop-at-first-match": { "type": "boolean", "title": "stop at first match", "description": "Stop the execution after a match is found" }, "skip-variables-check": { "type": "boolean", "title": "skip variable checks", "description": "Skips the check for unresolved variables in request" }, "iterate-all": { "type": "boolean", "title": "iterate all the values", "description": "Iterates all the values extracted from internal extractors" }, "digest-username": { "type": "string", "title": "specifies the username for digest authentication", "description": "Optional parameter which specifies the username for digest auth" }, "digest-password": { "type": "string", "title": "specifies the password for digest authentication", "description": "Optional parameter which specifies the password for digest auth" }, "disable-path-automerge": { "type": "boolean", "title": "disable auto merging of path", "description": "Disable merging target url path with raw request path" }, "pre-condition": { "items": { "$ref": "#/$defs/matchers.Matcher" }, "type": "array", "title": "pre-condition for fuzzing/dast", "description": "PreCondition is matcher-like field to check if fuzzing should be performed on this request or not" }, "pre-condition-operator": { "type": "string", "enum": [ "and", "or" ], "title": "condition between the filters", "description": "Operator to use between multiple per-conditions" }, "global-matchers": { "type": "boolean", "title": "global matchers", "description": "marks matchers as static and applies globally to all result events from other templates" } }, "additionalProperties": false, "type": "object" }, "http.SignatureTypeHolder": { "type": "string", "enum": [ "AWS" ], "title": "type of the signature", "description": "Type of the signature" }, "javascript.Request": { "properties": { "matchers": { "items": { "$ref": "#/$defs/matchers.Matcher" }, "type": "array", "title": "matchers to run on response", "description": "Detection mechanism to identify whether the request was successful by doing pattern matching" }, "extractors": { "items": { "$ref": "#/$defs/extractors.Extractor" }, "type": "array", "title": "extractors to run on response", "description": "Extractors contains the extraction mechanism for the request to identify and extract parts of the response" }, "matchers-condition": { "type": "string", "enum": [ "and", "or" ], "title": "condition between the matchers", "description": "Conditions between the matchers" }, "id": { "type": "string", "title": "id of the request", "description": "ID is the optional ID of the Request" }, "init": { "type": "string", "title": "init javascript code", "description": "Init is the javascript code to execute after compiling template" }, "pre-condition": { "type": "string", "title": "pre-condition for the request", "description": "PreCondition is a condition which is evaluated before sending the request" }, "args": { "$ref": "#/$defs/map[string]interface {}" }, "code": { "type": "string", "title": "code to execute in javascript", "description": "Executes inline javascript code for the request" }, "stop-at-first-match": { "type": "boolean", "title": "stop at first match", "description": "Stop the execution after a match is found" }, "attack": { "$ref": "#/$defs/generators.AttackTypeHolder", "title": "attack is the payload combination", "description": "Attack is the type of payload combinations to perform" }, "threads": { "type": "integer", "title": "threads for sending requests", "description": "Threads specifies number of threads to use sending requests. This enables Connection Pooling" }, "payloads": { "$ref": "#/$defs/map[string]interface {}", "title": "payloads for the webosocket request", "description": "Payloads contains any payloads for the current request" } }, "additionalProperties": false, "type": "object" }, "map[string]interface {}": { "type": "object" }, "map[string]string": { "additionalProperties": { "type": "string" }, "type": "object" }, "matchers.Matcher": { "properties": { "type": { "$ref": "#/$defs/matchers.MatcherTypeHolder", "title": "type of matcher", "description": "Type of the matcher" }, "condition": { "type": "string", "enum": [ "and", "or" ], "title": "condition between matcher variables", "description": "Condition between the matcher variables" }, "part": { "type": "string", "title": "part of response to match", "description": "Part of response to match data from" }, "negative": { "type": "boolean", "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" }, "name": { "type": "string", "title": "name of the matcher", "description": "Name of the matcher" }, "status": { "items": { "type": "integer" }, "type": "array", "title": "status to match", "description": "Status to match for the response" }, "size": { "items": { "type": "integer" }, "type": "array", "title": "acceptable size for response", "description": "Size is the acceptable size for the response" }, "words": { "items": { "type": "string" }, "type": "array", "title": "words to match in response", "description": " Words contains word patterns required to be present in the response part" }, "regex": { "items": { "type": "string" }, "type": "array", "title": "regex to match in response", "description": "Regex contains regex patterns required to be present in the response part" }, "binary": { "items": { "type": "string" }, "type": "array", "title": "binary patterns to match in response", "description": "Binary are the binary patterns required to be present in the response part" }, "dsl": { "items": { "type": "string" }, "type": "array", "title": "dsl expressions to match in response", "description": "DSL are the dsl expressions that will be evaluated as part of nuclei matching rules" }, "xpath": { "items": { "type": "string" }, "type": "array", "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" }, "encoding": { "type": "string", "enum": [ "hex" ], "title": "encoding for word field", "description": "Optional encoding for the word fields" }, "case-insensitive": { "type": "boolean", "title": "use case insensitive match", "description": "use case insensitive match" }, "match-all": { "type": "boolean", "title": "match all values", "description": "match all matcher values ignoring condition" }, "internal": { "type": "boolean", "title": "hide matcher from output", "description": "hide matcher from output" } }, "additionalProperties": false, "type": "object", "required": [ "type" ] }, "matchers.MatcherTypeHolder": { "type": "string", "enum": [ "word", "regex", "binary", "status", "size", "dsl", "xpath" ], "title": "type of the matcher", "description": "Type of the matcher" }, "model.Classification": { "properties": { "cve-id": { "$ref": "#/$defs/stringslice.StringOrSlice", "title": "cve ids for the template", "description": "CVE IDs for the template" }, "cwe-id": { "$ref": "#/$defs/stringslice.StringOrSlice", "title": "cwe ids for the template", "description": "CWE IDs for the template" }, "cvss-metrics": { "type": "string", "title": "cvss metrics for the template", "description": "CVSS Metrics for the template", "examples": [ "3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" ] }, "cvss-score": { "type": "number", "title": "cvss score for the template", "description": "CVSS Score for the template", "examples": [ 9.8 ] }, "epss-score": { "type": "number", "title": "epss score for the template", "description": "EPSS Score for the template", "examples": [ 0.42509 ] }, "epss-percentile": { "type": "number", "title": "epss percentile for the template", "description": "EPSS Percentile for the template", "examples": [ 0.42509 ] }, "cpe": { "type": "string", "title": "cpe for the template", "description": "CPE for the template", "examples": [ "cpe:/a:vendor:product:version" ] } }, "additionalProperties": false, "type": "object" }, "model.Info": { "properties": { "name": { "type": "string", "title": "name of the template", "description": "Name is a short summary of what the template does", "examples": [ "Nagios Default Credentials Check" ] }, "author": { "$ref": "#/$defs/stringslice.StringOrSlice", "oneOf": [ { "type": "string", "examples": [ "pdteam" ] }, { "type": "array", "examples": [ "pdteam,mr.robot" ] } ], "title": "author of the template", "description": "Author is the author of the template" }, "tags": { "$ref": "#/$defs/stringslice.StringOrSlice", "title": "tags of the template", "description": "Any tags for the template" }, "description": { "type": "string", "title": "description of the template", "description": "In-depth explanation on what the template does", "examples": [ "Bower is a package manager which stores package information in the bower.json file" ] }, "impact": { "type": "string", "title": "impact of the template", "description": "In-depth explanation on the impact of the issue found by the template", "examples": [ "Successful exploitation of this vulnerability could allow an attacker to execute arbitrary SQL queries" ] }, "reference": { "$ref": "#/$defs/stringslice.StringOrSlice", "title": "references for the template", "description": "Links relevant to the template" }, "severity": { "$ref": "#/$defs/severity.Holder" }, "metadata": { "$ref": "#/$defs/map[string]interface {}", "type": "object", "title": "additional metadata for the template", "description": "Additional metadata fields for the template" }, "classification": { "$ref": "#/$defs/model.Classification", "type": "object", "title": "classification info for the template", "description": "Classification information for the template" }, "remediation": { "type": "string", "title": "remediation steps for the template", "description": "In-depth explanation on how to fix the issues found by the template", "examples": [ "Change the default administrative username and password of Apache ActiveMQ by editing the file jetty-realm.properties" ] } }, "additionalProperties": false, "type": "object", "required": [ "name", "author" ] }, "network.Input": { "properties": { "data": { "oneOf": [ { "type": "string" }, { "type": "integer" } ], "title": "data to send as input", "description": "Data is the data to send as the input" }, "type": { "$ref": "#/$defs/network.NetworkInputTypeHolder", "title": "type is the type of input data", "description": "Type of input specified in data field" }, "read": { "type": "integer", "title": "bytes to read from socket", "description": "Number of bytes to read from socket" }, "name": { "type": "string", "title": "optional name for data read", "description": "Optional name of the data read to provide matching on" } }, "additionalProperties": false, "type": "object" }, "network.NetworkInputTypeHolder": { "type": "string", "enum": [ "hex", "text" ], "title": "type is the type of input data", "description": "description=Type of input specified in data field" }, "network.Request": { "properties": { "id": { "type": "string", "title": "id of the request", "description": "ID of the network request" }, "host": { "items": { "type": "string" }, "type": "array", "title": "host to send requests to", "description": "Host to send network requests to" }, "attack": { "$ref": "#/$defs/generators.AttackTypeHolder", "title": "attack is the payload combination", "description": "Attack is the type of payload combinations to perform" }, "payloads": { "$ref": "#/$defs/map[string]interface {}", "title": "payloads for the network request", "description": "Payloads contains any payloads for the current request" }, "threads": { "type": "integer", "title": "threads for sending requests", "description": "Threads specifies number of threads to use sending requests. This enables Connection Pooling" }, "inputs": { "items": { "$ref": "#/$defs/network.Input" }, "type": "array", "title": "inputs for the network request", "description": "Inputs contains any input/output for the current request" }, "port": { "oneOf": [ { "type": "string" }, { "type": "integer" } ], "title": "port to send requests to", "description": "Port to send network requests to" }, "exclude-ports": { "type": "string", "title": "exclude ports from being scanned", "description": "Exclude ports from being scanned" }, "read-size": { "type": "integer", "title": "size of network response to read", "description": "Size of response to read at the end. Default is 1024 bytes" }, "read-all": { "type": "boolean", "title": "read all response stream", "description": "Read all response stream till the server stops sending" }, "stop-at-first-match": { "type": "boolean", "title": "stop at first match", "description": "Stop the execution after a match is found" }, "matchers": { "items": { "$ref": "#/$defs/matchers.Matcher" }, "type": "array", "title": "matchers to run on response", "description": "Detection mechanism to identify whether the request was successful by doing pattern matching" }, "extractors": { "items": { "$ref": "#/$defs/extractors.Extractor" }, "type": "array", "title": "extractors to run on response", "description": "Extractors contains the extraction mechanism for the request to identify and extract parts of the response" }, "matchers-condition": { "type": "string", "enum": [ "and", "or" ], "title": "condition between the matchers", "description": "Conditions between the matchers" } }, "additionalProperties": false, "type": "object" }, "severity.Holder": { "type": "string", "enum": [ "info", "low", "medium", "high", "critical", "unknown" ], "title": "severity of the template", "description": "Seriousness of the implications of the template" }, "ssl.Request": { "properties": { "matchers": { "items": { "$ref": "#/$defs/matchers.Matcher" }, "type": "array", "title": "matchers to run on response", "description": "Detection mechanism to identify whether the request was successful by doing pattern matching" }, "extractors": { "items": { "$ref": "#/$defs/extractors.Extractor" }, "type": "array", "title": "extractors to run on response", "description": "Extractors contains the extraction mechanism for the request to identify and extract parts of the response" }, "matchers-condition": { "type": "string", "enum": [ "and", "or" ], "title": "condition between the matchers", "description": "Conditions between the matchers" }, "id": { "type": "string", "title": "id of the request", "description": "ID of the request" }, "address": { "type": "string", "title": "address for the ssl request", "description": "Address contains address for the request" }, "min_version": { "type": "string", "enum": [ "sslv3", "tls10", "tls11", "tls12", "tls13" ], "title": "Min. TLS version", "description": "Minimum tls version - automatic if not specified." }, "max_version": { "type": "string", "enum": [ "sslv3", "tls10", "tls11", "tls12", "tls13" ], "title": "Max. TLS version", "description": "Max tls version - automatic if not specified." }, "cipher_suites": { "items": { "type": "string" }, "type": "array" }, "scan_mode": { "type": "string", "enum": [ "ctls", "ztls", "auto" ], "title": "Scan Mode", "description": "Scan Mode - auto if not specified." }, "tls_version_enum": { "type": "boolean", "title": "Enumerate Versions", "description": "Enumerate Version - false if not specified" }, "tls_cipher_enum": { "type": "boolean", "title": "Enumerate Ciphers", "description": "Enumerate Ciphers - false if not specified" }, "tls_cipher_types": { "items": { "type": "string", "enum": [ "weak", "secure", "insecure", "all" ] }, "type": "array", "title": "TLS Cipher Types", "description": "TLS Cipher Types to enumerate" } }, "additionalProperties": false, "type": "object" }, "stringslice.StringOrSlice": { "oneOf": [ { "type": "string" }, { "type": "array" } ] }, "templates.Template": { "properties": { "id": { "type": "string", "pattern": "^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$", "title": "id of the template", "description": "The Unique ID for the template", "examples": [ "cve-2021-19520" ] }, "info": { "$ref": "#/$defs/model.Info", "type": "object", "title": "info for the template", "description": "Info contains metadata for the template" }, "flow": { "type": "string", "title": "template execution flow in js", "description": "Flow contains js code which defines how the template should be executed", "examples": [ "'flow: http(0) \u0026\u0026 http(1)'" ] }, "requests": { "items": { "$ref": "#/$defs/http.Request" }, "type": "array", "title": "http requests to make", "description": "HTTP requests to make for the template" }, "http": { "items": { "$ref": "#/$defs/http.Request" }, "type": "array", "title": "http requests to make", "description": "HTTP requests to make for the template" }, "dns": { "items": { "$ref": "#/$defs/dns.Request" }, "type": "array", "title": "dns requests to make", "description": "DNS requests to make for the template" }, "file": { "items": { "$ref": "#/$defs/file.Request" }, "type": "array", "title": "file requests to make", "description": "File requests to make for the template" }, "network": { "items": { "$ref": "#/$defs/network.Request" }, "type": "array", "title": "network requests to make", "description": "Network requests to make for the template" }, "tcp": { "items": { "$ref": "#/$defs/network.Request" }, "type": "array", "title": "network(tcp) requests to make", "description": "Network requests to make for the template" }, "headless": { "items": { "$ref": "#/$defs/headless.Request" }, "type": "array", "title": "headless requests to make", "description": "Headless requests to make for the template" }, "ssl": { "items": { "$ref": "#/$defs/ssl.Request" }, "type": "array", "title": "ssl requests to make", "description": "SSL requests to make for the template" }, "websocket": { "items": { "$ref": "#/$defs/websocket.Request" }, "type": "array", "title": "websocket requests to make", "description": "Websocket requests to make for the template" }, "whois": { "items": { "$ref": "#/$defs/whois.Request" }, "type": "array", "title": "whois requests to make", "description": "WHOIS requests to make for the template" }, "code": { "items": { "$ref": "#/$defs/code.Request" }, "type": "array", "title": "code snippets to make", "description": "Code snippets" }, "javascript": { "items": { "$ref": "#/$defs/javascript.Request" }, "type": "array", "title": "javascript requests to make", "description": "Javascript requests to make for the template" }, "workflows": { "items": { "$ref": "#/$defs/workflows.WorkflowTemplate" }, "type": "array", "title": "list of workflows to execute", "description": "List of workflows to execute for template" }, "self-contained": { "type": "boolean", "title": "mark requests as self-contained", "description": "Mark Requests for the template as self-contained" }, "stop-at-first-match": { "type": "boolean", "title": "stop at first match", "description": "Stop at first match for the template" }, "signature": { "$ref": "#/$defs/http.SignatureTypeHolder", "title": "signature is the http request signature method", "description": "Signature is the HTTP Request signature Method" }, "variables": { "$ref": "#/$defs/variables.Variable", "type": "object", "title": "variables for the http request", "description": "Variables contains any variables for the current request" }, "constants": { "$ref": "#/$defs/map[string]interface {}", "type": "object", "title": "constant for the template", "description": "constants contains any constant for the template" } }, "additionalProperties": false, "type": "object", "required": [ "id", "info" ] }, "userAgent.UserAgentHolder": { "type": "string", "enum": [ "off", "default", "custom" ], "title": "userAgent for the headless", "description": "userAgent for the headless http request" }, "variables.Variable": { "additionalProperties": true, "type": "object", "title": "variables for the request", "description": "Additional variables for the request" }, "websocket.Input": { "properties": { "data": { "type": "string", "title": "data to send as input", "description": "Data is the data to send as the input" }, "name": { "type": "string", "title": "optional name for data read", "description": "Optional name of the data read to provide matching on" } }, "additionalProperties": false, "type": "object" }, "websocket.Request": { "properties": { "matchers": { "items": { "$ref": "#/$defs/matchers.Matcher" }, "type": "array", "title": "matchers to run on response", "description": "Detection mechanism to identify whether the request was successful by doing pattern matching" }, "extractors": { "items": { "$ref": "#/$defs/extractors.Extractor" }, "type": "array", "title": "extractors to run on response", "description": "Extractors contains the extraction mechanism for the request to identify and extract parts of the response" }, "matchers-condition": { "type": "string", "enum": [ "and", "or" ], "title": "condition between the matchers", "description": "Conditions between the matchers" }, "id": { "type": "string", "title": "id of the request", "description": "ID of the network request" }, "address": { "type": "string", "title": "address for the websocket request", "description": "Address contains address for the request" }, "inputs": { "items": { "$ref": "#/$defs/websocket.Input" }, "type": "array", "title": "inputs for the websocket request", "description": "Inputs contains any input/output for the current request" }, "headers": { "$ref": "#/$defs/map[string]string", "title": "headers contains the request headers", "description": "Headers contains headers for the request" }, "attack": { "$ref": "#/$defs/generators.AttackTypeHolder", "title": "attack is the payload combination", "description": "Attack is the type of payload combinations to perform" }, "payloads": { "$ref": "#/$defs/map[string]interface {}", "title": "payloads for the websocket request", "description": "Payloads contains any payloads for the current request" } }, "additionalProperties": false, "type": "object" }, "whois.Request": { "properties": { "matchers": { "items": { "$ref": "#/$defs/matchers.Matcher" }, "type": "array", "title": "matchers to run on response", "description": "Detection mechanism to identify whether the request was successful by doing pattern matching" }, "extractors": { "items": { "$ref": "#/$defs/extractors.Extractor" }, "type": "array", "title": "extractors to run on response", "description": "Extractors contains the extraction mechanism for the request to identify and extract parts of the response" }, "matchers-condition": { "type": "string", "enum": [ "and", "or" ], "title": "condition between the matchers", "description": "Conditions between the matchers" }, "id": { "type": "string", "title": "id of the request", "description": "ID of the network request" }, "query": { "type": "string", "title": "query for the WHOIS request", "description": "Query contains query for the request" }, "server": { "type": "string", "title": "server url to execute the WHOIS request on", "description": "Server contains the server url to execute the WHOIS request on" } }, "additionalProperties": false, "type": "object" }, "workflows.Matcher": { "properties": { "name": { "$ref": "#/$defs/stringslice.StringOrSlice", "title": "name of items to match", "description": "Name of items to match" }, "condition": { "type": "string", "enum": [ "and", "or" ], "title": "condition between names", "description": "Condition between the names" }, "subtemplates": { "items": { "$ref": "#/$defs/workflows.WorkflowTemplate" }, "type": "array", "title": "templates to run after match", "description": "Templates to run after match" } }, "additionalProperties": false, "type": "object" }, "workflows.WorkflowTemplate": { "properties": { "template": { "type": "string", "title": "template/directory to execute", "description": "Template or directory to execute as part of workflow" }, "tags": { "$ref": "#/$defs/stringslice.StringOrSlice", "title": "tags to execute", "description": "Tags to run template based on" }, "matchers": { "items": { "$ref": "#/$defs/workflows.Matcher" }, "type": "array", "title": "name based template result matchers", "description": "Matchers perform name based matching to run subtemplates for a workflow" }, "subtemplates": { "items": { "$ref": "#/$defs/workflows.WorkflowTemplate" }, "type": "array", "title": "subtemplate based result matchers", "description": "Subtemplates are ran if the template field Template matches" } }, "additionalProperties": false, "type": "object" } } } ================================================ FILE: pkg/authprovider/authx/basic_auth.go ================================================ package authx import ( "net/http" "github.com/projectdiscovery/retryablehttp-go" ) var ( _ AuthStrategy = &BasicAuthStrategy{} ) // BasicAuthStrategy is a strategy for basic auth type BasicAuthStrategy struct { Data *Secret } // NewBasicAuthStrategy creates a new basic auth strategy func NewBasicAuthStrategy(data *Secret) *BasicAuthStrategy { return &BasicAuthStrategy{Data: data} } // Apply applies the basic auth strategy to the request func (s *BasicAuthStrategy) Apply(req *http.Request) { req.SetBasicAuth(s.Data.Username, s.Data.Password) } // ApplyOnRR applies the basic auth strategy to the retryable request func (s *BasicAuthStrategy) ApplyOnRR(req *retryablehttp.Request) { req.SetBasicAuth(s.Data.Username, s.Data.Password) } ================================================ FILE: pkg/authprovider/authx/bearer_auth.go ================================================ package authx import ( "net/http" "github.com/projectdiscovery/retryablehttp-go" ) var ( _ AuthStrategy = &BearerTokenAuthStrategy{} ) // BearerTokenAuthStrategy is a strategy for bearer token auth type BearerTokenAuthStrategy struct { Data *Secret } // NewBearerTokenAuthStrategy creates a new bearer token auth strategy func NewBearerTokenAuthStrategy(data *Secret) *BearerTokenAuthStrategy { return &BearerTokenAuthStrategy{Data: data} } // Apply applies the bearer token auth strategy to the request func (s *BearerTokenAuthStrategy) Apply(req *http.Request) { req.Header.Set("Authorization", "Bearer "+s.Data.Token) } // ApplyOnRR applies the bearer token auth strategy to the retryable request func (s *BearerTokenAuthStrategy) ApplyOnRR(req *retryablehttp.Request) { req.Header.Set("Authorization", "Bearer "+s.Data.Token) } ================================================ FILE: pkg/authprovider/authx/cookies_auth.go ================================================ package authx import ( "net/http" "slices" "github.com/projectdiscovery/retryablehttp-go" ) var ( _ AuthStrategy = &CookiesAuthStrategy{} ) // CookiesAuthStrategy is a strategy for cookies auth type CookiesAuthStrategy struct { Data *Secret } // NewCookiesAuthStrategy creates a new cookies auth strategy func NewCookiesAuthStrategy(data *Secret) *CookiesAuthStrategy { return &CookiesAuthStrategy{Data: data} } // Apply applies the cookies auth strategy to the request func (s *CookiesAuthStrategy) Apply(req *http.Request) { for _, cookie := range s.Data.Cookies { c := &http.Cookie{ Name: cookie.Key, Value: cookie.Value, } req.AddCookie(c) } } // ApplyOnRR applies the cookies auth strategy to the retryable request func (s *CookiesAuthStrategy) ApplyOnRR(req *retryablehttp.Request) { existingCookies := req.Cookies() for _, newCookie := range s.Data.Cookies { for i, existing := range existingCookies { if existing.Name == newCookie.Key { existingCookies = slices.Delete(existingCookies, i, i+1) break } } } // Clear and reset remaining cookies req.Header.Del("Cookie") for _, cookie := range existingCookies { req.AddCookie(cookie) } // Add new cookies for _, cookie := range s.Data.Cookies { req.AddCookie(&http.Cookie{ Name: cookie.Key, Value: cookie.Value, }) } } ================================================ FILE: pkg/authprovider/authx/dynamic.go ================================================ package authx import ( "fmt" "strings" "sync" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/replacer" "github.com/projectdiscovery/nuclei/v3/pkg/utils/json" "github.com/projectdiscovery/utils/errkit" sliceutil "github.com/projectdiscovery/utils/slice" ) type LazyFetchSecret func(d *Dynamic) error // fetchState holds the sync.Once and error for thread-safe fetching. // This is stored as a pointer in Dynamic so that value copies share the same state. type fetchState struct { once sync.Once err error } var ( _ json.Unmarshaler = &Dynamic{} ) // Dynamic is a struct for dynamic secret or credential // these are high level secrets that take action to generate the actual secret // ex: username and password are dynamic secrets, the actual secret is the token obtained // after authenticating with the username and password type Dynamic struct { *Secret `yaml:",inline"` // this is a static secret that will be generated after the dynamic secret is resolved Secrets []*Secret `yaml:"secrets"` TemplatePath string `json:"template" yaml:"template"` Variables []KV `json:"variables" yaml:"variables"` Input string `json:"input" yaml:"input"` // (optional) target for the dynamic secret Extracted map[string]interface{} `json:"-" yaml:"-"` // extracted values from the dynamic secret fetchCallback LazyFetchSecret `json:"-" yaml:"-"` // fetchState is shared across value-copies of Dynamic (e.g., inside DynamicAuthStrategy). // It must be initialized via Validate() before calling Fetch(). fetchState *fetchState `json:"-" yaml:"-"` } func (d *Dynamic) GetDomainAndDomainRegex() ([]string, []string) { var domains []string var domainRegex []string for _, secret := range d.Secrets { domains = append(domains, secret.Domains...) domainRegex = append(domainRegex, secret.DomainsRegex...) } if d.Secret != nil { domains = append(domains, d.Domains...) domainRegex = append(domainRegex, d.DomainsRegex...) } uniqueDomains := sliceutil.Dedupe(domains) uniqueDomainRegex := sliceutil.Dedupe(domainRegex) return uniqueDomains, uniqueDomainRegex } func (d *Dynamic) UnmarshalJSON(data []byte) error { if d == nil { return errkit.New("cannot unmarshal into nil Dynamic struct") } // Use an alias type (auxiliary) to avoid a recursive call in this method. type Alias Dynamic // If d.Secret was nil, json.Unmarshal will allocate a new Secret object // and populate it from the top level JSON fields. if err := json.Unmarshal(data, (*Alias)(d)); err != nil { return err } return nil } // Validate validates the dynamic secret func (d *Dynamic) Validate() error { // NOTE: Validate() must not be called concurrently with Fetch()/GetStrategies(). // Re-validating resets fetch state and allows re-fetching. d.fetchState = &fetchState{} if d.TemplatePath == "" { return errkit.New(" template-path is required for dynamic secret") } if len(d.Variables) == 0 { return errkit.New("variables are required for dynamic secret") } if d.Secret != nil { d.skipCookieParse = true // skip cookie parsing in dynamic secrets during validation if err := d.Secret.Validate(); err != nil { return err } } for _, secret := range d.Secrets { secret.skipCookieParse = true if err := secret.Validate(); err != nil { return err } } return nil } // SetLazyFetchCallback sets the lazy fetch callback for the dynamic secret func (d *Dynamic) SetLazyFetchCallback(callback LazyFetchSecret) { d.fetchCallback = func(d *Dynamic) error { err := callback(d) if err != nil { return err } if len(d.Extracted) == 0 { return fmt.Errorf("no extracted values found for dynamic secret") } if d.Secret != nil { if err := d.applyValuesToSecret(d.Secret); err != nil { return err } } for _, secret := range d.Secrets { if err := d.applyValuesToSecret(secret); err != nil { return err } } return nil } } func (d *Dynamic) applyValuesToSecret(secret *Secret) error { // evaluate headers for i, header := range secret.Headers { if strings.Contains(header.Value, "{{") { header.Value = replacer.Replace(header.Value, d.Extracted) } if strings.Contains(header.Key, "{{") { header.Key = replacer.Replace(header.Key, d.Extracted) } secret.Headers[i] = header } // evaluate cookies for i, cookie := range secret.Cookies { if strings.Contains(cookie.Value, "{{") { cookie.Value = replacer.Replace(cookie.Value, d.Extracted) } if strings.Contains(cookie.Key, "{{") { cookie.Key = replacer.Replace(cookie.Key, d.Extracted) } if strings.Contains(cookie.Raw, "{{") { cookie.Raw = replacer.Replace(cookie.Raw, d.Extracted) } secret.Cookies[i] = cookie } // evaluate query params for i, query := range secret.Params { if strings.Contains(query.Value, "{{") { query.Value = replacer.Replace(query.Value, d.Extracted) } if strings.Contains(query.Key, "{{") { query.Key = replacer.Replace(query.Key, d.Extracted) } secret.Params[i] = query } // check username, password and token if strings.Contains(secret.Username, "{{") { secret.Username = replacer.Replace(secret.Username, d.Extracted) } if strings.Contains(secret.Password, "{{") { secret.Password = replacer.Replace(secret.Password, d.Extracted) } if strings.Contains(secret.Token, "{{") { secret.Token = replacer.Replace(secret.Token, d.Extracted) } // now attempt to parse the cookies secret.skipCookieParse = false for i, cookie := range secret.Cookies { if cookie.Raw != "" { if err := cookie.Parse(); err != nil { return fmt.Errorf("[%s] invalid raw cookie in cookiesAuth: %s", d.TemplatePath, err) } secret.Cookies[i] = cookie } } return nil } // GetStrategies returns the auth strategies for the dynamic secret func (d *Dynamic) GetStrategies() []AuthStrategy { // Ensure fetch has completed before returning strategies. // Fetch errors are treated as non-fatal here so a failed dynamic auth fetch // does not terminate the entire scan process. _ = d.Fetch(false) if d.fetchState != nil && d.fetchState.err != nil { return nil } var strategies []AuthStrategy if d.Secret != nil { strategies = append(strategies, d.GetStrategy()) } for _, secret := range d.Secrets { strategies = append(strategies, secret.GetStrategy()) } return strategies } // Fetch fetches the dynamic secret // if isFatal is true, it will stop the execution if the secret could not be fetched func (d *Dynamic) Fetch(isFatal bool) error { if d.fetchState == nil { if isFatal { gologger.Fatal().Msgf("Could not fetch dynamic secret: Validate() must be called before Fetch()") } return errkit.New("dynamic secret not validated: call Validate() before Fetch()") } d.fetchState.once.Do(func() { if d.fetchCallback == nil { d.fetchState.err = errkit.New("dynamic secret fetch callback not set: call SetLazyFetchCallback() before Fetch()") return } d.fetchState.err = d.fetchCallback(d) }) if d.fetchState.err != nil && isFatal { gologger.Fatal().Msgf("Could not fetch dynamic secret: %s\n", d.fetchState.err) } return d.fetchState.err } // Error returns the error if any func (d *Dynamic) Error() error { if d.fetchState == nil { return nil } return d.fetchState.err } ================================================ FILE: pkg/authprovider/authx/dynamic_test.go ================================================ package authx import ( "errors" "sync" "sync/atomic" "testing" "time" "github.com/stretchr/testify/require" ) func TestDynamicUnmarshalJSON(t *testing.T) { t.Run("basic-unmarshal", func(t *testing.T) { data := []byte(`{ "template": "test-template.yaml", "variables": [ { "key": "username", "value": "testuser" } ], "secrets": [ { "type": "BasicAuth", "domains": ["example.com"], "username": "user1", "password": "pass1" } ], "type": "BasicAuth", "domains": ["test.com"], "username": "testuser", "password": "testpass" }`) var d Dynamic err := d.UnmarshalJSON(data) require.NoError(t, err) // Secret require.NotNil(t, d.Secret) require.Equal(t, "BasicAuth", d.Type) require.Equal(t, []string{"test.com"}, d.Domains) require.Equal(t, "testuser", d.Username) require.Equal(t, "testpass", d.Password) // Dynamic fields require.Equal(t, "test-template.yaml", d.TemplatePath) require.Len(t, d.Variables, 1) require.Equal(t, "username", d.Variables[0].Key) require.Equal(t, "testuser", d.Variables[0].Value) require.Len(t, d.Secrets, 1) require.Equal(t, "BasicAuth", d.Secrets[0].Type) require.Equal(t, []string{"example.com"}, d.Secrets[0].Domains) require.Equal(t, "user1", d.Secrets[0].Username) require.Equal(t, "pass1", d.Secrets[0].Password) }) t.Run("complex-unmarshal", func(t *testing.T) { data := []byte(`{ "template": "test-template.yaml", "variables": [ { "key": "token", "value": "Bearer xyz" } ], "secrets": [ { "type": "CookiesAuth", "domains": ["example.com"], "cookies": [ { "key": "session", "value": "abc123" } ] } ], "type": "HeadersAuth", "domains": ["api.test.com"], "headers": [ { "key": "X-API-Key", "value": "secret-key" } ] }`) var d Dynamic err := d.UnmarshalJSON(data) require.NoError(t, err) // Secret require.NotNil(t, d.Secret) require.Equal(t, "HeadersAuth", d.Type) require.Equal(t, []string{"api.test.com"}, d.Domains) require.Len(t, d.Headers, 1) require.Equal(t, "X-API-Key", d.Secret.Headers[0].Key) require.Equal(t, "secret-key", d.Secret.Headers[0].Value) // Dynamic fields require.Equal(t, "test-template.yaml", d.TemplatePath) require.Len(t, d.Variables, 1) require.Equal(t, "token", d.Variables[0].Key) require.Equal(t, "Bearer xyz", d.Variables[0].Value) require.Len(t, d.Secrets, 1) require.Equal(t, "CookiesAuth", d.Secrets[0].Type) require.Equal(t, []string{"example.com"}, d.Secrets[0].Domains) require.Len(t, d.Secrets[0].Cookies, 1) require.Equal(t, "session", d.Secrets[0].Cookies[0].Key) require.Equal(t, "abc123", d.Secrets[0].Cookies[0].Value) }) t.Run("invalid-json", func(t *testing.T) { data := []byte(`{invalid json}`) var d Dynamic err := d.UnmarshalJSON(data) require.Error(t, err) }) t.Run("empty-json", func(t *testing.T) { data := []byte(`{}`) var d Dynamic err := d.UnmarshalJSON(data) require.NoError(t, err) }) } func TestDynamicFetchConcurrent(t *testing.T) { t.Run("all-waiters-block-until-done", func(t *testing.T) { const numGoroutines = 10 wantErr := errors.New("auth fetch failed") fetchStarted := make(chan struct{}) fetchUnblock := make(chan struct{}) d := &Dynamic{ TemplatePath: "test-template.yaml", Variables: []KV{{Key: "username", Value: "test"}}, } require.NoError(t, d.Validate()) d.SetLazyFetchCallback(func(_ *Dynamic) error { close(fetchStarted) <-fetchUnblock return wantErr }) results := make([]error, numGoroutines) var wg sync.WaitGroup wg.Add(numGoroutines) for i := 0; i < numGoroutines; i++ { go func(idx int) { defer wg.Done() results[idx] = d.Fetch(false) }(i) } select { case <-fetchStarted: case <-time.After(5 * time.Second): t.Fatal("fetch callback never started") } done := make(chan struct{}) go func() { wg.Wait() close(done) }() select { case <-done: t.Fatal("fetch callers returned before fetch completed") case <-time.After(25 * time.Millisecond): } close(fetchUnblock) select { case <-done: case <-time.After(5 * time.Second): t.Fatal("fetch callers did not complete in time") } for _, err := range results { require.ErrorIs(t, err, wantErr) } }) t.Run("fetch-callback-runs-once", func(t *testing.T) { const numGoroutines = 20 var callCount atomic.Int32 errs := make(chan error, numGoroutines) barrier := make(chan struct{}) d := &Dynamic{ TemplatePath: "test-template.yaml", Variables: []KV{{Key: "username", Value: "test"}}, } require.NoError(t, d.Validate()) d.SetLazyFetchCallback(func(dynamic *Dynamic) error { callCount.Add(1) time.Sleep(20 * time.Millisecond) dynamic.Extracted = map[string]interface{}{"token": "secret-token"} return nil }) var wg sync.WaitGroup wg.Add(numGoroutines) for i := 0; i < numGoroutines; i++ { go func() { defer wg.Done() <-barrier errs <- d.Fetch(false) }() } close(barrier) wg.Wait() close(errs) for err := range errs { require.NoError(t, err) } require.Equal(t, int32(1), callCount.Load(), "fetch callback must be called exactly once") }) } ================================================ FILE: pkg/authprovider/authx/file.go ================================================ package authx import ( "fmt" "os" "path/filepath" "regexp" "strings" "github.com/projectdiscovery/nuclei/v3/pkg/utils/json" "github.com/projectdiscovery/utils/errkit" "github.com/projectdiscovery/utils/generic" stringsutil "github.com/projectdiscovery/utils/strings" "gopkg.in/yaml.v3" ) type AuthType string const ( BasicAuth AuthType = "BasicAuth" BearerTokenAuth AuthType = "BearerToken" HeadersAuth AuthType = "Header" CookiesAuth AuthType = "Cookie" QueryAuth AuthType = "Query" ) // SupportedAuthTypes returns the supported auth types func SupportedAuthTypes() []string { return []string{ string(BasicAuth), string(BearerTokenAuth), string(HeadersAuth), string(CookiesAuth), string(QueryAuth), } } // Authx is a struct for secrets or credentials file type Authx struct { ID string `json:"id" yaml:"id"` Info AuthFileInfo `json:"info" yaml:"info"` Secrets []Secret `json:"static" yaml:"static"` Dynamic []Dynamic `json:"dynamic" yaml:"dynamic"` } type AuthFileInfo struct { Name string `json:"name" yaml:"name"` Author string `json:"author" yaml:"author"` Severity string `json:"severity" yaml:"severity"` Description string `json:"description" yaml:"description"` } // Secret is a struct for secret or credential type Secret struct { Type string `json:"type" yaml:"type"` Domains []string `json:"domains" yaml:"domains"` DomainsRegex []string `json:"domains-regex" yaml:"domains-regex"` Headers []KV `json:"headers" yaml:"headers"` // Headers preserve exact casing (useful for case-sensitive APIs) Cookies []Cookie `json:"cookies" yaml:"cookies"` Params []KV `json:"params" yaml:"params"` Username string `json:"username" yaml:"username"` // can be either email or username Password string `json:"password" yaml:"password"` Token string `json:"token" yaml:"token"` // Bearer Auth token skipCookieParse bool `json:"-" yaml:"-"` // temporary flag to skip cookie parsing (used in dynamic secrets) } // GetStrategy returns the auth strategy for the secret func (s *Secret) GetStrategy() AuthStrategy { switch { case strings.EqualFold(s.Type, string(BasicAuth)): return NewBasicAuthStrategy(s) case strings.EqualFold(s.Type, string(BearerTokenAuth)): return NewBearerTokenAuthStrategy(s) case strings.EqualFold(s.Type, string(HeadersAuth)): return NewHeadersAuthStrategy(s) case strings.EqualFold(s.Type, string(CookiesAuth)): return NewCookiesAuthStrategy(s) case strings.EqualFold(s.Type, string(QueryAuth)): return NewQueryAuthStrategy(s) } return nil } func (s *Secret) Validate() error { if !stringsutil.EqualFoldAny(s.Type, SupportedAuthTypes()...) { return fmt.Errorf("invalid type: %s", s.Type) } if len(s.Domains) == 0 && len(s.DomainsRegex) == 0 { return fmt.Errorf("domains or domains-regex cannot be empty") } if len(s.DomainsRegex) > 0 { for _, domain := range s.DomainsRegex { _, err := regexp.Compile(domain) if err != nil { return fmt.Errorf("invalid domain regex: %s", domain) } } } switch { case strings.EqualFold(s.Type, string(BasicAuth)): if s.Username == "" { return fmt.Errorf("username cannot be empty in basic auth") } if s.Password == "" { return fmt.Errorf("password cannot be empty in basic auth") } case strings.EqualFold(s.Type, string(BearerTokenAuth)): if s.Token == "" { return fmt.Errorf("token cannot be empty in bearer token auth") } case strings.EqualFold(s.Type, string(HeadersAuth)): if len(s.Headers) == 0 { return fmt.Errorf("headers cannot be empty in headers auth") } for _, header := range s.Headers { if err := header.Validate(); err != nil { return fmt.Errorf("invalid header in headersAuth: %s", err) } } case strings.EqualFold(s.Type, string(CookiesAuth)): if len(s.Cookies) == 0 { return fmt.Errorf("cookies cannot be empty in cookies auth") } for _, cookie := range s.Cookies { if cookie.Raw != "" && !s.skipCookieParse { if err := cookie.Parse(); err != nil { return fmt.Errorf("invalid raw cookie in cookiesAuth: %s", err) } } if err := cookie.Validate(); err != nil { return fmt.Errorf("invalid cookie in cookiesAuth: %s", err) } } case strings.EqualFold(s.Type, string(QueryAuth)): if len(s.Params) == 0 { return fmt.Errorf("query cannot be empty in query auth") } for _, query := range s.Params { if err := query.Validate(); err != nil { return fmt.Errorf("invalid query in queryAuth: %s", err) } } default: return fmt.Errorf("invalid type: %s", s.Type) } return nil } type KV struct { Key string `json:"key" yaml:"key"` // Header key (preserves exact casing) Value string `json:"value" yaml:"value"` } func (k *KV) Validate() error { if k.Key == "" { return fmt.Errorf("key cannot be empty") } if k.Value == "" { return fmt.Errorf("value cannot be empty") } return nil } type Cookie struct { Key string `json:"key" yaml:"key"` Value string `json:"value" yaml:"value"` Raw string `json:"raw" yaml:"raw"` } func (c *Cookie) Validate() error { if c.Raw != "" { return nil } if c.Key == "" { return fmt.Errorf("key cannot be empty") } if c.Value == "" { return fmt.Errorf("value cannot be empty") } return nil } // Parse parses the cookie // in raw the cookie is in format of // Set-Cookie: =; Expires=; Path=; Domain=; Secure; HttpOnly func (c *Cookie) Parse() error { if c.Raw == "" { return fmt.Errorf("raw cookie cannot be empty") } tmp := strings.TrimPrefix(c.Raw, "Set-Cookie: ") slice := strings.Split(tmp, ";") if len(slice) == 0 { return fmt.Errorf("invalid raw cookie no ; found") } // first element is the cookie name and value cookie := strings.Split(slice[0], "=") if len(cookie) == 2 { c.Key = cookie[0] c.Value = cookie[1] return nil } return fmt.Errorf("invalid raw cookie: %s", c.Raw) } // GetAuthDataFromFile reads the auth data from file func GetAuthDataFromFile(file string) (*Authx, error) { ext := filepath.Ext(file) if !generic.EqualsAny(ext, ".yml", ".yaml", ".json") { return nil, fmt.Errorf("invalid file extension: supported extensions are .yml,.yaml and .json got %s", ext) } bin, err := os.ReadFile(file) if err != nil { return nil, err } if ext == ".yml" || ext == ".yaml" { return GetAuthDataFromYAML(bin) } return GetAuthDataFromJSON(bin) } // GetTemplatePathsFromSecretFile reads the template IDs from the secret file func GetTemplatePathsFromSecretFile(file string) ([]string, error) { auth, err := GetAuthDataFromFile(file) if err != nil { return nil, err } var paths []string for _, dynamic := range auth.Dynamic { paths = append(paths, dynamic.TemplatePath) } return paths, nil } // GetAuthDataFromYAML reads the auth data from yaml func GetAuthDataFromYAML(data []byte) (*Authx, error) { var auth Authx err := yaml.Unmarshal(data, &auth) if err != nil { errorErr := errkit.FromError(err) errorErr.Msgf("could not unmarshal yaml") return nil, errorErr } return &auth, nil } // GetAuthDataFromJSON reads the auth data from json func GetAuthDataFromJSON(data []byte) (*Authx, error) { var auth Authx err := json.Unmarshal(data, &auth) if err != nil { errorErr := errkit.FromError(err) errorErr.Msgf("could not unmarshal json") return nil, errorErr } return &auth, nil } ================================================ FILE: pkg/authprovider/authx/file_test.go ================================================ package authx import ( "testing" "github.com/stretchr/testify/require" ) func TestSecretsUnmarshal(t *testing.T) { loc := "testData/example-auth.yaml" data, err := GetAuthDataFromFile(loc) require.Nil(t, err, "could not read secrets file") require.NotNil(t, data, "could not read secrets file") for _, s := range data.Secrets { require.Nil(t, s.Validate(), "could not validate secret") } for _, d := range data.Dynamic { require.Nil(t, d.Validate(), "could not validate dynamic") } } ================================================ FILE: pkg/authprovider/authx/headers_auth.go ================================================ package authx import ( "net/http" "github.com/projectdiscovery/retryablehttp-go" ) var ( _ AuthStrategy = &HeadersAuthStrategy{} ) // HeadersAuthStrategy is a strategy for headers auth type HeadersAuthStrategy struct { Data *Secret } // NewHeadersAuthStrategy creates a new headers auth strategy func NewHeadersAuthStrategy(data *Secret) *HeadersAuthStrategy { return &HeadersAuthStrategy{Data: data} } // Apply applies the headers auth strategy to the request // NOTE: This preserves exact header casing (e.g., barAuthToken stays as barAuthToken) // This is useful for APIs that require case-sensitive header names func (s *HeadersAuthStrategy) Apply(req *http.Request) { for _, header := range s.Data.Headers { req.Header[header.Key] = []string{header.Value} } } // ApplyOnRR applies the headers auth strategy to the retryable request // NOTE: This preserves exact header casing (e.g., barAuthToken stays as barAuthToken) // This is useful for APIs that require case-sensitive header names func (s *HeadersAuthStrategy) ApplyOnRR(req *retryablehttp.Request) { for _, header := range s.Data.Headers { req.Header[header.Key] = []string{header.Value} } } ================================================ FILE: pkg/authprovider/authx/query_auth.go ================================================ package authx import ( "net/http" "github.com/projectdiscovery/retryablehttp-go" urlutil "github.com/projectdiscovery/utils/url" ) var ( _ AuthStrategy = &QueryAuthStrategy{} ) // QueryAuthStrategy is a strategy for query auth type QueryAuthStrategy struct { Data *Secret } // NewQueryAuthStrategy creates a new query auth strategy func NewQueryAuthStrategy(data *Secret) *QueryAuthStrategy { return &QueryAuthStrategy{Data: data} } // Apply applies the query auth strategy to the request func (s *QueryAuthStrategy) Apply(req *http.Request) { q := urlutil.NewOrderedParams() q.Decode(req.URL.RawQuery) for _, p := range s.Data.Params { q.Add(p.Key, p.Value) } req.URL.RawQuery = q.Encode() } // ApplyOnRR applies the query auth strategy to the retryable request func (s *QueryAuthStrategy) ApplyOnRR(req *retryablehttp.Request) { q := urlutil.NewOrderedParams() q.Decode(req.Request.URL.RawQuery) for _, p := range s.Data.Params { q.Add(p.Key, p.Value) } req.Request.URL.RawQuery = q.Encode() } ================================================ FILE: pkg/authprovider/authx/strategy.go ================================================ package authx import ( "net/http" "github.com/projectdiscovery/retryablehttp-go" ) // AuthStrategy is an interface for auth strategies // basic auth , bearer token, headers, cookies, query type AuthStrategy interface { // Apply applies the strategy to the request Apply(*http.Request) // ApplyOnRR applies the strategy to the retryable request ApplyOnRR(*retryablehttp.Request) } // DynamicAuthStrategy is an auth strategy for dynamic secrets // it implements the AuthStrategy interface type DynamicAuthStrategy struct { // Dynamic is the dynamic secret to use Dynamic Dynamic } // Apply applies the strategy to the request func (d *DynamicAuthStrategy) Apply(req *http.Request) { strategies := d.Dynamic.GetStrategies() if strategies == nil { return } for _, s := range strategies { if s == nil { continue } s.Apply(req) } } // ApplyOnRR applies the strategy to the retryable request func (d *DynamicAuthStrategy) ApplyOnRR(req *retryablehttp.Request) { strategy := d.Dynamic.GetStrategies() for _, s := range strategy { s.ApplyOnRR(req) } } ================================================ FILE: pkg/authprovider/authx/testData/example-auth.yaml ================================================ id: pd-nuclei-auth-test info: name: ProjectDiscovery Test Dev Servers author: pdteam description: | This is a auth file for ProjectDiscovery dev servers. It contains auth data of all projectdiscovery dev servers. # Note: this is a dummy example file. none of the secrets here are real. # static secrets static: # for header based auth session # NOTE: Headers preserve exact casing (e.g., x-pdcp-key stays as x-pdcp-key) # This is useful for APIs that require case-sensitive header names - type: header domains: - api.projectdiscovery.io - cve.projectdiscovery.io - chaos.projectdiscovery.io headers: - key: x-pdcp-key value: - key: barAuthToken value: # for query based auth session - type: Query domains: - scanme.sh params: - key: token value: 1a2b3c4d5e6f7g8h9i0j # for cookie based auth session - type: Cookie domains: - scanme.sh cookies: - key: PHPSESSID value: 1a2b3c4d5e6f7g8h9i0j # for basic auth session - type: BasicAuth domains: - scanme.sh username: test password: test # for authorization bearer token - type: BearerToken domains-regex: - .*scanme.sh - .*pdtm.sh token: test # dynamic secrets (powered by nuclei-templates) dynamic: - template: /path/to/wordpress-login.yaml variables: - name: username value: pdteam - name: password value: nuclei-v3.2.0 type: Cookie domains: - localhost:8080 cookies: - raw: "{{wp-global-cookie}}" - raw: "{{wp-admin-cookie}}" - raw: "{{wp-plugin-cookie}}" ================================================ FILE: pkg/authprovider/file.go ================================================ package authprovider import ( "net" "net/url" "regexp" "strings" "github.com/projectdiscovery/nuclei/v3/pkg/authprovider/authx" "github.com/projectdiscovery/utils/errkit" urlutil "github.com/projectdiscovery/utils/url" ) // FileAuthProvider is an auth provider for file based auth // it accepts a secrets file and returns its provider type FileAuthProvider struct { Path string store *authx.Authx compiled map[*regexp.Regexp][]authx.AuthStrategy domains map[string][]authx.AuthStrategy } // NewFileAuthProvider creates a new file based auth provider func NewFileAuthProvider(path string, callback authx.LazyFetchSecret) (AuthProvider, error) { store, err := authx.GetAuthDataFromFile(path) if err != nil { return nil, err } if len(store.Secrets) == 0 && len(store.Dynamic) == 0 { return nil, ErrNoSecrets } if len(store.Dynamic) > 0 && callback == nil { return nil, errkit.New("lazy fetch callback is required for dynamic secrets") } for _, secret := range store.Secrets { if err := secret.Validate(); err != nil { errorErr := errkit.FromError(err) errorErr.Msgf("invalid secret in file: %s", path) return nil, errorErr } } for i, dynamic := range store.Dynamic { if err := dynamic.Validate(); err != nil { errorErr := errkit.FromError(err) errorErr.Msgf("invalid dynamic in file: %s", path) return nil, errorErr } dynamic.SetLazyFetchCallback(callback) store.Dynamic[i] = dynamic } f := &FileAuthProvider{Path: path, store: store} f.init() return f, nil } // init initializes the file auth provider func (f *FileAuthProvider) init() { for _, _secret := range f.store.Secrets { secret := _secret // allocate copy of pointer if len(secret.DomainsRegex) > 0 { for _, domain := range secret.DomainsRegex { if f.compiled == nil { f.compiled = make(map[*regexp.Regexp][]authx.AuthStrategy) } compiled, err := regexp.Compile(domain) if err != nil { continue } if ss, ok := f.compiled[compiled]; ok { f.compiled[compiled] = append(ss, secret.GetStrategy()) } else { f.compiled[compiled] = []authx.AuthStrategy{secret.GetStrategy()} } } } for _, domain := range secret.Domains { if f.domains == nil { f.domains = make(map[string][]authx.AuthStrategy) } domain = strings.TrimSpace(domain) domain = strings.TrimSuffix(domain, ":80") domain = strings.TrimSuffix(domain, ":443") if ss, ok := f.domains[domain]; ok { f.domains[domain] = append(ss, secret.GetStrategy()) } else { f.domains[domain] = []authx.AuthStrategy{secret.GetStrategy()} } } } for _, dynamic := range f.store.Dynamic { domain, domainsRegex := dynamic.GetDomainAndDomainRegex() if len(domainsRegex) > 0 { for _, domain := range domainsRegex { if f.compiled == nil { f.compiled = make(map[*regexp.Regexp][]authx.AuthStrategy) } compiled, err := regexp.Compile(domain) if err != nil { continue } if ss, ok := f.compiled[compiled]; !ok { f.compiled[compiled] = []authx.AuthStrategy{&authx.DynamicAuthStrategy{Dynamic: dynamic}} } else { f.compiled[compiled] = append(ss, &authx.DynamicAuthStrategy{Dynamic: dynamic}) } } } for _, domain := range domain { if f.domains == nil { f.domains = make(map[string][]authx.AuthStrategy) } domain = strings.TrimSpace(domain) domain = strings.TrimSuffix(domain, ":80") domain = strings.TrimSuffix(domain, ":443") if ss, ok := f.domains[domain]; !ok { f.domains[domain] = []authx.AuthStrategy{&authx.DynamicAuthStrategy{Dynamic: dynamic}} } else { f.domains[domain] = append(ss, &authx.DynamicAuthStrategy{Dynamic: dynamic}) } } } } // LookupAddr looks up a given domain/address and returns appropriate auth strategy func (f *FileAuthProvider) LookupAddr(addr string) []authx.AuthStrategy { var strategies []authx.AuthStrategy if strings.Contains(addr, ":") { // default normalization for host:port host, port, err := net.SplitHostPort(addr) if err == nil && (port == "80" || port == "443") { addr = host } } for domain, strategy := range f.domains { if strings.EqualFold(domain, addr) { strategies = append(strategies, strategy...) } } for compiled, strategy := range f.compiled { if compiled.MatchString(addr) { strategies = append(strategies, strategy...) } } return strategies } // LookupURL looks up a given URL and returns appropriate auth strategy func (f *FileAuthProvider) LookupURL(u *url.URL) []authx.AuthStrategy { return f.LookupAddr(u.Host) } // LookupURLX looks up a given URL and returns appropriate auth strategy func (f *FileAuthProvider) LookupURLX(u *urlutil.URL) []authx.AuthStrategy { return f.LookupAddr(u.Host) } // GetTemplatePaths returns the template path for the auth provider func (f *FileAuthProvider) GetTemplatePaths() []string { res := []string{} for _, dynamic := range f.store.Dynamic { if dynamic.TemplatePath != "" { res = append(res, dynamic.TemplatePath) } } return res } // PreFetchSecrets pre-fetches the secrets from the auth provider func (f *FileAuthProvider) PreFetchSecrets() error { for _, ss := range f.domains { for _, s := range ss { if val, ok := s.(*authx.DynamicAuthStrategy); ok { if err := val.Dynamic.Fetch(false); err != nil { return err } } } } for _, ss := range f.compiled { for _, s := range ss { if val, ok := s.(*authx.DynamicAuthStrategy); ok { if err := val.Dynamic.Fetch(false); err != nil { return err } } } } return nil } ================================================ FILE: pkg/authprovider/file_test.go ================================================ package authprovider import ( "fmt" "net/http" "os" "path/filepath" "sync" "sync/atomic" "testing" "time" "github.com/projectdiscovery/nuclei/v3/pkg/authprovider/authx" "github.com/stretchr/testify/require" ) func TestFileAuthProviderDynamicSecretConcurrentAccess(t *testing.T) { secretFile := filepath.Join(t.TempDir(), "secret.yaml") secretData := []byte(`id: test-auth info: name: test author: test severity: info dynamic: - template: auth-template.yaml variables: - key: username value: test type: Header domains: - example.com headers: - key: Authorization value: "Bearer {{token}}" `) require.NoError(t, os.WriteFile(secretFile, secretData, 0o600)) var fetchCalls atomic.Int32 provider, err := NewFileAuthProvider(secretFile, func(dynamic *authx.Dynamic) error { fetchCalls.Add(1) time.Sleep(75 * time.Millisecond) dynamic.Extracted = map[string]interface{}{"token": "session-token"} return nil }) require.NoError(t, err) const workers = 20 barrier := make(chan struct{}) errs := make(chan error, workers) var wg sync.WaitGroup wg.Add(workers) for i := 0; i < workers; i++ { go func() { defer wg.Done() <-barrier strategies := provider.LookupAddr("example.com") if len(strategies) == 0 { errs <- fmt.Errorf("no auth strategies found") return } req, reqErr := http.NewRequest(http.MethodGet, "https://example.com", nil) if reqErr != nil { errs <- reqErr return } for _, strategy := range strategies { strategy.Apply(req) } if got := req.Header.Get("Authorization"); got != "Bearer session-token" { errs <- fmt.Errorf("expected Authorization header to be set, got %q", got) } }() } close(barrier) wg.Wait() close(errs) for gotErr := range errs { require.NoError(t, gotErr) } require.Equal(t, int32(1), fetchCalls.Load(), "dynamic secret fetch should execute once") } ================================================ FILE: pkg/authprovider/interface.go ================================================ package authprovider import ( "fmt" "net/url" "github.com/projectdiscovery/nuclei/v3/pkg/authprovider/authx" urlutil "github.com/projectdiscovery/utils/url" ) var ( ErrNoSecrets = fmt.Errorf("no secrets in given provider") ) var ( _ AuthProvider = &FileAuthProvider{} ) // AuthProvider is an interface for auth providers // It implements a data structure suitable for quick lookup and retrieval // of auth strategies type AuthProvider interface { // LookupAddr looks up a given domain/address and returns appropriate auth strategy // for it (accepted inputs are scanme.sh or scanme.sh:443) LookupAddr(string) []authx.AuthStrategy // LookupURL looks up a given URL and returns appropriate auth strategy // it accepts a valid url struct and returns the auth strategy LookupURL(*url.URL) []authx.AuthStrategy // LookupURLX looks up a given URL and returns appropriate auth strategy // it accepts pd url struct (i.e urlutil.URL) and returns the auth strategy LookupURLX(*urlutil.URL) []authx.AuthStrategy // GetTemplatePaths returns the template path for the auth provider // that will be used for dynamic secret fetching GetTemplatePaths() []string // PreFetchSecrets pre-fetches the secrets from the auth provider // instead of lazy fetching PreFetchSecrets() error } // AuthProviderOptions contains options for the auth provider type AuthProviderOptions struct { // File based auth provider options SecretsFiles []string // LazyFetchSecret is a callback for lazy fetching of dynamic secrets LazyFetchSecret authx.LazyFetchSecret } // NewAuthProvider creates a new auth provider from the given options func NewAuthProvider(options *AuthProviderOptions) (AuthProvider, error) { var providers []AuthProvider for _, file := range options.SecretsFiles { provider, err := NewFileAuthProvider(file, options.LazyFetchSecret) if err != nil { return nil, err } providers = append(providers, provider) } return NewMultiAuthProvider(providers...), nil } ================================================ FILE: pkg/authprovider/multi.go ================================================ package authprovider import ( "net/url" "github.com/projectdiscovery/nuclei/v3/pkg/authprovider/authx" urlutil "github.com/projectdiscovery/utils/url" ) // MultiAuthProvider is a convenience wrapper for multiple auth providers // it returns the first matching auth strategy for a given domain // if there are multiple auth strategies for a given domain, it returns the first one type MultiAuthProvider struct { Providers []AuthProvider } // NewMultiAuthProvider creates a new multi auth provider func NewMultiAuthProvider(providers ...AuthProvider) AuthProvider { return &MultiAuthProvider{Providers: providers} } func (m *MultiAuthProvider) LookupAddr(host string) []authx.AuthStrategy { for _, provider := range m.Providers { strategy := provider.LookupAddr(host) if len(strategy) > 0 { return strategy } } return nil } func (m *MultiAuthProvider) LookupURL(u *url.URL) []authx.AuthStrategy { for _, provider := range m.Providers { strategy := provider.LookupURL(u) if strategy != nil { return strategy } } return nil } func (m *MultiAuthProvider) LookupURLX(u *urlutil.URL) []authx.AuthStrategy { for _, provider := range m.Providers { strategy := provider.LookupURLX(u) if strategy != nil { return strategy } } return nil } func (m *MultiAuthProvider) GetTemplatePaths() []string { var res []string for _, provider := range m.Providers { res = append(res, provider.GetTemplatePaths()...) } return res } func (m *MultiAuthProvider) PreFetchSecrets() error { for _, provider := range m.Providers { if err := provider.PreFetchSecrets(); err != nil { return err } } return nil } ================================================ FILE: pkg/catalog/aws/catalog.go ================================================ package aws import ( "bytes" "context" "errors" "fmt" "io" "path" "slices" "strings" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/feature/s3/manager" "github.com/aws/aws-sdk-go-v2/service/s3" ) // Catalog manages the AWS S3 template catalog type Catalog struct { svc client } // client interface abstracts S3 connections type client interface { getAllKeys() ([]string, error) downloadKey(name string) (io.ReadCloser, error) setBucket(bucket string) } type s3svc struct { client *s3.Client bucket string } // NewCatalog creates a new AWS Catalog object given a required S3 bucket name and optional configurations. If // no configurations to set AWS keys are provided then environment variables will be used to obtain AWS credentials. func NewCatalog(bucket string, configurations ...func(*Catalog) error) (Catalog, error) { var c Catalog for _, configuration := range configurations { err := configuration(&c) if err != nil { return c, err } } if c.svc == nil { cfg, err := config.LoadDefaultConfig(context.TODO()) if err != nil { return c, err } c.svc = &s3svc{ client: s3.NewFromConfig(cfg), } } c.svc.setBucket(bucket) return c, nil } // WithAWSKeys enables explicitly setting the AWS access key, secret key and region func WithAWSKeys(accessKey, secretKey, region string) func(*Catalog) error { return func(c *Catalog) error { cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accessKey, secretKey, "")), config.WithRegion(region)) if err != nil { return err } c.svc = &s3svc{ client: s3.NewFromConfig(cfg), bucket: "", } return nil } } // OpenFile downloads a file from S3 and returns the contents as an io.ReadCloser func (c Catalog) OpenFile(filename string) (io.ReadCloser, error) { if filename == "" { return nil, errors.New("empty filename") } return c.svc.downloadKey(filename) } // GetTemplatePath looks for a target string performing a simple substring check // against all S3 keys. If the input includes a wildcard (*) it is removed. func (c Catalog) GetTemplatePath(target string) ([]string, error) { target = strings.ReplaceAll(target, "*", "") keys, err := c.svc.getAllKeys() if err != nil { return nil, err } var matches []string for _, key := range keys { if strings.Contains(key, target) { matches = append(matches, key) } } return matches, nil } // GetTemplatesPath returns all templates from S3 func (c Catalog) GetTemplatesPath(definitions []string) ([]string, map[string]error) { keys, err := c.svc.getAllKeys() if err != nil { // necessary to implement the Catalog interface return nil, map[string]error{"aws": err} } return keys, nil } // ResolvePath gets a full S3 key given the first param. If the second parameter is // provided it tries to find paths relative to the second path. func (c Catalog) ResolvePath(templateName, second string) (string, error) { keys, err := c.svc.getAllKeys() if err != nil { return "", err } // if c second path is given, it's c folder and we join the two and check against keys if second != "" { // Note: Do not replace `path` with `filepath` since filepath is aware of Os path separator // and we only see `/` in s3 paths changing it to filepath cause build fail and other errors target := path.Join(path.Dir(second), templateName) for _, key := range keys { if key == target { return key, nil } } } // check if templateName is already an absolute path to c key if slices.Contains(keys, templateName) { return templateName, nil } return "", fmt.Errorf("no such path found: %s%s for keys: %v", second, templateName, keys) } func (s *s3svc) getAllKeys() ([]string, error) { paginator := s3.NewListObjectsV2Paginator(s.client, &s3.ListObjectsV2Input{ Bucket: &s.bucket, }) var keys []string for paginator.HasMorePages() { page, err := paginator.NextPage(context.TODO()) if err != nil { return nil, err } for _, obj := range page.Contents { key := aws.ToString(obj.Key) keys = append(keys, key) } } return keys, nil } func (s *s3svc) downloadKey(name string) (io.ReadCloser, error) { downloader := manager.NewDownloader(s.client) buf := manager.NewWriteAtBuffer([]byte{}) _, err := downloader.Download(context.TODO(), buf, &s3.GetObjectInput{ Bucket: aws.String(s.bucket), Key: aws.String(name), }) if err != nil { return nil, err } return io.NopCloser(bytes.NewReader(buf.Bytes())), nil } func (s *s3svc) setBucket(bucket string) { s.bucket = bucket } ================================================ FILE: pkg/catalog/aws/catalog_test.go ================================================ package aws import ( "io" "reflect" "slices" "strings" "testing" "github.com/pkg/errors" ) func TestCatalog_GetTemplatePath(t *testing.T) { type args struct { target string } tests := []struct { name string args args want []string wantErr bool }{ { "get all ssl files", args{ target: "ssl", }, []string{ "ssl/deprecated-tls.yaml", "ssl/detect-ssl-issuer.yaml", "ssl/expired-ssl.yaml", "ssl/mismatched-ssl.yaml", }, false, }, { "get all ssl files with wildcard", args{ target: "ssl*", }, []string{ "ssl/deprecated-tls.yaml", "ssl/detect-ssl-issuer.yaml", "ssl/expired-ssl.yaml", "ssl/mismatched-ssl.yaml", }, false, }, { "non-matching target", args{ target: "I-DONT-EXIST", }, []string{}, false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c, _ := NewCatalog("bucket", withMockS3Service()) got, err := c.GetTemplatePath(tt.args.target) if (err != nil) != tt.wantErr { t.Errorf("GetTemplatePath() error = %v, wantErr %v", err, tt.wantErr) return } if len(tt.want) > 0 && !reflect.DeepEqual(got, tt.want) { t.Errorf("GetTemplatePath() got = %v, want %v", got, tt.want) } if len(tt.want) == 0 && len(got) > 0 { t.Errorf("GetTemplatePath() got = %v, want %v", got, tt.want) } }) } } func TestCatalog_GetTemplatesPath(t *testing.T) { tmp := newMockS3Service() keys, _ := tmp.getAllKeys() type args struct { definitions []string } tests := []struct { name string args args want []string wantErr bool }{ { "without definitions", args{ definitions: nil, }, keys, false, }, { "with definitions", args{ definitions: []string{"ssl/deprecated-tls.yaml"}, }, keys, false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c, _ := NewCatalog("bucket", withMockS3Service()) got, got1 := c.GetTemplatesPath(tt.args.definitions) if got1 != nil { val, exists := got1["aws"] if exists && !tt.wantErr { t.Errorf("GetTemplatesPath() error = %v, wantErr %v", val, tt.wantErr) } if !exists && len(got1) > 0 { t.Errorf("GetTemplatesPath() should only return one key 'aws': %v", got1) } if !exists && tt.wantErr { t.Errorf("GetTemplatesPath() error = %v, wantErr %v", val, tt.wantErr) } } if !reflect.DeepEqual(got, tt.want) { t.Errorf("GetTemplatesPath() got = %v, want %v", got, tt.want) } }) } } func TestCatalog_OpenFile(t *testing.T) { tests := []struct { name string filename string wantErr bool }{ { "valid key", "ssl/deprecated-tls.yaml", false, }, { "nonexistent key", "something/that-doesnt-exist.yaml", true, }, { "path to folder", "cves/2023", true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c, _ := NewCatalog("bucket", withMockS3Service()) got, err := c.OpenFile(tt.filename) if (err != nil) != tt.wantErr { t.Errorf("OpenFile() error = %v, wantErr %v", err, tt.wantErr) return } if err == nil && got == nil { t.Error("OpenFile() didn't return error but io.ReadCloser is nil") } }) } } func TestCatalog_ResolvePath(t *testing.T) { type args struct { templateName string second string } tests := []struct { name string args args want string wantErr bool }{ { "absolute path", args{ "ssl/deprecated-tls.yaml", "", }, "ssl/deprecated-tls.yaml", false, }, { "relative path with second param", args{ "deprecated-tls.yaml", "ssl/", }, "ssl/deprecated-tls.yaml", false, }, { "relative path and no second param", args{ "cves/2023", "", }, "", true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c, _ := NewCatalog("bucket", withMockS3Service()) got, err := c.ResolvePath(tt.args.templateName, tt.args.second) if (err != nil) != tt.wantErr { t.Errorf("ResolvePath() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("ResolvePath() got = %v, want %v", got, tt.want) } }) } } func withMockS3Service() func(*Catalog) error { return func(c *Catalog) error { c.svc = newMockS3Service() return nil } } type mocks3svc struct { keys []string } func newMockS3Service() mocks3svc { return mocks3svc{ keys: []string{ "ssl/deprecated-tls.yaml", "ssl/detect-ssl-issuer.yaml", "ssl/expired-ssl.yaml", "ssl/mismatched-ssl.yaml", "cves/2023/CVE-2023-0669.yaml", "cves/2023/CVE-2023-23488.yaml", "cves/2023/CVE-2023-23489.yaml", }, } } func (m mocks3svc) getAllKeys() ([]string, error) { return m.keys, nil } func (m mocks3svc) downloadKey(name string) (io.ReadCloser, error) { found := slices.Contains(m.keys, name) if !found { return nil, errors.New("key not found") } sample := ` id: git-config info: name: Git Config File author: Ice3man severity: medium description: Searches for the pattern /.git/config on passed URLs. requests: - method: GET path: - "{{BaseURL}}/.git/config" matchers: - type: word words: - "[core]" ` return io.NopCloser(strings.NewReader(sample)), nil } func (m mocks3svc) setBucket(bucket string) {} ================================================ FILE: pkg/catalog/catalog.go ================================================ package catalog import "io" // Catalog is a catalog storage implementations type Catalog interface { // OpenFile opens a file and returns an io.ReadCloser to the file. // It is used to read template and payload files based on catalog responses. OpenFile(filename string) (io.ReadCloser, error) // GetTemplatePath parses the specified input template path and returns a compiled // list of finished absolute paths to the templates evaluating any glob patterns // or folders provided as in. GetTemplatePath(target string) ([]string, error) // GetTemplatesPath returns a list of absolute paths for the provided template list. GetTemplatesPath(definitions []string) ([]string, map[string]error) // ResolvePath resolves the path to an absolute one in various ways. // // It checks if the filename is an absolute path, looks in the current directory // or checking the nuclei templates directory. If a second path is given, // it also tries to find paths relative to that second path. ResolvePath(templateName, second string) (string, error) } ================================================ FILE: pkg/catalog/config/constants.go ================================================ package config import ( "strings" "github.com/Masterminds/semver/v3" ) type AppMode string const ( AppModeLibrary AppMode = "library" AppModeCLI AppMode = "cli" ) var ( // Global Var to control behaviours specific to cli or library // maybe this should be moved to utils ?? // this is overwritten in cmd/nuclei/main.go CurrentAppMode = AppModeLibrary ) const ( TemplateConfigFileName = ".templates-config.json" NucleiTemplatesDirName = "nuclei-templates" OfficialNucleiTemplatesRepoName = "nuclei-templates" NucleiIgnoreFileName = ".nuclei-ignore" NucleiTemplatesIndexFileName = ".templates-index" // contains index of official nuclei templates NucleiTemplatesCheckSumFileName = ".checksum" NewTemplateAdditionsFileName = ".new-additions" CLIConfigFileName = "config.yaml" ReportingConfigFilename = "reporting-config.yaml" // Version is the current version of nuclei Version = `v3.7.1` // Directory Names of custom templates CustomS3TemplatesDirName = "s3" CustomGitHubTemplatesDirName = "github" CustomAzureTemplatesDirName = "azure" CustomGitLabTemplatesDirName = "gitlab" BinaryName = "nuclei" FallbackConfigFolderName = ".nuclei-config" NucleiConfigDirEnv = "NUCLEI_CONFIG_DIR" NucleiTemplatesDirEnv = "NUCLEI_TEMPLATES_DIR" ) // IsOutdatedVersion compares two versions and returns true // if the current version is outdated func IsOutdatedVersion(current, latest string) bool { if latest == "" { // NOTE(dwisiswant0): if PDTM API call failed or returned empty, we // cannot determine if templates are outdated w/o additional checks // return false to avoid unnecessary updates. return false } current = trimDevIfExists(current) currentVer, _ := semver.NewVersion(current) newVer, _ := semver.NewVersion(latest) if currentVer == nil || newVer == nil { // fallback to naive comparison - return true only if they are different return current != latest } return newVer.GreaterThan(currentVer) } // trimDevIfExists trims `-dev` suffix from version string if it exists func trimDevIfExists(version string) string { if strings.HasSuffix(version, "-dev") { return strings.TrimSuffix(version, "-dev") } return version } // similar to go pattern of enabling debug related features // we add custom/extra switches for debugging purposes const ( // DebugArgHostErrorStats is used to print host error stats // when it is closed DebugArgHostErrorStats = "host-error-stats" // DebugExportReqURLPattern is used to export request URL pattern DebugExportURLPattern = "req-url-pattern" ) ================================================ FILE: pkg/catalog/config/ignorefile.go ================================================ package config import ( "os" "github.com/projectdiscovery/gologger" "gopkg.in/yaml.v2" ) // IgnoreFile is an internal nuclei template blocking configuration file type IgnoreFile struct { Tags []string `yaml:"tags"` Files []string `yaml:"files"` } // ReadIgnoreFile reads the nuclei ignore file returning blocked tags and paths func ReadIgnoreFile() IgnoreFile { file, err := os.Open(DefaultConfig.GetIgnoreFilePath()) if err != nil { gologger.Error().Msgf("Could not read nuclei-ignore file: %s\n", err) return IgnoreFile{} } defer func() { _ = file.Close() }() ignore := IgnoreFile{} if err := yaml.NewDecoder(file).Decode(&ignore); err != nil { gologger.Error().Msgf("Could not parse nuclei-ignore file: %s\n", err) return IgnoreFile{} } return ignore } ================================================ FILE: pkg/catalog/config/nucleiconfig.go ================================================ package config import ( "bytes" "crypto/md5" "fmt" "os" "path/filepath" "slices" "strings" "sync" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/utils/json" "github.com/projectdiscovery/utils/env" "github.com/projectdiscovery/utils/errkit" fileutil "github.com/projectdiscovery/utils/file" folderutil "github.com/projectdiscovery/utils/folder" ) // DefaultConfig is the default nuclei configuration // all config values and default are centralized here var DefaultConfig *Config type Config struct { TemplatesDirectory string `json:"nuclei-templates-directory,omitempty"` // customtemplates exists in templates directory with the name of custom-templates provider // below custom paths are absolute paths to respective custom-templates directories CustomS3TemplatesDirectory string `json:"custom-s3-templates-directory"` CustomGitHubTemplatesDirectory string `json:"custom-github-templates-directory"` CustomGitLabTemplatesDirectory string `json:"custom-gitlab-templates-directory"` CustomAzureTemplatesDirectory string `json:"custom-azure-templates-directory"` TemplateVersion string `json:"nuclei-templates-version,omitempty"` NucleiIgnoreHash string `json:"nuclei-ignore-hash,omitempty"` LogAllEvents bool `json:"-"` // when enabled logs all events (more than verbose) HideTemplateSigWarning bool `json:"-"` // when enabled disables template signature warning // LatestXXX are not meant to be used directly and is used as // local cache of nuclei version check endpoint // these fields are only update during nuclei version check // TODO: move these fields to a separate unexported struct as they are not meant to be used directly LatestNucleiVersion string `json:"nuclei-latest-version"` LatestNucleiTemplatesVersion string `json:"nuclei-templates-latest-version"` LatestNucleiIgnoreHash string `json:"nuclei-latest-ignore-hash,omitempty"` Logger *gologger.Logger `json:"-"` // logger // internal / unexported fields disableUpdates bool `json:"-"` // disable updates both version check and template updates homeDir string `json:"-"` // User Home Directory configDir string `json:"-"` // Nuclei Global Config Directory debugArgs []string `json:"-"` // debug args m sync.Mutex } // IsCustomTemplate determines whether a given template is custom-built or part of the official Nuclei templates. // It checks if the template's path matches any of the predefined custom template directories // (such as S3, GitHub, GitLab, and Azure directories). If the template resides in any of these directories, // it is considered custom. Additionally, if the template's path does not start with the main Nuclei TemplatesDirectory, // it is also considered custom. This function assumes that template paths are either absolute // or relative to the same base as the paths configured in DefaultConfig. func (c *Config) IsCustomTemplate(templatePath string) bool { customDirs := []string{ c.CustomS3TemplatesDirectory, c.CustomGitHubTemplatesDirectory, c.CustomGitLabTemplatesDirectory, c.CustomAzureTemplatesDirectory, } for _, dir := range customDirs { if strings.HasPrefix(templatePath, dir) { return true } } return !strings.HasPrefix(templatePath, c.TemplatesDirectory) } // WriteVersionCheckData writes version check data to config file func (c *Config) WriteVersionCheckData(ignorehash, nucleiVersion, templatesVersion string) error { updated := false if ignorehash != "" && c.LatestNucleiIgnoreHash != ignorehash { c.LatestNucleiIgnoreHash = ignorehash updated = true } if nucleiVersion != "" && c.LatestNucleiVersion != nucleiVersion { c.LatestNucleiVersion = nucleiVersion updated = true } if templatesVersion != "" && c.LatestNucleiTemplatesVersion != templatesVersion { c.LatestNucleiTemplatesVersion = templatesVersion updated = true } // write config to disk if any of the fields are updated if updated { return c.WriteTemplatesConfig() } return nil } // GetTemplateDir returns the nuclei templates directory absolute path func (c *Config) GetTemplateDir() string { val, _ := filepath.Abs(c.TemplatesDirectory) return val } // DisableUpdateCheck disables update check and template updates func (c *Config) DisableUpdateCheck() { c.m.Lock() defer c.m.Unlock() c.disableUpdates = true } // CanCheckForUpdates returns true if update check is enabled func (c *Config) CanCheckForUpdates() bool { c.m.Lock() defer c.m.Unlock() return !c.disableUpdates } // NeedsTemplateUpdate returns true if template installation/update is required func (c *Config) NeedsTemplateUpdate() bool { c.m.Lock() defer c.m.Unlock() return !c.disableUpdates && (c.TemplateVersion == "" || IsOutdatedVersion(c.TemplateVersion, c.LatestNucleiTemplatesVersion) || !fileutil.FolderExists(c.TemplatesDirectory)) } // NeedsIgnoreFileUpdate returns true if Ignore file hash is different (aka ignore file is outdated) func (c *Config) NeedsIgnoreFileUpdate() bool { c.m.Lock() defer c.m.Unlock() return c.NucleiIgnoreHash == "" || c.NucleiIgnoreHash != c.LatestNucleiIgnoreHash } // UpdateNucleiIgnoreHash updates the nuclei ignore hash in config func (c *Config) UpdateNucleiIgnoreHash() error { // calculate hash of ignore file and update config ignoreFilePath := c.GetIgnoreFilePath() if fileutil.FileExists(ignoreFilePath) { bin, err := os.ReadFile(ignoreFilePath) if err != nil { return errkit.Newf("could not read nuclei ignore file: %v", err) } c.NucleiIgnoreHash = fmt.Sprintf("%x", md5.Sum(bin)) // write config to disk return c.WriteTemplatesConfig() } return errkit.New("ignore file not found: could not update nuclei ignore hash") } // GetConfigDir returns the nuclei configuration directory func (c *Config) GetConfigDir() string { return c.configDir } // GetKeysDir returns the nuclei signer keys directory func (c *Config) GetKeysDir() string { return filepath.Join(c.configDir, "keys") } // GetAllCustomTemplateDirs returns all custom template directories func (c *Config) GetAllCustomTemplateDirs() []string { return []string{c.CustomS3TemplatesDirectory, c.CustomGitHubTemplatesDirectory, c.CustomGitLabTemplatesDirectory, c.CustomAzureTemplatesDirectory} } // GetReportingConfigFilePath returns the nuclei reporting config file path func (c *Config) GetReportingConfigFilePath() string { return filepath.Join(c.configDir, ReportingConfigFilename) } // GetIgnoreFilePath returns the nuclei ignore file path func (c *Config) GetIgnoreFilePath() string { return filepath.Join(c.configDir, NucleiIgnoreFileName) } func (c *Config) GetTemplateIndexFilePath() string { return filepath.Join(c.TemplatesDirectory, NucleiTemplatesIndexFileName) } // GetChecksumFilePath returns checksum file path of nuclei templates func (c *Config) GetChecksumFilePath() string { return filepath.Join(c.TemplatesDirectory, NucleiTemplatesCheckSumFileName) } // GetFlagsConfigFilePath returns the nuclei cli config file path func (c *Config) GetFlagsConfigFilePath() string { return filepath.Join(c.configDir, CLIConfigFileName) } // GetNewAdditions returns new template additions in current template release // if .new-additions file is not present empty slice is returned func (c *Config) GetNewAdditions() []string { arr := []string{} newAdditionsPath := filepath.Join(c.TemplatesDirectory, NewTemplateAdditionsFileName) if !fileutil.FileExists(newAdditionsPath) { return arr } bin, err := os.ReadFile(newAdditionsPath) if err != nil { return arr } for v := range strings.FieldsSeq(string(bin)) { if IsTemplateWithRoot(v, c.TemplatesDirectory) { arr = append(arr, v) } } return arr } // GetCacheDir returns the nuclei cache directory // with new version of nuclei cache directory is changed // instead of saving resume files in nuclei config directory // they are saved in nuclei cache directory func (c *Config) GetCacheDir() string { return folderutil.AppCacheDirOrDefault(".nuclei-cache", BinaryName) } // SetConfigDir sets the nuclei configuration directory // and appropriate changes are made to the config func (c *Config) SetConfigDir(dir string) { c.configDir = dir if err := c.createConfigDirIfNotExists(); err != nil { c.Logger.Fatal().Msgf("Could not create nuclei config directory at %s: %s", c.configDir, err) } // if folder already exists read config or create new if err := c.ReadTemplatesConfig(); err != nil { // create new config applyDefaultConfig() if err2 := c.WriteTemplatesConfig(); err2 != nil { c.Logger.Fatal().Msgf("Could not create nuclei config file at %s: %s", c.getTemplatesConfigFilePath(), err2) } } // while other config files are optional, ignore file is mandatory // since it is used to ignore templates with weak matchers c.copyIgnoreFile() } // SetTemplatesDir sets the new nuclei templates directory func (c *Config) SetTemplatesDir(dirPath string) { if dirPath != "" && !filepath.IsAbs(dirPath) { cwd, _ := os.Getwd() dirPath = filepath.Join(cwd, dirPath) } c.TemplatesDirectory = dirPath // Update the custom templates directory c.CustomGitHubTemplatesDirectory = filepath.Join(dirPath, CustomGitHubTemplatesDirName) c.CustomS3TemplatesDirectory = filepath.Join(dirPath, CustomS3TemplatesDirName) c.CustomGitLabTemplatesDirectory = filepath.Join(dirPath, CustomGitLabTemplatesDirName) c.CustomAzureTemplatesDirectory = filepath.Join(dirPath, CustomAzureTemplatesDirName) } // SetTemplatesVersion sets the new nuclei templates version func (c *Config) SetTemplatesVersion(version string) error { c.TemplateVersion = version // write config to disk if err := c.WriteTemplatesConfig(); err != nil { return errkit.Newf("could not write nuclei config file at %s: %v", c.getTemplatesConfigFilePath(), err) } return nil } // ReadTemplatesConfig reads the nuclei templates config file func (c *Config) ReadTemplatesConfig() error { if !fileutil.FileExists(c.getTemplatesConfigFilePath()) { return errkit.Newf("nuclei config file at %s does not exist", c.getTemplatesConfigFilePath()) } var cfg *Config bin, err := os.ReadFile(c.getTemplatesConfigFilePath()) if err != nil { return errkit.Newf("could not read nuclei config file at %s: %v", c.getTemplatesConfigFilePath(), err) } if err := json.Unmarshal(bin, &cfg); err != nil { return errkit.Newf("could not unmarshal nuclei config file at %s: %v", c.getTemplatesConfigFilePath(), err) } // apply config c.TemplatesDirectory = cfg.TemplatesDirectory c.TemplateVersion = cfg.TemplateVersion c.NucleiIgnoreHash = cfg.NucleiIgnoreHash c.LatestNucleiIgnoreHash = cfg.LatestNucleiIgnoreHash c.LatestNucleiTemplatesVersion = cfg.LatestNucleiTemplatesVersion return nil } // WriteTemplatesConfig writes the nuclei templates config file func (c *Config) WriteTemplatesConfig() error { // check if config folder exists if not create one if err := c.createConfigDirIfNotExists(); err != nil { return err } bin, err := json.Marshal(c) if err != nil { return errkit.Newf("failed to marshal nuclei config: %v", err) } if err = os.WriteFile(c.getTemplatesConfigFilePath(), bin, 0600); err != nil { return errkit.Newf("failed to write nuclei config file at %s: %v", c.getTemplatesConfigFilePath(), err) } return nil } // WriteTemplatesIndex writes the nuclei templates index file func (c *Config) WriteTemplatesIndex(index map[string]string) error { indexFile := c.GetTemplateIndexFilePath() var buff bytes.Buffer for k, v := range index { _, _ = buff.WriteString(k + "," + v + "\n") } return os.WriteFile(indexFile, buff.Bytes(), 0600) } // getTemplatesConfigFilePath returns configDir/.templates-config.json file path func (c *Config) getTemplatesConfigFilePath() string { return filepath.Join(c.configDir, TemplateConfigFileName) } // createConfigDirIfNotExists creates the nuclei config directory if not exists func (c *Config) createConfigDirIfNotExists() error { if !fileutil.FolderExists(c.configDir) { if err := fileutil.CreateFolder(c.configDir); err != nil { return errkit.Newf("could not create nuclei config directory at %s: %v", c.configDir, err) } } return nil } // copyIgnoreFile copies the nuclei ignore file default config directory // to the current config directory func (c *Config) copyIgnoreFile() { if err := c.createConfigDirIfNotExists(); err != nil { c.Logger.Error().Msgf("Could not create nuclei config directory at %s: %s", c.configDir, err) return } ignoreFilePath := c.GetIgnoreFilePath() if !fileutil.FileExists(ignoreFilePath) { // copy ignore file from default config directory if err := fileutil.CopyFile(filepath.Join(folderutil.AppConfigDirOrDefault(FallbackConfigFolderName, BinaryName), NucleiIgnoreFileName), ignoreFilePath); err != nil { c.Logger.Error().Msgf("Could not copy nuclei ignore file at %s: %s", ignoreFilePath, err) } } } // IsDebugArgEnabled checks if debug arg is enabled // this could be a feature specific to debugging like PPROF or printing stats // of max host error etc func (c *Config) IsDebugArgEnabled(arg string) bool { return slices.Contains(c.debugArgs, arg) } // parseDebugArgs from string func (c *Config) parseDebugArgs(data string) { // use space as separator instead of commas tmp := strings.Fields(data) for _, v := range tmp { key := v value := "" // if it is key value pair then split it if strings.Contains(v, "=") { parts := strings.SplitN(v, "=", 2) if len(parts) != 2 { continue } key, value = strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) } if value == "false" || value == "0" { // if false or disabled then skip continue } switch key { case DebugArgHostErrorStats: c.debugArgs = append(c.debugArgs, DebugArgHostErrorStats) case DebugExportURLPattern: c.debugArgs = append(c.debugArgs, DebugExportURLPattern) } } } func init() { ConfigDir := folderutil.AppConfigDirOrDefault(FallbackConfigFolderName, BinaryName) if cfgDir := os.Getenv(NucleiConfigDirEnv); cfgDir != "" { ConfigDir = cfgDir } // create config directory if not exists if !fileutil.FolderExists(ConfigDir) { if err := fileutil.CreateFolder(ConfigDir); err != nil { gologger.Error().Msgf("failed to create config directory at %v got: %s", ConfigDir, err) } } DefaultConfig = &Config{ homeDir: folderutil.HomeDirOrDefault(""), configDir: ConfigDir, Logger: gologger.DefaultLogger, } // when enabled will log events in more verbosity than -v or -debug // ex: N templates are excluded // with this switch enabled nuclei will print details of above N templates if value := env.GetEnvOrDefault("NUCLEI_LOG_ALL", false); value { DefaultConfig.LogAllEvents = true } if value := env.GetEnvOrDefault("HIDE_TEMPLATE_SIG_WARNING", false); value { DefaultConfig.HideTemplateSigWarning = true } // try to read config from file if err := DefaultConfig.ReadTemplatesConfig(); err != nil { gologger.Verbose().Msgf("config file not found, creating new config file at %s", DefaultConfig.getTemplatesConfigFilePath()) applyDefaultConfig() // write config to file if err := DefaultConfig.WriteTemplatesConfig(); err != nil { gologger.Error().Msgf("failed to write config file at %s got: %s", DefaultConfig.getTemplatesConfigFilePath(), err) } } // Loads/updates paths of custom templates // Note: custom templates paths should not be updated in config file // and even if it is changed we don't follow it since it is not expected behavior // If custom templates are in default locations only then they are loaded while running nuclei DefaultConfig.SetTemplatesDir(DefaultConfig.TemplatesDirectory) DefaultConfig.parseDebugArgs(env.GetEnvOrDefault("NUCLEI_ARGS", "")) } // Add Default Config adds default when .templates-config.json file is not present func applyDefaultConfig() { DefaultConfig.TemplatesDirectory = filepath.Join(DefaultConfig.homeDir, NucleiTemplatesDirName) // updates all necessary paths DefaultConfig.SetTemplatesDir(DefaultConfig.TemplatesDirectory) } ================================================ FILE: pkg/catalog/config/template.go ================================================ package config import ( "encoding/csv" "io" "os" "path/filepath" "strings" "github.com/projectdiscovery/nuclei/v3/pkg/templates/extensions" fileutil "github.com/projectdiscovery/utils/file" stringsutil "github.com/projectdiscovery/utils/strings" ) var ( knownConfigFiles = []string{"cves.json", "contributors.json", "TEMPLATES-STATS.json"} knownMiscDirectories = []string{".git", ".github", "helpers"} ) // TemplateFormat type TemplateFormat uint8 const ( YAML TemplateFormat = iota JSON Unknown ) // GetKnownConfigFiles returns known config files. func GetKnownConfigFiles() []string { return knownConfigFiles } // GetKnownMiscDirectories returns known misc directories with trailing slashes. // // The trailing slash ensures that directory matching is explicit and avoids // falsely match files with similar names (e.g. "helpers" matching // "some-helpers.yaml"), since [IsTemplate] checks against normalized full paths. func GetKnownMiscDirectories() []string { trailedSlashDirs := make([]string, 0, len(knownMiscDirectories)) for _, dir := range knownMiscDirectories { trailedSlashDirs = append(trailedSlashDirs, dir+string(os.PathSeparator)) } return trailedSlashDirs } // GetTemplateFormatFromExt returns template format func GetTemplateFormatFromExt(filePath string) TemplateFormat { fileExt := strings.ToLower(filepath.Ext(filePath)) switch fileExt { case extensions.JSON: return JSON case extensions.YAML: return YAML default: return Unknown } } // GetSupportTemplateFileExtensions returns all supported template file extensions func GetSupportTemplateFileExtensions() []string { return []string{extensions.YAML, extensions.JSON} } // IsTemplate returns true if the file is a template based on its path. // It used by goflags and other places to filter out non-template files. func IsTemplate(fpath string) bool { return IsTemplateWithRoot(fpath, "") } // IsTemplateWithRoot returns true if the file is a template based on its path // and root directory. If rootDir is provided, it checks for excluded // directories relative to the root. func IsTemplateWithRoot(fpath, rootDir string) bool { fpath = filepath.FromSlash(fpath) fname := filepath.Base(fpath) fext := strings.ToLower(filepath.Ext(fpath)) if stringsutil.ContainsAny(fname, GetKnownConfigFiles()...) { return false } var pathToCheck string if rootDir != "" { if filepath.IsAbs(fpath) { rel, err := filepath.Rel(rootDir, fpath) if err == nil && !strings.HasPrefix(rel, "..") { pathToCheck = rel } else { pathToCheck = fpath } } else { pathToCheck = fpath } } else { pathToCheck = fpath } // Only check components if pathToCheck is NOT absolute // This avoids false positives on parent directories for absolute paths if !filepath.IsAbs(pathToCheck) { parts := strings.Split(pathToCheck, string(os.PathSeparator)) for _, p := range parts { for _, excluded := range knownMiscDirectories { if strings.EqualFold(p, excluded) { return false } } } } return stringsutil.EqualFoldAny(fext, GetSupportTemplateFileExtensions()...) } type template struct { ID string `json:"id" yaml:"id"` } // GetTemplateIDFromReader returns template id from reader func GetTemplateIDFromReader(data io.Reader, filename string) (string, error) { var t template var err error switch GetTemplateFormatFromExt(filename) { case YAML: err = fileutil.UnmarshalFromReader(fileutil.YAML, data, &t) case JSON: err = fileutil.UnmarshalFromReader(fileutil.JSON, data, &t) } return t.ID, err } func getTemplateID(filePath string) (string, error) { file, err := os.Open(filePath) if err != nil { return "", err } defer func() { _ = file.Close() }() return GetTemplateIDFromReader(file, filePath) } // GetTemplatesIndexFile returns map[template-id]: template-file-path func GetNucleiTemplatesIndex() (map[string]string, error) { indexFile := DefaultConfig.GetTemplateIndexFilePath() index := map[string]string{} if fileutil.FileExists(indexFile) { f, err := os.Open(indexFile) if err == nil { csvReader := csv.NewReader(f) records, err := csvReader.ReadAll() if err == nil { for _, v := range records { if len(v) >= 2 { templateID := v[0] templatePath := v[1] // Normalize path for consistent comparison (handles Windows path issues) normalizedPath := filepath.Clean(templatePath) // Validate that the file actually exists (prevents stale entries from deleted files on Windows) if fileutil.FileExists(normalizedPath) { index[templateID] = normalizedPath } } } // Close file handle before returning _ = f.Close() return index, nil } _ = f.Close() } DefaultConfig.Logger.Error().Msgf("failed to read index file creating new one: %v", err) } ignoreDirs := DefaultConfig.GetAllCustomTemplateDirs() // empty index if templates are not installed if !fileutil.FolderExists(DefaultConfig.TemplatesDirectory) { return index, nil } err := filepath.WalkDir(DefaultConfig.TemplatesDirectory, func(path string, d os.DirEntry, err error) error { if err != nil { DefaultConfig.Logger.Verbose().Msgf("failed to walk path=%v err=%v", path, err) return nil } if d.IsDir() || !IsTemplateWithRoot(path, DefaultConfig.TemplatesDirectory) || stringsutil.ContainsAny(path, ignoreDirs...) { return nil } // Normalize path for consistent comparison (handles Windows path issues) normalizedPath := filepath.Clean(path) // get template id from file id, err := getTemplateID(normalizedPath) if err != nil || id == "" { DefaultConfig.Logger.Verbose().Msgf("failed to get template id from file=%v got id=%v err=%v", normalizedPath, id, err) return nil } index[id] = normalizedPath return nil }) return index, err } ================================================ FILE: pkg/catalog/config/template_test.go ================================================ package config import ( "os" "path/filepath" "testing" osutils "github.com/projectdiscovery/utils/os" "github.com/stretchr/testify/require" ) func toAbs(p string) string { if osutils.IsWindows() { // Infer the drive letter from the Windows system path // SystemRoot or WINDIR typically points to something like "C:\Windows" systemRoot := os.Getenv("SystemRoot") if systemRoot == "" { systemRoot = os.Getenv("WINDIR") } // Extract volume name (e.g., "C:") from the system path volumeName := filepath.VolumeName(systemRoot) if volumeName == "" { // Fallback to C: if we can't determine the volume volumeName = "C:" } return filepath.FromSlash(volumeName + p) } return filepath.FromSlash(p) } func TestIsTemplate(t *testing.T) { tests := []struct { name string fpath string rootDir string want bool }{ { name: "valid template", fpath: "dns/cname.yaml", rootDir: "", want: true, }, { name: "excluded directory relative", fpath: "helpers/data.txt", rootDir: "", want: false, }, { name: "excluded directory .git", fpath: ".git/config", rootDir: "", want: false, }, { name: "absolute path with excluded parent dir (bug fix)", fpath: toAbs("/path/to/somewhere/that/has/helpers/dir/nuclei/nuclei-templates/dns/cname.yaml"), rootDir: toAbs("/path/to/somewhere/that/has/helpers/dir/nuclei/nuclei-templates"), want: true, }, { name: "absolute path with excluded dir inside root", fpath: toAbs("/path/to/somewhere/that/has/helpers/dir/nuclei/nuclei-templates/helpers/data.txt"), rootDir: toAbs("/path/to/somewhere/that/has/helpers/dir/nuclei/nuclei-templates"), want: false, }, { name: "absolute path without root (skip check)", fpath: toAbs("/opt/helpers/foo.yaml"), rootDir: "", want: true, }, { name: "valid template with different extension", fpath: "dns/cname.json", rootDir: "", want: true, }, { name: "invalid extension", fpath: "dns/cname.txt", rootDir: "", want: false, }, { name: "excluded config file", fpath: "cves.json", rootDir: "", want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := IsTemplateWithRoot(tt.fpath, tt.rootDir) require.Equal(t, tt.want, got, "IsTemplateWithRoot(%q, %q)", tt.fpath, tt.rootDir) }) } } ================================================ FILE: pkg/catalog/disk/catalog.go ================================================ package disk import ( "io" "io/fs" "os" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" ) // DiskCatalog is a template catalog helper implementation based on disk type DiskCatalog struct { templatesDirectory string templatesFS fs.FS // Due to issues with how Go has implemented fs.FS, we'll have to also implement normal os operations, as well. See: https://github.com/golang/go/issues/44279 } // NewCatalog creates a new Catalog structure using provided input items // using disk based items func NewCatalog(directory string) *DiskCatalog { catalog := &DiskCatalog{templatesDirectory: directory} if directory == "" { catalog.templatesDirectory = config.DefaultConfig.GetTemplateDir() } return catalog } // NewFSCatalog creates a new Catalog structure using provided input items // using the fs.FS as its filesystem. func NewFSCatalog(fs fs.FS, directory string) *DiskCatalog { catalog := &DiskCatalog{ templatesDirectory: directory, templatesFS: fs, } return catalog } // OpenFile opens a file and returns an io.ReadCloser to the file. // It is used to read template and payload files based on catalog responses. func (d *DiskCatalog) OpenFile(filename string) (io.ReadCloser, error) { if d.templatesFS == nil { return os.Open(filename) } return d.templatesFS.Open(filename) } ================================================ FILE: pkg/catalog/disk/errors.go ================================================ package disk import "errors" var ( ErrNoTemplatesFound = errors.New("no templates found") ) ================================================ FILE: pkg/catalog/disk/find.go ================================================ package disk import ( "fmt" "io/fs" "os" "path/filepath" "strings" "github.com/logrusorgru/aurora" "github.com/pkg/errors" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" stringsutil "github.com/projectdiscovery/utils/strings" updateutils "github.com/projectdiscovery/utils/update" urlutil "github.com/projectdiscovery/utils/url" ) var deprecatedPathsCounter int // GetTemplatesPath returns a list of absolute paths for the provided template list. func (c *DiskCatalog) GetTemplatesPath(definitions []string) ([]string, map[string]error) { // keeps track of processed dirs and files processed := make(map[string]bool) allTemplates := []string{} erred := make(map[string]error) for _, t := range definitions { if stringsutil.ContainsAny(t, knownConfigFiles...) { // TODO: this is a temporary fix to avoid treating these files as templates // this should be replaced with more appropriate and robust logic continue } if strings.Contains(t, urlutil.SchemeSeparator) && stringsutil.ContainsAny(t, config.GetSupportTemplateFileExtensions()...) { if _, ok := processed[t]; !ok { processed[t] = true allTemplates = append(allTemplates, t) } } else { paths, err := c.GetTemplatePath(t) if err != nil { erred[t] = err } for _, path := range paths { if _, ok := processed[path]; !ok { processed[path] = true allTemplates = append(allTemplates, path) } } } } // purge all false positives filteredTemplates := []string{} for _, v := range allTemplates { // TODO: this is a temporary fix to avoid treating these files as templates // this should be replaced with more appropriate and robust logic if !stringsutil.ContainsAny(v, knownConfigFiles...) { filteredTemplates = append(filteredTemplates, v) } } return filteredTemplates, erred } // GetTemplatePath parses the specified input template path and returns a compiled // list of finished absolute paths to the templates evaluating any glob patterns // or folders provided as in. func (c *DiskCatalog) GetTemplatePath(target string) ([]string, error) { processed := make(map[string]struct{}) if c.templatesFS == nil { var err error target, err = c.convertPathToAbsolute(target) if err != nil { return nil, errors.Wrapf(err, "could not find template file") } } if strings.Contains(target, "*") { globMatches, err := c.findGlobPathMatches(target, processed) if err != nil { return nil, errors.Wrap(err, "could not globbing path") } if len(globMatches) > 0 { return globMatches, nil } else { return globMatches, fmt.Errorf("%w in path %q", ErrNoTemplatesFound, target) } } // `target` is either a file or a directory match, file, err := c.findFileMatches(target, processed) if err != nil { return nil, errors.Wrap(err, "could not find file") } if file { if match != "" { return []string{match}, nil } return nil, nil } // Recursively walk down the Templates directory and run all // the template file checks matches, err := c.findDirectoryMatches(target, processed) if err != nil { return nil, errors.Wrap(err, "could not find directory matches") } if len(matches) == 0 { return nil, fmt.Errorf("%w in path %q", ErrNoTemplatesFound, target) } return matches, nil } // convertPathToAbsolute resolves the paths provided to absolute paths // before doing any operations on them regardless of them being BLOB, folders, files, etc. func (c *DiskCatalog) convertPathToAbsolute(t string) (string, error) { if strings.Contains(t, "*") { file := filepath.Base(t) absPath, err := c.ResolvePath(filepath.Dir(t), "") if err != nil { return "", err } return filepath.Join(absPath, file), nil } return c.ResolvePath(t, "") } // findGlobPathMatches returns the matched files from a glob path func (c *DiskCatalog) findGlobPathMatches(absPath string, processed map[string]struct{}) ([]string, error) { // trim templateDir if any relPath := strings.TrimPrefix(absPath, c.templatesDirectory) // trim leading slash if any if c.templatesFS != nil { // fs.FS always uses forward slashes relPath = strings.TrimPrefix(relPath, "/") } else { relPath = strings.TrimPrefix(relPath, string(os.PathSeparator)) } var err error var matches []string if c.templatesFS != nil { matches, err = fs.Glob(c.templatesFS, relPath) } else { matches, err = filepath.Glob(absPath) } if err != nil { return nil, err } results := make([]string, 0, len(matches)) for _, match := range matches { if _, ok := processed[match]; !ok { processed[match] = struct{}{} results = append(results, match) } } return results, nil } // findFileMatches finds if a path is an absolute file. If the path // is a file, it returns true otherwise false with no errors. func (c *DiskCatalog) findFileMatches(absPath string, processed map[string]struct{}) (match string, matched bool, err error) { if c.templatesFS != nil { absPath = strings.Trim(absPath, "/") } var info fs.File if c.templatesFS == nil { info, err = os.Open(absPath) } else { // If we were given no path, then it's not a file, it's the root, and we can quietly return. if absPath == "" { return "", false, nil } info, err = c.templatesFS.Open(absPath) } if err != nil { return "", false, err } stat, err := info.Stat() if err != nil { return "", false, err } if !stat.Mode().IsRegular() { return "", false, nil } if _, ok := processed[absPath]; !ok { processed[absPath] = struct{}{} return absPath, true, nil } return "", true, nil } // findDirectoryMatches finds matches for templates from a directory func (c *DiskCatalog) findDirectoryMatches(absPath string, processed map[string]struct{}) ([]string, error) { var results []string var err error if c.templatesFS == nil { err = filepath.WalkDir( absPath, func(path string, d fs.DirEntry, err error) error { // continue on errors if err != nil { return nil } if !d.IsDir() && config.IsTemplateWithRoot(path, absPath) { if _, ok := processed[path]; !ok { results = append(results, path) processed[path] = struct{}{} } } return nil }, ) } else { // For the special case of the root directory, we need to pass "." to `fs.WalkDir`. if absPath == "" { absPath = "." } absPath = strings.TrimSuffix(absPath, "/") err = fs.WalkDir( c.templatesFS, absPath, func(path string, d fs.DirEntry, err error) error { // continue on errors if err != nil { return nil } if !d.IsDir() && config.IsTemplateWithRoot(path, absPath) { if _, ok := processed[path]; !ok { results = append(results, path) processed[path] = struct{}{} } } return nil }, ) } return results, err } // PrintDeprecatedPathsMsgIfApplicable prints a warning message if any // deprecated paths are found. Unless mode is silent warning message is printed. // // Deprecated: No longer used since the official Nuclei Templates repository // have restructured this a long time ago. func PrintDeprecatedPathsMsgIfApplicable(isSilent bool) { if !updateutils.IsOutdated("v9.4.3", config.DefaultConfig.TemplateVersion) { return } if deprecatedPathsCounter > 0 && !isSilent { config.DefaultConfig.Logger.Print().Msgf("[%v] Found %v template[s] loaded with deprecated paths, update before v3 for continued support.\n", aurora.Yellow("WRN").String(), deprecatedPathsCounter) } } ================================================ FILE: pkg/catalog/disk/known-files.go ================================================ package disk var knownConfigFiles = []string{"cves.json", "contributors.json", "TEMPLATES-STATS.json"} ================================================ FILE: pkg/catalog/disk/path.go ================================================ package disk import ( "fmt" "io/fs" "os" "path/filepath" "strings" "github.com/pkg/errors" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" fileutil "github.com/projectdiscovery/utils/file" urlutil "github.com/projectdiscovery/utils/url" ) // ResolvePath resolves the path to an absolute one in various ways. // // It checks if the filename is an absolute path, looks in the current directory // or checking the nuclei templates directory. If a second path is given, // it also tries to find paths relative to that second path. func (c *DiskCatalog) ResolvePath(templateName, second string) (string, error) { if filepath.IsAbs(templateName) { return templateName, nil } if c.templatesFS != nil { if potentialPath, err := c.tryResolve(templateName); err != errNoValidCombination { return potentialPath, nil } } if second != "" { secondBasePath := filepath.Join(filepath.Dir(second), templateName) if potentialPath, err := c.tryResolve(secondBasePath); err != errNoValidCombination { return potentialPath, nil } } if c.templatesFS == nil { curDirectory, err := os.Getwd() if err != nil { return "", err } templatePath := filepath.Join(curDirectory, templateName) if potentialPath, err := c.tryResolve(templatePath); err != errNoValidCombination { return potentialPath, nil } } templatePath := filepath.Join(config.DefaultConfig.GetTemplateDir(), templateName) if potentialPath, err := c.tryResolve(templatePath); err != errNoValidCombination { return potentialPath, nil } return "", fmt.Errorf("no such path found: %s", templateName) } var errNoValidCombination = errors.New("no valid combination found") // tryResolve attempts to load locate the target by iterating across all the folders tree func (c *DiskCatalog) tryResolve(fullPath string) (string, error) { if c.templatesFS == nil { if fileutil.FileOrFolderExists(fullPath) { return fullPath, nil } } else { if _, err := fs.Stat(c.templatesFS, fullPath); err == nil { return fullPath, nil } } return "", errNoValidCombination } // BackwardsCompatiblePaths returns new paths for all old/legacy template paths // Note: this is a temporary function and will be removed in the future release // // Deprecated: No longer used since the official Nuclei Templates repository // have restructured this a long time ago. func BackwardsCompatiblePaths(templateDir string, oldPath string) string { // TODO: remove this function in the future release // 1. all http related paths are now moved at path /http // 2. network related CVES are now moved at path /network/cves newPathCallback := func(path string) string { // trim prefix slash if any path = strings.TrimPrefix(path, "/") // try to resolve path at /http subdirectory if fileutil.FileOrFolderExists(filepath.Join(templateDir, "http", path)) { return filepath.Join(templateDir, "http", path) // try to resolve path at /network/cves subdirectory } else if strings.HasPrefix(path, "cves") && fileutil.FileOrFolderExists(filepath.Join(templateDir, "network", "cves", path)) { return filepath.Join(templateDir, "network", "cves", path) } // most likely the path is not found return filepath.Join(templateDir, path) } switch { case fileutil.FileOrFolderExists(oldPath): // new path specified skip processing return oldPath case filepath.IsAbs(oldPath): tmp := strings.TrimPrefix(oldPath, templateDir) if tmp == oldPath { // user provided absolute path which is not in template directory // skip processing return oldPath } // trim the template directory from the path return newPathCallback(tmp) case strings.Contains(oldPath, urlutil.SchemeSeparator): // scheme separator is used to identify the path as url // TBD: add support for url directories ?? return oldPath case strings.Contains(oldPath, "*"): // this is most likely a glob path skip processing return oldPath default: // this is most likely a relative path return newPathCallback(oldPath) } } ================================================ FILE: pkg/catalog/index/filter.go ================================================ package index import ( "path/filepath" "slices" "strings" "github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity" "github.com/projectdiscovery/nuclei/v3/pkg/templates/types" ) // Filter represents filtering criteria for template metadata. // // Inclusion fields (e.g., Authors, Tags, IDs, Severities, ProtocolTypes) use // AND logic across different filter types and OR logic within each type. // Exclusion fields (e.g., ExcludeTags, ExcludeIDs, ExcludeSeverities, // ExcludeProtocolTypes) take precedence over inclusion fields. Additionally, // IncludeTemplates and IncludeTags can force inclusion of templates even if // they match exclusion criteria. type Filter struct { // Authors to include. Authors []string // Tags to include. Tags []string // ExcludeTags to exclude (takes precedence over Tags). ExcludeTags []string // IncludeTags to force include even if excluded. IncludeTags []string // IDs to include (supports wildcards, OR logic). IDs []string // ExcludeIDs to exclude (supports wildcards). ExcludeIDs []string // IncludeTemplates paths to force include even if excluded. IncludeTemplates []string // ExcludeTemplates paths to exclude. ExcludeTemplates []string // Severities to include. Severities []severity.Severity // ExcludeSeverities to exclude. ExcludeSeverities []severity.Severity // ProtocolTypes to include. ProtocolTypes []types.ProtocolType // ExcludeProtocolTypes to exclude. ExcludeProtocolTypes []types.ProtocolType } // Matches checks if metadata matches the filter criteria. func (f *Filter) Matches(m *Metadata) bool { if f.isForcedInclude(m) { return true } if f.isExcluded(m) { return false } if !f.matchesIncludes(m) { return false } return true } // isForcedInclude checks if template is forced to be included. func (f *Filter) isForcedInclude(m *Metadata) bool { if len(f.IncludeTemplates) > 0 { for _, includePath := range f.IncludeTemplates { if matchesPath(m.FilePath, includePath) { return true } } } if len(f.IncludeTags) > 0 { if slices.ContainsFunc(f.IncludeTags, m.HasTag) { return true } } return false } // isExcluded checks if template should be excluded. func (f *Filter) isExcluded(m *Metadata) bool { if len(f.ExcludeTemplates) > 0 { for _, excludePath := range f.ExcludeTemplates { if matchesPath(m.FilePath, excludePath) { return true } } } if len(f.ExcludeTags) > 0 { if slices.ContainsFunc(f.ExcludeTags, m.HasTag) { return true } } if len(f.ExcludeIDs) > 0 { for _, excludeID := range f.ExcludeIDs { if matchesID(m.ID, excludeID) { return true } } } if len(f.ExcludeSeverities) > 0 { if slices.ContainsFunc(f.ExcludeSeverities, m.MatchesSeverity) { return true } } if len(f.ExcludeProtocolTypes) > 0 { if slices.ContainsFunc(f.ExcludeProtocolTypes, m.MatchesProtocol) { return true } } return false } // matchesIncludes checks if metadata matches include filters. // // Returns true if no include filters are specified, or if all specified filter // types match. func (f *Filter) matchesIncludes(m *Metadata) bool { if len(f.Authors) > 0 { if !slices.ContainsFunc(f.Authors, m.HasAuthor) { return false } } if len(f.Tags) > 0 { if !slices.ContainsFunc(f.Tags, m.HasTag) { return false } } if len(f.IDs) > 0 { matched := false for _, id := range f.IDs { if matchesID(m.ID, id) { matched = true break } } if !matched { return false } } if len(f.Severities) > 0 { if !slices.ContainsFunc(f.Severities, m.MatchesSeverity) { return false } } if len(f.ProtocolTypes) > 0 { if !slices.ContainsFunc(f.ProtocolTypes, m.MatchesProtocol) { return false } } return true } // matchesID checks if template ID matches pattern (supports wildcards). func matchesID(templateID, pattern string) bool { // Convert to lowercase for case-insensitive matching templateID = strings.ToLower(templateID) pattern = strings.ToLower(pattern) if templateID == pattern { return true } matched, _ := filepath.Match(pattern, templateID) return matched } // matchesPath checks if template path matches pattern. func matchesPath(templatePath, pattern string) bool { templatePath = filepath.Clean(templatePath) pattern = filepath.Clean(pattern) if templatePath == pattern { return true } if strings.HasPrefix(templatePath, pattern+string(filepath.Separator)) { return true } matched, _ := filepath.Match(pattern, templatePath) return matched } // FilterFunc is a function that filters metadata. type FilterFunc func(*Metadata) bool // UnmarshalFilter creates a Filter from nuclei options. func UnmarshalFilter( authors, tags, excludeTags, includeTags []string, ids, excludeIDs []string, includeTemplates, excludeTemplates []string, severities, excludeSeverities []string, protocolTypes, excludeProtocolTypes []string, ) (*Filter, error) { filter := &Filter{ Authors: authors, Tags: tags, ExcludeTags: excludeTags, IncludeTags: includeTags, IDs: ids, ExcludeIDs: excludeIDs, IncludeTemplates: includeTemplates, ExcludeTemplates: excludeTemplates, } for _, sev := range severities { holder := &severity.Holder{} if err := holder.UnmarshalYAML(func(v interface{}) error { *v.(*string) = sev return nil }); err == nil { filter.Severities = append(filter.Severities, holder.Severity) } } for _, sev := range excludeSeverities { holder := &severity.Holder{} if err := holder.UnmarshalYAML(func(v interface{}) error { *v.(*string) = sev return nil }); err == nil { filter.ExcludeSeverities = append(filter.ExcludeSeverities, holder.Severity) } } for _, pt := range protocolTypes { holder := &types.TypeHolder{} if err := holder.UnmarshalYAML(func(v interface{}) error { *v.(*string) = pt return nil }); err == nil && holder.ProtocolType != types.InvalidProtocol { filter.ProtocolTypes = append(filter.ProtocolTypes, holder.ProtocolType) } } for _, pt := range excludeProtocolTypes { holder := &types.TypeHolder{} if err := holder.UnmarshalYAML(func(v interface{}) error { *v.(*string) = pt return nil }); err == nil && holder.ProtocolType != types.InvalidProtocol { filter.ExcludeProtocolTypes = append(filter.ExcludeProtocolTypes, holder.ProtocolType) } } return filter, nil } // UnmarshalFilterFunc creates a FilterFunc from filter criteria. func UnmarshalFilterFunc(filter *Filter) FilterFunc { if filter == nil { return func(*Metadata) bool { return true } } return filter.Matches } // IsEmpty returns true if filter has no criteria set. func (f *Filter) IsEmpty() bool { return len(f.Authors) == 0 && len(f.Tags) == 0 && len(f.ExcludeTags) == 0 && len(f.IncludeTags) == 0 && len(f.IDs) == 0 && len(f.ExcludeIDs) == 0 && len(f.IncludeTemplates) == 0 && len(f.ExcludeTemplates) == 0 && len(f.Severities) == 0 && len(f.ExcludeSeverities) == 0 && len(f.ProtocolTypes) == 0 && len(f.ExcludeProtocolTypes) == 0 } // String returns a human-readable representation of the filter. func (f *Filter) String() string { var parts []string if len(f.Authors) > 0 { parts = append(parts, "authors="+strings.Join(f.Authors, ",")) } if len(f.Tags) > 0 { parts = append(parts, "tags="+strings.Join(f.Tags, ",")) } if len(f.ExcludeTags) > 0 { parts = append(parts, "exclude-tags="+strings.Join(f.ExcludeTags, ",")) } if len(f.IDs) > 0 { parts = append(parts, "ids="+strings.Join(f.IDs, ",")) } if len(f.Severities) > 0 { sevs := make([]string, len(f.Severities)) for i, s := range f.Severities { sevs[i] = s.String() } parts = append(parts, "severities="+strings.Join(sevs, ",")) } if len(f.ProtocolTypes) > 0 { pts := make([]string, len(f.ProtocolTypes)) for i, p := range f.ProtocolTypes { pts[i] = p.String() } parts = append(parts, "types="+strings.Join(pts, ",")) } if len(parts) == 0 { return "filter=" } return "filter(" + strings.Join(parts, ", ") + ")" } ================================================ FILE: pkg/catalog/index/filter_test.go ================================================ package index import ( "os" "path/filepath" "testing" "github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity" "github.com/projectdiscovery/nuclei/v3/pkg/templates/types" "github.com/stretchr/testify/require" ) func TestFilterMatches(t *testing.T) { metadata := &Metadata{ ID: "test-template-1", FilePath: "/templates/cves/2021/CVE-2021-1234.yaml", Name: "Test CVE Template", Authors: []string{"pdteam", "geeknik"}, Tags: []string{"cve", "rce", "apache"}, Severity: "critical", ProtocolType: "http", } t.Run("Empty filter matches all", func(t *testing.T) { filter := &Filter{} require.True(t, filter.Matches(metadata)) require.True(t, filter.IsEmpty()) }) t.Run("Author filter - match", func(t *testing.T) { filter := &Filter{Authors: []string{"pdteam"}} require.True(t, filter.Matches(metadata)) }) t.Run("Author filter - no match", func(t *testing.T) { filter := &Filter{Authors: []string{"unknown"}} require.False(t, filter.Matches(metadata)) }) t.Run("Multiple authors - OR logic", func(t *testing.T) { filter := &Filter{Authors: []string{"unknown", "geeknik"}} require.True(t, filter.Matches(metadata)) }) t.Run("Tag filter - match", func(t *testing.T) { filter := &Filter{Tags: []string{"cve"}} require.True(t, filter.Matches(metadata)) }) t.Run("Tag filter - no match", func(t *testing.T) { filter := &Filter{Tags: []string{"xss"}} require.False(t, filter.Matches(metadata)) }) t.Run("Exclude tags - match", func(t *testing.T) { filter := &Filter{ExcludeTags: []string{"rce"}} require.False(t, filter.Matches(metadata)) }) t.Run("Include tags overrides exclude", func(t *testing.T) { filter := &Filter{ ExcludeTags: []string{"rce"}, IncludeTags: []string{"cve"}, } require.True(t, filter.Matches(metadata)) }) t.Run("ID filter - exact match", func(t *testing.T) { filter := &Filter{IDs: []string{"test-template-1"}} require.True(t, filter.Matches(metadata)) }) t.Run("ID filter - wildcard match", func(t *testing.T) { filter := &Filter{IDs: []string{"test-*"}} require.True(t, filter.Matches(metadata)) }) t.Run("ID filter - no match", func(t *testing.T) { filter := &Filter{IDs: []string{"other-*"}} require.False(t, filter.Matches(metadata)) }) t.Run("Exclude ID - exact match", func(t *testing.T) { filter := &Filter{ExcludeIDs: []string{"test-template-1"}} require.False(t, filter.Matches(metadata)) }) t.Run("Exclude ID - wildcard match", func(t *testing.T) { filter := &Filter{ExcludeIDs: []string{"test-*"}} require.False(t, filter.Matches(metadata)) }) t.Run("Severity filter - match", func(t *testing.T) { filter := &Filter{Severities: []severity.Severity{severity.Critical}} require.True(t, filter.Matches(metadata)) }) t.Run("Severity filter - no match", func(t *testing.T) { filter := &Filter{Severities: []severity.Severity{severity.High, severity.Medium}} require.False(t, filter.Matches(metadata)) }) t.Run("Exclude severity - match", func(t *testing.T) { filter := &Filter{ExcludeSeverities: []severity.Severity{severity.Critical}} require.False(t, filter.Matches(metadata)) }) t.Run("Protocol type filter - match", func(t *testing.T) { filter := &Filter{ProtocolTypes: []types.ProtocolType{types.HTTPProtocol}} require.True(t, filter.Matches(metadata)) }) t.Run("Protocol type filter - no match", func(t *testing.T) { filter := &Filter{ProtocolTypes: []types.ProtocolType{types.DNSProtocol}} require.False(t, filter.Matches(metadata)) }) t.Run("Exclude protocol type - match", func(t *testing.T) { filter := &Filter{ExcludeProtocolTypes: []types.ProtocolType{types.HTTPProtocol}} require.False(t, filter.Matches(metadata)) }) t.Run("Include templates - path match", func(t *testing.T) { filter := &Filter{ ExcludeTags: []string{"cve"}, IncludeTemplates: []string{"/templates/cves/"}, } require.True(t, filter.Matches(metadata)) }) t.Run("Exclude templates - path match", func(t *testing.T) { filter := &Filter{ ExcludeTemplates: []string{"/templates/cves/"}, } require.False(t, filter.Matches(metadata)) }) t.Run("Complex filter - all match", func(t *testing.T) { filter := &Filter{ Authors: []string{"pdteam"}, Tags: []string{"cve"}, Severities: []severity.Severity{severity.Critical}, ProtocolTypes: []types.ProtocolType{types.HTTPProtocol}, } require.True(t, filter.Matches(metadata)) }) t.Run("Complex filter - AND logic across types", func(t *testing.T) { filter := &Filter{ Authors: []string{"pdteam"}, // matches Tags: []string{"xss"}, // doesn't match Severities: []severity.Severity{severity.Critical}, // matches } // With AND logic across filter types, doesn't match because tags don't match // even though author and severity match require.False(t, filter.Matches(metadata)) }) t.Run("Complex filter - no match at all", func(t *testing.T) { filter := &Filter{ Authors: []string{"unknown"}, // doesn't match Tags: []string{"xss"}, // doesn't match Severities: []severity.Severity{severity.Low}, // doesn't match } require.False(t, filter.Matches(metadata)) }) } func TestMatchesPath(t *testing.T) { tests := []struct { name string path string pattern string expected bool }{ {"exact match", "/templates/cves/2021/test.yaml", "/templates/cves/2021/test.yaml", true}, {"directory prefix", "/templates/cves/2021/test.yaml", "/templates/cves", true}, {"directory with slash", "/templates/cves/2021/test.yaml", "/templates/cves/", true}, {"no match", "/templates/cves/2021/test.yaml", "/templates/exploits", false}, {"wildcard match", "/templates/cves/2021/test.yaml", "/templates/*/2021/*.yaml", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := matchesPath(tt.path, tt.pattern) require.Equal(t, tt.expected, result) }) } } func TestMatchesID(t *testing.T) { tests := []struct { name string id string pattern string expected bool }{ {"exact match", "CVE-2021-1234", "CVE-2021-1234", true}, {"wildcard prefix", "CVE-2021-1234", "CVE-*", true}, {"wildcard suffix", "CVE-2021-1234", "*-1234", true}, {"wildcard middle", "CVE-2021-1234", "CVE-*-1234", true}, {"no match", "CVE-2021-1234", "CVE-2022-*", false}, {"partial no match", "CVE-2021-1234", "CVE-2021-12", false}, {"case insensitive exact", "cve-2021-1234", "CVE-2021-1234", true}, {"case insensitive wildcard", "CVE-2021-1234", "cve-*", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := matchesID(tt.id, tt.pattern) require.Equal(t, tt.expected, result) }) } } func TestUnmarshalFilter(t *testing.T) { filter, err := UnmarshalFilter( []string{"author1", "author2"}, []string{"tag1", "tag2"}, []string{"exclude-tag"}, []string{"include-tag"}, []string{"id1", "id2*"}, []string{"exclude-id*"}, []string{"/include/path"}, []string{"/exclude/path"}, []string{"critical", "high"}, []string{"info"}, []string{"http", "dns"}, []string{"file"}, ) require.NoError(t, err) require.NotNil(t, filter) require.Equal(t, []string{"author1", "author2"}, filter.Authors) require.Equal(t, []string{"tag1", "tag2"}, filter.Tags) require.Equal(t, []string{"exclude-tag"}, filter.ExcludeTags) require.Equal(t, []string{"include-tag"}, filter.IncludeTags) require.Equal(t, []string{"id1", "id2*"}, filter.IDs) require.Equal(t, []string{"exclude-id*"}, filter.ExcludeIDs) require.Equal(t, []string{"/include/path"}, filter.IncludeTemplates) require.Equal(t, []string{"/exclude/path"}, filter.ExcludeTemplates) require.Len(t, filter.Severities, 2) require.Contains(t, filter.Severities, severity.Critical) require.Contains(t, filter.Severities, severity.High) require.Len(t, filter.ExcludeSeverities, 1) require.Contains(t, filter.ExcludeSeverities, severity.Info) require.Len(t, filter.ProtocolTypes, 2) require.Contains(t, filter.ProtocolTypes, types.HTTPProtocol) require.Contains(t, filter.ProtocolTypes, types.DNSProtocol) require.Len(t, filter.ExcludeProtocolTypes, 1) require.Contains(t, filter.ExcludeProtocolTypes, types.FileProtocol) } func TestIndexFilter(t *testing.T) { tmpDir := t.TempDir() idx, err := NewIndex(tmpDir) require.NoError(t, err) // Create test templates and metadata templates := []struct { id string path string authors []string tags []string severity string protocol string }{ {"cve-2021-1", "/templates/cves/CVE-2021-1.yaml", []string{"pdteam"}, []string{"cve", "rce"}, "critical", "http"}, {"cve-2021-2", "/templates/cves/CVE-2021-2.yaml", []string{"pdteam"}, []string{"cve", "xss"}, "high", "http"}, {"exploit-1", "/templates/exploits/exploit-1.yaml", []string{"geeknik"}, []string{"exploit"}, "medium", "dns"}, {"info-1", "/templates/info/info-1.yaml", []string{"author1"}, []string{"info"}, "info", "http"}, } for _, tmpl := range templates { tmpFile := filepath.Join(tmpDir, filepath.Base(tmpl.path)) err := os.WriteFile(tmpFile, []byte("id: "+tmpl.id), 0644) require.NoError(t, err) metadata := &Metadata{ ID: tmpl.id, FilePath: tmpFile, Authors: tmpl.authors, Tags: tmpl.tags, Severity: tmpl.severity, ProtocolType: tmpl.protocol, } idx.Set(tmpl.path, metadata) } t.Run("No filter returns all", func(t *testing.T) { results := idx.Filter(nil) require.Len(t, results, 4) }) t.Run("Filter by author", func(t *testing.T) { filter := &Filter{Authors: []string{"pdteam"}} results := idx.Filter(filter) require.Len(t, results, 2) }) t.Run("Filter by tag", func(t *testing.T) { filter := &Filter{Tags: []string{"cve"}} results := idx.Filter(filter) require.Len(t, results, 2) }) t.Run("Filter by severity", func(t *testing.T) { filter := &Filter{Severities: []severity.Severity{severity.Critical}} results := idx.Filter(filter) require.Len(t, results, 1) }) t.Run("Filter by protocol type", func(t *testing.T) { filter := &Filter{ProtocolTypes: []types.ProtocolType{types.HTTPProtocol}} results := idx.Filter(filter) require.Len(t, results, 3) }) t.Run("Exclude by severity", func(t *testing.T) { filter := &Filter{ExcludeSeverities: []severity.Severity{severity.Info}} results := idx.Filter(filter) require.Len(t, results, 3) }) t.Run("Exclude by tag", func(t *testing.T) { filter := &Filter{ExcludeTags: []string{"info"}} results := idx.Filter(filter) require.Len(t, results, 3) }) t.Run("Complex filter", func(t *testing.T) { filter := &Filter{ Tags: []string{"cve"}, Severities: []severity.Severity{severity.Critical, severity.High}, ExcludeSeverities: []severity.Severity{severity.Info}, } results := idx.Filter(filter) require.Len(t, results, 2) }) t.Run("Count with filter", func(t *testing.T) { filter := &Filter{Tags: []string{"cve"}} count := idx.Count(filter) require.Equal(t, 2, count) }) t.Run("Count without filter", func(t *testing.T) { count := idx.Count(nil) require.Equal(t, 4, count) }) } func TestIndexFilterFunc(t *testing.T) { tmpDir := t.TempDir() idx, err := NewIndex(tmpDir) require.NoError(t, err) // Add test metadata for i := 0; i < 5; i++ { metadata := &Metadata{ ID: "test-" + string(rune('a'+i)), FilePath: "/tmp/test.yaml", Severity: "high", } if i%2 == 0 { metadata.Tags = []string{"even"} } else { metadata.Tags = []string{"odd"} } idx.Set("/tmp/test-"+string(rune('a'+i))+".yaml", metadata) } t.Run("Custom filter function", func(t *testing.T) { results := idx.FilterFunc(func(m *Metadata) bool { return m.HasTag("even") }) require.Len(t, results, 3) // 0, 2, 4 }) t.Run("Nil filter function returns all", func(t *testing.T) { results := idx.FilterFunc(nil) require.Len(t, results, 5) }) } func TestFilterString(t *testing.T) { filter := &Filter{ Authors: []string{"author1", "author2"}, Tags: []string{"tag1"}, Severities: []severity.Severity{severity.Critical, severity.High}, ProtocolTypes: []types.ProtocolType{types.HTTPProtocol}, } str := filter.String() require.Contains(t, str, "authors=") require.Contains(t, str, "tags=") require.Contains(t, str, "severities=") require.Contains(t, str, "types=") emptyFilter := &Filter{} require.Equal(t, "filter=", emptyFilter.String()) } ================================================ FILE: pkg/catalog/index/index.go ================================================ package index import ( "encoding/gob" "maps" "os" "path/filepath" "sync" "github.com/maypok86/otter/v2" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/templates" folderutil "github.com/projectdiscovery/utils/folder" ) const ( // IndexFileName is the name of the persistent cache file. IndexFileName = "index.gob" // IndexVersion is the schema version for cache invalidation on breaking // changes. IndexVersion = 1 // DefaultMaxSize is the default maximum number of templates to cache. DefaultMaxSize = 50000 // DefaultMaxWeight is the default maximum weight of the cache. DefaultMaxWeight = DefaultMaxSize * 800 // ~40MB assuming ~800B/entry ) // Index represents a cache for template metadata. type Index struct { cache *otter.Cache[string, *Metadata] cacheFile string mu sync.RWMutex version int } // cacheSnapshot represents the serialized cache structure. type cacheSnapshot struct { Version int `gob:"version"` Data map[string]*Metadata `gob:"data"` } // NewIndex creates a new template metadata cache with the given options. func NewIndex(cacheDir string) (*Index, error) { if cacheDir == "" { cacheDir = folderutil.AppCacheDirOrDefault(".nuclei-cache", config.BinaryName) } if err := os.MkdirAll(cacheDir, 0755); err != nil { return nil, err } cacheFile := filepath.Join(cacheDir, IndexFileName) // NOTE(dwisiswant0): Build cache with adaptive sizing based on memory cost. opts := &otter.Options[string, *Metadata]{ MaximumWeight: uint64(DefaultMaxWeight), Weigher: func(key string, value *Metadata) uint32 { if value == nil { return uint32(len(key)) } weight := len(key) weight += len(value.ID) weight += len(value.FilePath) weight += 24 // ModTime is time.Time (24B) weight += len(value.Name) weight += len(value.Severity) weight += len(value.ProtocolType) weight += len(value.TemplateVerifier) for _, author := range value.Authors { weight += len(author) } for _, tag := range value.Tags { weight += len(tag) } return uint32(weight) }, } cache, err := otter.New(opts) if err != nil { return nil, err } c := &Index{ cache: cache, cacheFile: cacheFile, version: IndexVersion, } return c, nil } // NewDefaultIndex creates a index with default settings in the default cache // directory. func NewDefaultIndex() (*Index, error) { return NewIndex("") } // Get retrieves metadata for a template path, validating freshness via mtime. func (i *Index) Get(path string) (*Metadata, bool) { i.mu.RLock() defer i.mu.RUnlock() metadata, found := i.cache.GetIfPresent(path) if !found { return nil, false } if !metadata.IsValid() { go i.Delete(path) return nil, false } return metadata, true } // Set stores metadata for a template path. // // The caller is responsible for ensuring the metadata is valid and contains // the correct checksum before calling this method. // Use [SetFromTemplate] for automatic extraction and checksum computation. // // Returns the metadata and whether it was successfully cached (false if evicted). func (i *Index) Set(path string, metadata *Metadata) (*Metadata, bool) { i.mu.Lock() defer i.mu.Unlock() return i.cache.Set(path, metadata) } // SetFromTemplate extracts metadata from a parsed template and stores it. // // Returns the metadata and whether it was successfully cached. The metadata is // always returned (even on checksum failure) for immediate filtering use. // Returns false if the metadata was not cached (e.g., set, evicted). func (i *Index) SetFromTemplate(path string, tpl *templates.Template) (*Metadata, bool) { metadata := NewMetadataFromTemplate(path, tpl) info, err := os.Stat(path) if err != nil { return metadata, false } metadata.ModTime = info.ModTime() if i.cache == nil { return metadata, false } return i.Set(path, metadata) } // Has checks if metadata exists for a path without validation. func (i *Index) Has(path string) bool { i.mu.RLock() defer i.mu.RUnlock() _, found := i.cache.GetIfPresent(path) return found } // Delete removes metadata for a path. func (i *Index) Delete(path string) { i.mu.Lock() defer i.mu.Unlock() i.cache.Invalidate(path) } // Size returns the number of cached entries. func (i *Index) Size() int { i.mu.RLock() defer i.mu.RUnlock() return i.cache.EstimatedSize() } // Clear removes all cached entries. func (i *Index) Clear() { i.mu.Lock() defer i.mu.Unlock() i.cache.InvalidateAll() } // Save persists the cache to disk using gob encoding. func (i *Index) Save() error { i.mu.RLock() defer i.mu.RUnlock() snapshot := &cacheSnapshot{ Version: i.version, Data: make(map[string]*Metadata), } maps.Insert(snapshot.Data, i.cache.All()) // NOTE(dwisiswant0): write to temp for atomic op. tmpFile := i.cacheFile + ".tmp" file, err := os.Create(tmpFile) if err != nil { return err } encoder := gob.NewEncoder(file) if err := encoder.Encode(snapshot); err != nil { _ = file.Close() _ = os.Remove(tmpFile) return err } if err := file.Close(); err != nil { _ = os.Remove(tmpFile) return err } if err := os.Rename(tmpFile, i.cacheFile); err != nil { _ = os.Remove(tmpFile) return err } return nil } // Load loads the cache from disk using gob decoding. func (i *Index) Load() error { file, err := os.Open(i.cacheFile) if err != nil { if os.IsNotExist(err) { return nil } return err } defer func() { _ = file.Close() }() var snapshot cacheSnapshot decoder := gob.NewDecoder(file) if err := decoder.Decode(&snapshot); err != nil { _ = file.Close() _ = os.Remove(i.cacheFile) return nil } if snapshot.Version != i.version { _ = file.Close() _ = os.Remove(i.cacheFile) return nil } i.mu.Lock() defer i.mu.Unlock() for key, value := range snapshot.Data { i.cache.Set(key, value) } return nil } // Filter returns all template paths that match the given filter criteria. func (i *Index) Filter(filter *Filter) []string { if filter == nil || filter.IsEmpty() { return i.All() } i.mu.RLock() defer i.mu.RUnlock() var matched []string for path, metadata := range i.cache.All() { if filter.Matches(metadata) { matched = append(matched, path) } } return matched } // FilterFunc returns all template paths that match the given filter function. func (i *Index) FilterFunc(fn FilterFunc) []string { if fn == nil { return i.All() } i.mu.RLock() defer i.mu.RUnlock() var matched []string for path, metadata := range i.cache.All() { if fn(metadata) { matched = append(matched, path) } } return matched } // All returns all template paths in the index. func (i *Index) All() []string { i.mu.RLock() defer i.mu.RUnlock() paths := make([]string, 0, i.cache.EstimatedSize()) for path := range i.cache.All() { paths = append(paths, path) } return paths } // GetAll returns all metadata entries in the index. func (i *Index) GetAll() map[string]*Metadata { i.mu.RLock() defer i.mu.RUnlock() result := maps.Collect(i.cache.All()) return result } // Count returns the number of templates matching the filter. func (i *Index) Count(filter *Filter) int { if filter == nil || filter.IsEmpty() { return i.Size() } i.mu.RLock() defer i.mu.RUnlock() count := 0 for _, metadata := range i.cache.All() { if filter.Matches(metadata) { count++ } } return count } ================================================ FILE: pkg/catalog/index/index_test.go ================================================ package index import ( "fmt" "os" "path/filepath" "testing" "time" "github.com/projectdiscovery/nuclei/v3/pkg/model" "github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity" "github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/code" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/http" "github.com/projectdiscovery/nuclei/v3/pkg/templates" "github.com/projectdiscovery/nuclei/v3/pkg/templates/types" "github.com/stretchr/testify/require" ) func TestNewIndex(t *testing.T) { t.Run("with custom directory", func(t *testing.T) { tmpDir := t.TempDir() cache, err := NewIndex(tmpDir) require.NoError(t, err, "Failed to create cache with custom directory") require.NotNil(t, cache, "Cache should not be nil") require.Equal(t, filepath.Join(tmpDir, IndexFileName), cache.cacheFile) require.Equal(t, IndexVersion, cache.version) }) t.Run("with default directory", func(t *testing.T) { cache, err := NewDefaultIndex() require.NoError(t, err, "Failed to create cache with default directory") require.NotNil(t, cache, "Cache should not be nil") }) } func TestCacheBasicOperations(t *testing.T) { tmpDir := t.TempDir() cache, err := NewIndex(tmpDir) require.NoError(t, err) metadata := &Metadata{ ID: "concurrent-test", FilePath: "/tmp/concurrent.yaml", } t.Run("Set and Has", func(t *testing.T) { cache.Set(metadata.FilePath, metadata) require.Equal(t, 1, cache.Size(), "Cache size should be 1 after Set") require.True(t, cache.Has(metadata.FilePath), "Cache should contain the path after Set") require.False(t, cache.Has("/nonexistent"), "Cache should not contain nonexistent path") }) t.Run("Get with validation", func(t *testing.T) { // Get should fail validation for nonexistent file retrieved, found := cache.Get(metadata.FilePath) require.False(t, found, "Get should fail validation for nonexistent file") require.Nil(t, retrieved, "Retrieved metadata should be nil for invalid entry") }) t.Run("Delete", func(t *testing.T) { cache.Set(metadata.FilePath, metadata) require.True(t, cache.Has(metadata.FilePath), "Cache should contain path before Delete") cache.Delete(metadata.FilePath) require.False(t, cache.Has(metadata.FilePath), "Cache should not contain path after Delete") }) t.Run("Clear", func(t *testing.T) { cache.Set(metadata.FilePath, metadata) cache.Set("/tmp/test2.yaml", &Metadata{ID: "test2", FilePath: "/tmp/test2.yaml"}) require.True(t, cache.Size() > 0, "Cache should have entries before Clear") cache.Clear() require.Equal(t, 0, cache.Size(), "Cache should be empty after Clear") }) } func TestCachePersistence(t *testing.T) { tmpDir := t.TempDir() metadata1 := &Metadata{ ID: "persist-test-1", FilePath: "/tmp/persist1.yaml", Name: "Persistence Test 1", Authors: []string{"tester"}, Tags: []string{"test"}, Severity: "medium", ProtocolType: "dns", } metadata2 := &Metadata{ ID: "persist-test-2", FilePath: "/tmp/persist2.yaml", Name: "Persistence Test 2", Authors: []string{"tester2"}, Tags: []string{"cve"}, Severity: "critical", ProtocolType: "http", } t.Run("Save and Load", func(t *testing.T) { // Create cache and add entries cache1, err := NewIndex(tmpDir) require.NoError(t, err) cache1.Set(metadata1.FilePath, metadata1) cache1.Set(metadata2.FilePath, metadata2) require.Equal(t, 2, cache1.Size()) // Save to disk err = cache1.Save() require.NoError(t, err, "Failed to save cache") // Verify cache file exists cacheFile := filepath.Join(tmpDir, IndexFileName) stat, err := os.Stat(cacheFile) require.NoError(t, err, "Cache file should exist") require.Greater(t, stat.Size(), int64(0), "Cache file should not be empty") // Create new cache and load cache2, err := NewIndex(tmpDir) require.NoError(t, err) require.Equal(t, 0, cache2.Size(), "New cache should be empty before Load") err = cache2.Load() require.NoError(t, err, "Failed to load cache") // Verify data was loaded require.Equal(t, 2, cache2.Size(), "Loaded cache should have 2 entries") require.True(t, cache2.Has(metadata1.FilePath), "Loaded cache should contain first entry") require.True(t, cache2.Has(metadata2.FilePath), "Loaded cache should contain second entry") }) t.Run("Load non-existent cache", func(t *testing.T) { emptyDir := t.TempDir() cache, err := NewIndex(emptyDir) require.NoError(t, err) // Loading non-existent cache should not error err = cache.Load() require.NoError(t, err, "Loading non-existent cache should not error") require.Equal(t, 0, cache.Size(), "Cache should be empty after loading non-existent file") }) t.Run("Atomic save", func(t *testing.T) { cache, err := NewIndex(tmpDir) require.NoError(t, err) cache.Set(metadata1.FilePath, metadata1) err = cache.Save() require.NoError(t, err) // Verify no .tmp file left behind tmpFile := filepath.Join(tmpDir, IndexFileName+".tmp") _, err = os.Stat(tmpFile) require.True(t, os.IsNotExist(err), "Temporary file should not exist after save") // Verify actual cache file exists cacheFile := filepath.Join(tmpDir, IndexFileName) _, err = os.Stat(cacheFile) require.NoError(t, err, "Cache file should exist") }) } func TestIndexVersionMismatch(t *testing.T) { tmpDir := t.TempDir() // Create cache with current version cache1, err := NewIndex(tmpDir) require.NoError(t, err) metadata := &Metadata{ ID: "version-test", FilePath: "/tmp/version.yaml", } cache1.Set(metadata.FilePath, metadata) // Save with current version err = cache1.Save() require.NoError(t, err) // Manually modify version and save again cache1.version = 999 err = cache1.Save() require.NoError(t, err) // Try to load with different version cache2, err := NewIndex(tmpDir) require.NoError(t, err) // Load should succeed but cache should be empty (version mismatch) err = cache2.Load() require.NoError(t, err, "Load should not error on version mismatch") require.Equal(t, 0, cache2.Size(), "Cache should be empty after version mismatch") } func TestCacheCorruptedFile(t *testing.T) { tmpDir := t.TempDir() cacheFile := filepath.Join(tmpDir, IndexFileName) // Create corrupted cache file err := os.WriteFile(cacheFile, []byte("corrupted data that is not valid gob"), 0644) require.NoError(t, err) // Try to load corrupted cache cache, err := NewIndex(tmpDir) require.NoError(t, err) err = cache.Load() require.NoError(t, err, "Load should not error on corrupted cache") require.Equal(t, 0, cache.Size(), "Cache should be empty after loading corrupted file") // Corrupted file should be removed _, err = os.Stat(cacheFile) require.True(t, os.IsNotExist(err), "Corrupted cache file should be removed") } func TestMetadataValidation(t *testing.T) { tmpDir := t.TempDir() tmpFile := filepath.Join(tmpDir, "test.yaml") t.Run("Valid metadata", func(t *testing.T) { // Create a test file err := os.WriteFile(tmpFile, []byte("id: test\ninfo:\n name: Test"), 0644) require.NoError(t, err) info, err := os.Stat(tmpFile) require.NoError(t, err) // Create metadata with correct checksum metadata := &Metadata{ ID: "test", FilePath: tmpFile, ModTime: info.ModTime(), } // Should be valid require.True(t, metadata.IsValid(), "Metadata should be valid for unchanged file") }) t.Run("Invalid metadata after file modification", func(t *testing.T) { // Create the test file first to ensure it exists in this subtest err := os.WriteFile(tmpFile, []byte("id: test\ninfo:\n name: Test"), 0644) require.NoError(t, err) // Set file ModTime to past to ensure modification is detectable oldTime := time.Now().Add(-2 * time.Second) err = os.Chtimes(tmpFile, oldTime, oldTime) require.NoError(t, err) info, err := os.Stat(tmpFile) require.NoError(t, err) metadata := &Metadata{ ID: "test", FilePath: tmpFile, ModTime: info.ModTime(), } // Modify file err = os.WriteFile(tmpFile, []byte("id: test\ninfo:\n name: Modified"), 0644) require.NoError(t, err) // Should now be invalid require.False(t, metadata.IsValid(), "Metadata should be invalid after file modification") }) t.Run("Invalid metadata for deleted file", func(t *testing.T) { // Create the test file first to ensure it exists in this subtest err := os.WriteFile(tmpFile, []byte("id: test\ninfo:\n name: Test"), 0644) require.NoError(t, err) info, err := os.Stat(tmpFile) require.NoError(t, err) metadata := &Metadata{ ID: "test", FilePath: tmpFile, ModTime: info.ModTime(), } // Delete file err = os.Remove(tmpFile) require.NoError(t, err) // Should be invalid require.False(t, metadata.IsValid(), "Metadata should be invalid for deleted file") }) } func TestSetFromTemplate(t *testing.T) { tmpDir := t.TempDir() tmpFile := filepath.Join(tmpDir, "extract.yaml") // Create a test file err := os.WriteFile(tmpFile, []byte("id: extract-test"), 0644) require.NoError(t, err) cache, err := NewIndex(tmpDir) require.NoError(t, err) t.Run("Basic metadata extraction", func(t *testing.T) { template := &templates.Template{ ID: "extract-test", Info: model.Info{ Name: "Extract Test Template", Authors: stringslice.StringSlice{Value: "author1,author2"}, Tags: stringslice.StringSlice{Value: "tag1,tag2"}, Description: "Test description", SeverityHolder: severity.Holder{ Severity: severity.High, }, }, SelfContained: true, Verified: true, TemplateVerifier: "test-verifier", } metadata, ok := cache.SetFromTemplate(tmpFile, template) require.True(t, ok, "Failed to set metadata from template") require.NotNil(t, metadata, "Metadata should not be nil") // Verify core fields require.Equal(t, "extract-test", metadata.ID) require.Equal(t, tmpFile, metadata.FilePath) // Verify Info fields require.Equal(t, "Extract Test Template", metadata.Name) require.Equal(t, []string{"author1,author2"}, metadata.Authors) require.Equal(t, []string{"tag1,tag2"}, metadata.Tags) require.Equal(t, "high", metadata.Severity) // Verify flags require.True(t, metadata.Verified) require.Equal(t, "test-verifier", metadata.TemplateVerifier) }) t.Run("HTTP protocol detection", func(t *testing.T) { // Create a separate test file for this test httpFile := filepath.Join(tmpDir, "http-test.yaml") err := os.WriteFile(httpFile, []byte("id: http-test"), 0644) require.NoError(t, err) template := &templates.Template{ ID: "http-test", Info: model.Info{ Name: "HTTP Test", Authors: stringslice.StringSlice{Value: "tester"}, SeverityHolder: severity.Holder{ Severity: severity.Medium, }, }, RequestsHTTP: []*http.Request{{Method: http.HTTPMethodTypeHolder{MethodType: http.HTTPGet}}}, } metadata, ok := cache.SetFromTemplate(httpFile, template) require.True(t, ok) require.NotNil(t, metadata) require.Equal(t, "http", metadata.ProtocolType) }) t.Run("Extract with missing file", func(t *testing.T) { template := &templates.Template{ ID: "missing-test", Info: model.Info{ Name: "Missing File Test", Authors: stringslice.StringSlice{Value: "tester"}, SeverityHolder: severity.Holder{ Severity: severity.Low, }, }, } metadata, ok := cache.SetFromTemplate("/nonexistent/file.yaml", template) require.False(t, ok, "Should return false for nonexistent file") require.NotNil(t, metadata, "Metadata should still be returned") }) } func TestMetadataMatchingHelpers(t *testing.T) { metadata := &Metadata{ Tags: []string{"cve", "rce", "apache"}, Authors: []string{"pdteam", "geeknik"}, Severity: "critical", ProtocolType: "http", } t.Run("HasTag", func(t *testing.T) { require.True(t, metadata.HasTag("cve")) require.True(t, metadata.HasTag("rce")) require.True(t, metadata.HasTag("apache")) require.False(t, metadata.HasTag("xxe")) require.False(t, metadata.HasTag("")) }) t.Run("HasAuthor", func(t *testing.T) { require.True(t, metadata.HasAuthor("pdteam")) require.True(t, metadata.HasAuthor("geeknik")) require.False(t, metadata.HasAuthor("unknown")) require.False(t, metadata.HasAuthor("")) }) t.Run("MatchesSeverity", func(t *testing.T) { require.True(t, metadata.MatchesSeverity(severity.Critical)) require.False(t, metadata.MatchesSeverity(severity.High)) require.False(t, metadata.MatchesSeverity(severity.Medium)) require.False(t, metadata.MatchesSeverity(severity.Low)) require.False(t, metadata.MatchesSeverity(severity.Info)) }) t.Run("MatchesProtocol", func(t *testing.T) { require.True(t, metadata.MatchesProtocol(types.HTTPProtocol)) require.False(t, metadata.MatchesProtocol(types.DNSProtocol)) require.False(t, metadata.MatchesProtocol(types.FileProtocol)) require.False(t, metadata.MatchesProtocol(types.NetworkProtocol)) }) t.Run("Empty metadata", func(t *testing.T) { emptyMetadata := &Metadata{} require.False(t, emptyMetadata.HasTag("any")) require.False(t, emptyMetadata.HasAuthor("any")) }) } func TestCacheConcurrency(t *testing.T) { tmpDir := t.TempDir() cache, err := NewIndex(tmpDir) require.NoError(t, err) // Test concurrent writes t.Run("Concurrent Set", func(t *testing.T) { done := make(chan bool) for i := 0; i < 10; i++ { go func(id int) { metadata := &Metadata{ ID: string(rune('a' + id)), FilePath: filepath.Join("/tmp", string(rune('a'+id))+".yaml"), } cache.Set(metadata.FilePath, metadata) done <- true }(i) } // Wait for all goroutines for i := 0; i < 10; i++ { <-done } require.Equal(t, 10, cache.Size(), "All concurrent writes should succeed") }) // Test concurrent reads t.Run("Concurrent Has", func(t *testing.T) { metadata := &Metadata{ ID: "concurrent-test", FilePath: "/tmp/concurrent.yaml", } cache.Set(metadata.FilePath, metadata) done := make(chan bool) for i := 0; i < 20; i++ { go func() { _ = cache.Has(metadata.FilePath) done <- true }() } // Wait for all goroutines for i := 0; i < 20; i++ { <-done } }) } func TestCacheSize(t *testing.T) { tmpDir := t.TempDir() cache, err := NewIndex(tmpDir) require.NoError(t, err) require.Equal(t, 0, cache.Size(), "New cache should have size 0") // Add entries for i := 0; i < 5; i++ { metadata := &Metadata{ ID: string(rune('a' + i)), FilePath: filepath.Join("/tmp", string(rune('a'+i))+".yaml"), } cache.Set(metadata.FilePath, metadata) } require.Equal(t, 5, cache.Size(), "Cache should have size 5 after adding 5 entries") // Delete entries cache.Delete(filepath.Join("/tmp", "a.yaml")) cache.Delete(filepath.Join("/tmp", "b.yaml")) require.Equal(t, 3, cache.Size(), "Cache should have size 3 after deleting 2 entries") // Clear cache cache.Clear() require.Equal(t, 0, cache.Size(), "Cache should have size 0 after Clear") } func TestCacheGetWithValidFile(t *testing.T) { tmpDir := t.TempDir() cache, err := NewIndex(tmpDir) require.NoError(t, err) // Create a real file for testing validation tmpFile := filepath.Join(tmpDir, "test.yaml") err = os.WriteFile(tmpFile, []byte("id: test"), 0644) require.NoError(t, err) info, err := os.Stat(tmpFile) require.NoError(t, err) metadata := &Metadata{ ID: "test", FilePath: tmpFile, ModTime: info.ModTime(), Name: "Test Template", } // Set and get should work with valid file cache.Set(metadata.FilePath, metadata) retrieved, found := cache.Get(metadata.FilePath) require.True(t, found, "Should find entry with valid file") require.NotNil(t, retrieved, "Retrieved metadata should not be nil") require.Equal(t, metadata.ID, retrieved.ID) } func TestCacheSaveErrorHandling(t *testing.T) { tmpDir := t.TempDir() cache, err := NewIndex(tmpDir) require.NoError(t, err) metadata := &Metadata{ ID: "test", FilePath: filepath.Join("/tmp", "test.yaml"), } cache.Set(metadata.FilePath, metadata) // Create a directory where the temp file would be created to force an error // The Save method creates a file at cacheFile + ".tmp" conflictPath := filepath.Join(tmpDir, IndexFileName+".tmp") err = os.Mkdir(conflictPath, 0755) require.NoError(t, err) err = cache.Save() require.Error(t, err, "Save should fail when temp file cannot be created") } func TestNewCacheWithInvalidDirectory(t *testing.T) { // Try to create cache in a file path (should fail) tmpFile := filepath.Join(t.TempDir(), "file.txt") err := os.WriteFile(tmpFile, []byte("test"), 0644) require.NoError(t, err) cache, err := NewIndex(tmpFile) require.Error(t, err, "NewCache should fail when path is a file") require.Nil(t, cache, "Cache should be nil on error") } func TestCacheLoadCorruptedRemoval(t *testing.T) { tmpDir := t.TempDir() cacheFile := filepath.Join(tmpDir, IndexFileName) // Create corrupted cache file with invalid gob data err := os.WriteFile(cacheFile, []byte("this is not valid gob encoding at all!"), 0644) require.NoError(t, err) // Verify file exists before Load _, err = os.Stat(cacheFile) require.NoError(t, err, "Corrupted file should exist") // Load should not error but should remove corrupted file cache, err := NewIndex(tmpDir) require.NoError(t, err) err = cache.Load() require.NoError(t, err, "Load should not return error for corrupted file") // Verify corrupted file was removed _, err = os.Stat(cacheFile) require.True(t, os.IsNotExist(err), "Corrupted file should be removed") require.Equal(t, 0, cache.Size(), "Cache should be empty after loading corrupted file") } func TestMetadataExtractionWithNilClassification(t *testing.T) { tmpDir := t.TempDir() tmpFile := filepath.Join(tmpDir, "test.yaml") err := os.WriteFile(tmpFile, []byte("id: test"), 0644) require.NoError(t, err) template := &templates.Template{ ID: "nil-classification", Info: model.Info{ Name: "Template without classification", Authors: stringslice.StringSlice{Value: "tester"}, SeverityHolder: severity.Holder{ Severity: severity.Medium, }, Classification: nil, // Explicitly nil }, } cache, err := NewIndex(tmpDir) require.NoError(t, err) metadata, ok := cache.SetFromTemplate(tmpFile, template) require.True(t, ok) require.NotNil(t, metadata) } func TestCachePersistenceWithLargeDataset(t *testing.T) { tmpDir := t.TempDir() cache, err := NewIndex(tmpDir) require.NoError(t, err) // Add 100 entries to test bulk operations for i := 0; i < 100; i++ { metadata := &Metadata{ ID: fmt.Sprintf("template-%d", i), FilePath: filepath.Join("/tmp", fmt.Sprintf("template-%d.yaml", i)), Name: fmt.Sprintf("Template %d", i), Authors: []string{fmt.Sprintf("author%d", i)}, Tags: []string{"tag1", "tag2", "tag3"}, Severity: "high", } cache.Set(metadata.FilePath, metadata) } require.Equal(t, 100, cache.Size(), "Cache should contain 100 entries") // Save to disk err = cache.Save() require.NoError(t, err) // Load into new cache cache2, err := NewIndex(tmpDir) require.NoError(t, err) err = cache2.Load() require.NoError(t, err) require.Equal(t, 100, cache2.Size(), "Loaded cache should contain 100 entries") // Verify a sample entry found := cache2.Has(filepath.Join("/tmp", "template-50.yaml")) require.True(t, found, "Should find sample entry") } func TestMetadataHelperMethods(t *testing.T) { metadata := &Metadata{ ID: "helper-test", Tags: []string{}, Authors: []string{}, Severity: "", ProtocolType: "", } t.Run("Empty tags", func(t *testing.T) { require.False(t, metadata.HasTag("anytag")) }) t.Run("Empty authors", func(t *testing.T) { require.False(t, metadata.HasAuthor("anyauthor")) }) t.Run("Empty severity", func(t *testing.T) { require.False(t, metadata.MatchesSeverity(severity.Critical)) }) t.Run("Empty protocol", func(t *testing.T) { require.False(t, metadata.MatchesProtocol(types.HTTPProtocol)) }) } func TestMultipleProtocolsDetection(t *testing.T) { tmpDir := t.TempDir() tmpFile := filepath.Join(tmpDir, "multi.yaml") err := os.WriteFile(tmpFile, []byte("id: multi"), 0644) require.NoError(t, err) // Template with multiple protocol types template := &templates.Template{ ID: "multi-protocol", Info: model.Info{ Name: "Multi Protocol Template", Authors: stringslice.StringSlice{Value: "tester"}, SeverityHolder: severity.Holder{ Severity: severity.High, }, }, RequestsHTTP: []*http.Request{{Method: http.HTTPMethodTypeHolder{MethodType: http.HTTPGet}}}, RequestsHeadless: []*headless.Request{{}}, RequestsCode: []*code.Request{{}}, } cache, err := NewIndex(tmpDir) require.NoError(t, err) metadata, ok := cache.SetFromTemplate(tmpFile, template) require.True(t, ok) require.NotNil(t, metadata) require.Equal(t, "http", metadata.ProtocolType, "Primary protocol should be http") } func TestNewMetadataFromTemplate(t *testing.T) { tmpl := &templates.Template{ ID: "test-template", Info: model.Info{ Name: "Test Template", Authors: stringslice.StringSlice{Value: []string{"author"}}, Tags: stringslice.StringSlice{Value: []string{"tag"}}, SeverityHolder: severity.Holder{ Severity: severity.Low, }, }, Verified: true, TemplateVerifier: "verifier", } path := "/tmp/test.yaml" metadata := NewMetadataFromTemplate(path, tmpl) require.Equal(t, tmpl.ID, metadata.ID) require.Equal(t, path, metadata.FilePath) require.Equal(t, tmpl.Info.Name, metadata.Name) require.Equal(t, tmpl.Info.Authors.ToSlice(), metadata.Authors) require.Equal(t, tmpl.Info.Tags.ToSlice(), metadata.Tags) require.Equal(t, tmpl.Info.SeverityHolder.Severity.String(), metadata.Severity) require.Equal(t, tmpl.Type().String(), metadata.ProtocolType) require.Equal(t, tmpl.Verified, metadata.Verified) require.Equal(t, tmpl.TemplateVerifier, metadata.TemplateVerifier) } ================================================ FILE: pkg/catalog/index/metadata.go ================================================ package index import ( "os" "slices" "time" "github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity" "github.com/projectdiscovery/nuclei/v3/pkg/templates" "github.com/projectdiscovery/nuclei/v3/pkg/templates/types" ) // Metadata contains lightweight metadata extracted from a template. type Metadata struct { // ID is the unique identifier of the template. ID string `gob:"id"` // FilePath is the path to the template file. FilePath string `gob:"file_path"` // ModTime is the modification time of the template file. ModTime time.Time `gob:"mod_time"` // Name is the name of the template. Name string `gob:"name"` // Authors are the authors of the template. Authors []string `gob:"authors"` // Tags are the tags associated with the template. Tags []string `gob:"tags"` // Severity is the severity level of the template. Severity string `gob:"severity"` // ProtocolType is the primary protocol type of the template. ProtocolType string `gob:"protocol_type"` // Verified indicates whether the template is verified. Verified bool `gob:"verified"` // TemplateVerifier is the verifier used for the template. TemplateVerifier string `gob:"verifier,omitempty"` // NOTE(dwisiswant0): Consider adding more fields here in the future to // enhance filtering caps w/o loading full templates, such as: // `has_{code,headless,file}` to indicate presence of protocol-based // requests, and/or classification fields (CVE, CWE, CVSS, EPSS), if needed. // // For maintainers: when adding new fields, don't forget to update the // Weigher logic in [NewIndex] to account for the new fields in cache weight // calculation, because it affects cache eviction behavior. Also, consider // the impact on existing cached data and whether a [IndexVersion] bump is // needed. } // NewMetadataFromTemplate creates a new metadata object from a template. func NewMetadataFromTemplate(path string, tpl *templates.Template) *Metadata { return &Metadata{ ID: tpl.ID, FilePath: path, Name: tpl.Info.Name, Authors: tpl.Info.Authors.ToSlice(), Tags: tpl.Info.Tags.ToSlice(), Severity: tpl.Info.SeverityHolder.Severity.String(), ProtocolType: tpl.Type().String(), Verified: tpl.Verified, TemplateVerifier: tpl.TemplateVerifier, } } // IsValid checks if the cached metadata is still valid by comparing the file // modification time. func (m *Metadata) IsValid() bool { info, err := os.Stat(m.FilePath) if err != nil { return false } return m.ModTime.Equal(info.ModTime()) } // MatchesSeverity checks if the metadata matches the given severity. func (m *Metadata) MatchesSeverity(sev severity.Severity) bool { return m.Severity == sev.String() } // MatchesProtocol checks if the metadata matches the given protocol type. func (m *Metadata) MatchesProtocol(protocolType types.ProtocolType) bool { return m.ProtocolType == protocolType.String() } // HasTag checks if the metadata contains the given tag. func (m *Metadata) HasTag(tag string) bool { return slices.Contains(m.Tags, tag) } // HasAuthor checks if the metadata contains the given author. func (m *Metadata) HasAuthor(author string) bool { return slices.Contains(m.Authors, author) } ================================================ FILE: pkg/catalog/loader/ai_loader.go ================================================ package loader import ( "bytes" "encoding/json" "io" "net/http" "os" "path/filepath" "strings" "github.com/alecthomas/chroma/quick" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/retryablehttp-go" pdcpauth "github.com/projectdiscovery/utils/auth/pdcp" "github.com/projectdiscovery/utils/errkit" ) const ( aiTemplateGeneratorAPIEndpoint = "https://api.projectdiscovery.io/v1/template/ai" ) type AITemplateResponse struct { CanRun bool `json:"canRun"` Comment string `json:"comment"` Completion string `json:"completion"` Message string `json:"message"` Name string `json:"name"` TemplateID string `json:"template_id"` } func getAIGeneratedTemplates(prompt string, options *types.Options) ([]string, error) { prompt = strings.TrimSpace(prompt) if len(prompt) < 5 { return nil, errkit.Newf("Prompt is too short. Please provide a more descriptive prompt") } if len(prompt) > 3000 { return nil, errkit.Newf("Prompt is too long. Please limit to 3000 characters") } template, templateID, err := generateAITemplate(prompt) if err != nil { return nil, errkit.Newf("Failed to generate template: %v", err) } pdcpTemplateDir := filepath.Join(config.DefaultConfig.GetTemplateDir(), "pdcp") if err := os.MkdirAll(pdcpTemplateDir, 0755); err != nil { return nil, errkit.Newf("Failed to create pdcp template directory: %v", err) } templateFile := filepath.Join(pdcpTemplateDir, templateID+".yaml") err = os.WriteFile(templateFile, []byte(template), 0644) if err != nil { return nil, errkit.Newf("Failed to generate template: %v", err) } options.Logger.Info().Msgf("Generated template available at: https://cloud.projectdiscovery.io/templates/%s", templateID) options.Logger.Info().Msgf("Generated template path: %s", templateFile) // Check if we should display the template // This happens when: // 1. No targets are provided (-target/-list) // 2. No stdin input is being used hasNoTargets := len(options.Targets) == 0 && options.TargetsFilePath == "" hasNoStdin := !options.Stdin if hasNoTargets && hasNoStdin { // Display the template content with syntax highlighting if !options.NoColor { var buf bytes.Buffer err = quick.Highlight(&buf, template, "yaml", "terminal16m", "monokai") if err == nil { template = buf.String() } } options.Logger.Debug().Msgf("\n%s", template) // FIXME: // we should not be exiting the program here // but we need to find a better way to handle this os.Exit(0) } return []string{templateFile}, nil } func generateAITemplate(prompt string) (string, string, error) { reqBody := map[string]string{ "prompt": prompt, } jsonBody, err := json.Marshal(reqBody) if err != nil { return "", "", errkit.Newf("Failed to marshal request body: %v", err) } req, err := http.NewRequest(http.MethodPost, aiTemplateGeneratorAPIEndpoint, bytes.NewBuffer(jsonBody)) if err != nil { return "", "", errkit.Newf("Failed to create HTTP request: %v", err) } ph := pdcpauth.PDCPCredHandler{} creds, err := ph.GetCreds() if err != nil { return "", "", errkit.Newf("Failed to get PDCP credentials: %v", err) } if creds == nil { return "", "", errkit.Newf("PDCP API Key not configured, Create one for free at https://cloud.projectdiscovery.io/") } req.Header.Set("Content-Type", "application/json") req.Header.Set(pdcpauth.ApiKeyHeaderName, creds.APIKey) resp, err := retryablehttp.DefaultClient().Do(req) if err != nil { return "", "", errkit.Newf("Failed to send HTTP request: %v", err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode == http.StatusUnauthorized { return "", "", errkit.Newf("Invalid API Key or API Key not configured, Create one for free at https://cloud.projectdiscovery.io/") } if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return "", "", errkit.Newf("API returned status code %d: %s", resp.StatusCode, string(body)) } var result AITemplateResponse if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { return "", "", errkit.Newf("Failed to decode API response: %v", err) } if result.TemplateID == "" || result.Completion == "" { return "", "", errkit.Newf("Failed to generate template") } return result.Completion, result.TemplateID, nil } ================================================ FILE: pkg/catalog/loader/filter/path_filter.go ================================================ package filter import ( "github.com/projectdiscovery/nuclei/v3/pkg/catalog" ) // PathFilter is a path based template filter type PathFilter struct { excludedTemplates []string alwaysIncludedTemplatesMap map[string]struct{} } // PathFilterConfig contains configuration options for Path based templates Filter type PathFilterConfig struct { IncludedTemplates []string ExcludedTemplates []string } // NewPathFilter creates a new path filter from provided config func NewPathFilter(config *PathFilterConfig, catalogClient catalog.Catalog) *PathFilter { paths, _ := catalogClient.GetTemplatesPath(config.ExcludedTemplates) filter := &PathFilter{ excludedTemplates: paths, alwaysIncludedTemplatesMap: make(map[string]struct{}), } alwaysIncludeTemplates, _ := catalogClient.GetTemplatesPath(config.IncludedTemplates) for _, tpl := range alwaysIncludeTemplates { filter.alwaysIncludedTemplatesMap[tpl] = struct{}{} } return filter } // Match performs match for path filter on templates and returns final list func (p *PathFilter) Match(templates []string) map[string]struct{} { templatesMap := make(map[string]struct{}) for _, tpl := range templates { templatesMap[tpl] = struct{}{} } for _, template := range p.excludedTemplates { if _, ok := p.alwaysIncludedTemplatesMap[template]; ok { continue } else { delete(templatesMap, template) } } return templatesMap } // MatchIncluded returns true if the template was included explicitly func (p *PathFilter) MatchIncluded(template string) bool { _, found := p.alwaysIncludedTemplatesMap[template] return found } ================================================ FILE: pkg/catalog/loader/loader.go ================================================ package loader import ( "fmt" "io" "net/url" "os" "sort" "strings" "sync" "github.com/logrusorgru/aurora" "github.com/pkg/errors" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/catalog" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/index" "github.com/projectdiscovery/nuclei/v3/pkg/keys" "github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity" "github.com/projectdiscovery/nuclei/v3/pkg/protocols" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate" "github.com/projectdiscovery/nuclei/v3/pkg/templates" templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/nuclei/v3/pkg/utils/stats" "github.com/projectdiscovery/nuclei/v3/pkg/workflows" "github.com/projectdiscovery/retryablehttp-go" "github.com/projectdiscovery/utils/errkit" mapsutil "github.com/projectdiscovery/utils/maps" sliceutil "github.com/projectdiscovery/utils/slice" stringsutil "github.com/projectdiscovery/utils/strings" syncutil "github.com/projectdiscovery/utils/sync" urlutil "github.com/projectdiscovery/utils/url" "github.com/rs/xid" ) const ( httpPrefix = "http://" httpsPrefix = "https://" AuthStoreId = "auth_store" ) var ( TrustedTemplateDomains = []string{"cloud.projectdiscovery.io"} ) // Config contains the configuration options for the loader type Config struct { StoreId string // used to set store id (optional) Templates []string TemplateURLs []string Workflows []string WorkflowURLs []string ExcludeTemplates []string IncludeTemplates []string RemoteTemplateDomainList []string AITemplatePrompt string Tags []string ExcludeTags []string Protocols templateTypes.ProtocolTypes ExcludeProtocols templateTypes.ProtocolTypes Authors []string Severities severity.Severities ExcludeSeverities severity.Severities IncludeTags []string IncludeIds []string ExcludeIds []string IncludeConditions []string Catalog catalog.Catalog ExecutorOptions *protocols.ExecutorOptions Logger *gologger.Logger } // Store is a storage for loaded nuclei templates type Store struct { id string // id of the store (optional) tagFilter *templates.TagFilter config *Config finalTemplates []string finalWorkflows []string templates []*templates.Template workflows []*templates.Template preprocessor templates.Preprocessor logger *gologger.Logger // parserCacheOnce is used to cache the parser cache result parserCacheOnce func() *templates.Cache // metadataIndex is the template metadata cache metadataIndex *index.Index // indexFilter is the cached filter for metadata matching indexFilter *index.Filter // saveTemplatesIndexOnce is used to ensure we only save the metadata index // once saveMetadataIndexOnce func() // NotFoundCallback is called for each not found template // This overrides error handling for not found templates NotFoundCallback func(template string) bool } // NewConfig returns a new loader config func NewConfig(options *types.Options, catalog catalog.Catalog, executerOpts *protocols.ExecutorOptions) *Config { loaderConfig := Config{ Templates: options.Templates, Workflows: options.Workflows, RemoteTemplateDomainList: options.RemoteTemplateDomainList, TemplateURLs: options.TemplateURLs, WorkflowURLs: options.WorkflowURLs, ExcludeTemplates: options.ExcludedTemplates, Tags: options.Tags, ExcludeTags: options.ExcludeTags, IncludeTemplates: options.IncludeTemplates, Authors: options.Authors, Severities: options.Severities, ExcludeSeverities: options.ExcludeSeverities, IncludeTags: options.IncludeTags, IncludeIds: options.IncludeIds, ExcludeIds: options.ExcludeIds, Protocols: options.Protocols, ExcludeProtocols: options.ExcludeProtocols, IncludeConditions: options.IncludeConditions, Catalog: catalog, ExecutorOptions: executerOpts, AITemplatePrompt: options.AITemplatePrompt, Logger: options.Logger, } loaderConfig.RemoteTemplateDomainList = append(loaderConfig.RemoteTemplateDomainList, TrustedTemplateDomains...) return &loaderConfig } // New creates a new template store based on provided configuration func New(cfg *Config) (*Store, error) { // tagFilter only for IncludeConditions (advanced filtering). // All other filtering (tags, authors, severities, IDs, protocols, paths) is // handled by [index.Filter]. tagFilter, err := templates.NewTagFilter(&templates.TagFilterConfig{ IncludeConditions: cfg.IncludeConditions, }) if err != nil { return nil, err } store := &Store{ id: cfg.StoreId, config: cfg, tagFilter: tagFilter, finalTemplates: cfg.Templates, finalWorkflows: cfg.Workflows, logger: cfg.Logger, } store.parserCacheOnce = sync.OnceValue(func() *templates.Cache { if cfg.ExecutorOptions == nil || cfg.ExecutorOptions.Parser == nil { return nil } if parser, ok := cfg.ExecutorOptions.Parser.(*templates.Parser); ok { return parser.Cache() } return nil }) // Initialize metadata index and filter (load from disk & cache for reuse) store.metadataIndex = store.loadTemplatesIndex() store.indexFilter = store.buildIndexFilter() if cfg.ExecutorOptions != nil { cfg.ExecutorOptions.TemplateVerificationCallback = store.getTemplateVerification } store.saveMetadataIndexOnce = sync.OnceFunc(func() { if store.metadataIndex == nil { return } if err := store.metadataIndex.Save(); err != nil { store.logger.Warning().Msgf("Could not save metadata cache: %v", err) } else { store.logger.Verbose().Msgf("Saved %d templates to metadata cache", store.metadataIndex.Size()) } }) // Do a check to see if we have URLs in templates flag, if so // we need to process them separately and remove them from the initial list var templatesFinal []string for _, template := range cfg.Templates { // TODO: Add and replace this with urlutil.IsURL() helper if stringsutil.HasPrefixAny(template, httpPrefix, httpsPrefix) { cfg.TemplateURLs = append(cfg.TemplateURLs, template) } else { templatesFinal = append(templatesFinal, template) } } // fix editor paths remoteTemplates := []string{} for _, v := range cfg.TemplateURLs { if _, err := urlutil.Parse(v); err == nil { remoteTemplates = append(remoteTemplates, handleTemplatesEditorURLs(v)) } else { templatesFinal = append(templatesFinal, v) // something went wrong, treat it as a file } } cfg.TemplateURLs = remoteTemplates store.finalTemplates = templatesFinal urlBasedTemplatesProvided := len(cfg.TemplateURLs) > 0 || len(cfg.WorkflowURLs) > 0 if urlBasedTemplatesProvided { remoteTemplates, remoteWorkflows, err := getRemoteTemplatesAndWorkflows(cfg.TemplateURLs, cfg.WorkflowURLs, cfg.RemoteTemplateDomainList) if err != nil { return store, err } store.finalTemplates = append(store.finalTemplates, remoteTemplates...) store.finalWorkflows = append(store.finalWorkflows, remoteWorkflows...) } // Handle AI template generation if prompt is provided if len(cfg.AITemplatePrompt) > 0 { aiTemplates, err := getAIGeneratedTemplates(cfg.AITemplatePrompt, cfg.ExecutorOptions.Options) if err != nil { return nil, err } store.finalTemplates = append(store.finalTemplates, aiTemplates...) } // Handle a dot as the current working directory if len(store.finalTemplates) == 1 && store.finalTemplates[0] == "." { currentDirectory, err := os.Getwd() if err != nil { return nil, errors.Wrap(err, "could not get current directory") } store.finalTemplates = []string{currentDirectory} } // Handle a case with no templates or workflows, where we use base directory if len(store.finalTemplates) == 0 && len(store.finalWorkflows) == 0 && !urlBasedTemplatesProvided { store.finalTemplates = []string{config.DefaultConfig.TemplatesDirectory} } return store, nil } func (store *Store) getTemplateVerification(templatePath string) *protocols.TemplateVerification { if store.metadataIndex == nil { return nil } metadata, found := store.metadataIndex.Get(templatePath) if !found { return nil } return &protocols.TemplateVerification{ Verified: metadata.Verified, Verifier: metadata.TemplateVerifier, } } func handleTemplatesEditorURLs(input string) string { parsed, err := url.Parse(input) if err != nil { return input } if !strings.HasSuffix(parsed.Hostname(), "cloud.projectdiscovery.io") { return input } if strings.HasSuffix(parsed.Path, ".yaml") { return input } parsed.Path = fmt.Sprintf("%s.yaml", parsed.Path) finalURL := parsed.String() return finalURL } // ReadTemplateFromURI should only be used for viewing templates // and should not be used anywhere else like loading and executing templates // there is no sandbox restriction here func (store *Store) ReadTemplateFromURI(uri string, remote bool) ([]byte, error) { if stringsutil.HasPrefixAny(uri, httpPrefix, httpsPrefix) && remote { uri = handleTemplatesEditorURLs(uri) remoteTemplates, _, err := getRemoteTemplatesAndWorkflows([]string{uri}, nil, store.config.RemoteTemplateDomainList) if err != nil || len(remoteTemplates) == 0 { return nil, errkit.Wrapf(err, "Could not load template %s: got %v", uri, remoteTemplates) } resp, err := retryablehttp.Get(remoteTemplates[0]) if err != nil { return nil, err } defer func() { _ = resp.Body.Close() }() return io.ReadAll(resp.Body) } else { return os.ReadFile(uri) } } func (store *Store) ID() string { return store.id } // Templates returns all the templates in the store func (store *Store) Templates() []*templates.Template { return store.templates } // Workflows returns all the workflows in the store func (store *Store) Workflows() []*templates.Template { return store.workflows } // RegisterPreprocessor allows a custom preprocessor to be passed to the store to run against templates func (store *Store) RegisterPreprocessor(preprocessor templates.Preprocessor) { store.preprocessor = preprocessor } // Load loads all the templates from a store, performs filtering and returns // the complete compiled templates for a nuclei execution configuration. func (store *Store) Load() error { templates, err := store.LoadTemplates(store.finalTemplates) if err != nil { return err } store.templates = templates store.workflows = store.LoadWorkflows(store.finalWorkflows) return nil } var templateIDPathMap map[string]string func init() { templateIDPathMap = make(map[string]string) } // buildIndexFilter creates an [index.Filter] from the store configuration. // This filter handles all basic filtering (paths, tags, authors, severities, // IDs, protocols). Advanced IncludeConditions filtering is handled separately // by tagFilter. func (store *Store) buildIndexFilter() *index.Filter { includeTemplates, _ := store.config.Catalog.GetTemplatesPath(store.config.IncludeTemplates) excludeTemplates, _ := store.config.Catalog.GetTemplatesPath(store.config.ExcludeTemplates) return &index.Filter{ Authors: store.config.Authors, Tags: store.config.Tags, ExcludeTags: store.config.ExcludeTags, IncludeTags: store.config.IncludeTags, IDs: store.config.IncludeIds, ExcludeIDs: store.config.ExcludeIds, IncludeTemplates: includeTemplates, ExcludeTemplates: excludeTemplates, Severities: []severity.Severity(store.config.Severities), ExcludeSeverities: []severity.Severity(store.config.ExcludeSeverities), ProtocolTypes: []templateTypes.ProtocolType(store.config.Protocols), ExcludeProtocolTypes: []templateTypes.ProtocolType(store.config.ExcludeProtocols), } } func (store *Store) loadTemplatesIndex() *index.Index { var metadataIdx *index.Index idx, err := index.NewDefaultIndex() if err != nil { store.logger.Warning().Msgf("Could not create metadata cache: %v", err) } else { metadataIdx = idx if err := metadataIdx.Load(); err != nil { store.logger.Warning().Msgf("Could not load metadata cache: %v", err) } } return metadataIdx } // LoadTemplateTags loads template tags count using metadata index when possible. // // This method is optimized for tag listing (`-tgl`) and avoids loading all // templates into the store. func (store *Store) LoadTemplateTags() (map[string]int, error) { defer store.saveMetadataIndexOnce() templatePaths, errs := store.config.Catalog.GetTemplatesPath(store.finalTemplates) store.logErroredTemplates(errs) tagsMap := make(map[string]int) indexFilter := store.indexFilter templatesCache := store.parserCacheOnce() if templatesCache == nil { return nil, errors.New("invalid parser") } // Include conditions require a parsed template and cannot be evaluated from // metadata alone. requiresTemplateParse := len(store.config.IncludeConditions) > 0 for _, templatePath := range templatePaths { if store.metadataIndex != nil { if metadata, found := store.metadataIndex.Get(templatePath); found { if !indexFilter.Matches(metadata) { continue } if !requiresTemplateParse { for _, tag := range metadata.Tags { tagsMap[tag]++ } continue } } } loaded, err := store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, nil, store.config.Catalog) if err != nil { if strings.Contains(err.Error(), templates.ErrExcluded.Error()) { stats.Increment(templates.TemplatesExcludedStats) if config.DefaultConfig.LogAllEvents { store.logger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error()) } continue } store.logger.Warning().Msg(err.Error()) continue } if !loaded { continue } template, _, _ := templatesCache.Has(templatePath) if template == nil { continue } var metadata *index.Metadata if store.metadataIndex != nil { metadata, _ = store.metadataIndex.SetFromTemplate(templatePath, template) } else { metadata = index.NewMetadataFromTemplate(templatePath, template) } if metadata != nil && !indexFilter.Matches(metadata) { continue } for _, tag := range template.Info.Tags.ToSlice() { tagsMap[tag]++ } } return tagsMap, nil } // LoadTemplatesOnlyMetadata loads only the metadata of the templates func (store *Store) LoadTemplatesOnlyMetadata() error { defer store.saveMetadataIndexOnce() templatePaths, errs := store.config.Catalog.GetTemplatesPath(store.finalTemplates) store.logErroredTemplates(errs) indexFilter := store.indexFilter validPaths := make(map[string]struct{}) for _, templatePath := range templatePaths { if store.metadataIndex != nil { if metadata, found := store.metadataIndex.Get(templatePath); found { if !indexFilter.Matches(metadata) { continue } if store.tagFilter != nil { loaded, err := store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, nil, store.config.Catalog) if !loaded { if err != nil && strings.Contains(err.Error(), templates.ErrExcluded.Error()) { stats.Increment(templates.TemplatesExcludedStats) if config.DefaultConfig.LogAllEvents { store.logger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error()) } } continue } } validPaths[templatePath] = struct{}{} continue } } loaded, err := store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, nil, store.config.Catalog) if loaded { templatesCache := store.parserCacheOnce() if templatesCache != nil { if template, _, _ := templatesCache.Has(templatePath); template != nil { var metadata *index.Metadata if store.metadataIndex != nil { metadata, _ = store.metadataIndex.SetFromTemplate(templatePath, template) } else { metadata = index.NewMetadataFromTemplate(templatePath, template) } if !indexFilter.Matches(metadata) { continue } validPaths[templatePath] = struct{}{} continue } } validPaths[templatePath] = struct{}{} } if err != nil { if strings.Contains(err.Error(), templates.ErrExcluded.Error()) { stats.Increment(templates.TemplatesExcludedStats) if config.DefaultConfig.LogAllEvents { store.logger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error()) } continue } store.logger.Warning().Msg(err.Error()) } } templatesCache := store.parserCacheOnce() if templatesCache == nil { return errors.New("invalid parser") } loadedTemplateIDs := mapsutil.NewSyncLockMap[string, struct{}]() caps := templates.Capabilities{ Headless: store.config.ExecutorOptions.Options.Headless, Code: store.config.ExecutorOptions.Options.EnableCodeTemplates, DAST: store.config.ExecutorOptions.Options.DAST, SelfContained: store.config.ExecutorOptions.Options.EnableSelfContainedTemplates, File: store.config.ExecutorOptions.Options.EnableFileTemplates, } isListOrDisplay := store.config.ExecutorOptions.Options.TemplateList || store.config.ExecutorOptions.Options.TemplateDisplay for templatePath := range validPaths { template, _, _ := templatesCache.Has(templatePath) if template == nil { continue } if !isListOrDisplay && !template.IsEnabledFor(caps) { continue } if loadedTemplateIDs.Has(template.ID) { store.logger.Debug().Msgf("Skipping duplicate template ID '%s' from path '%s'", template.ID, templatePath) continue } _ = loadedTemplateIDs.Set(template.ID, struct{}{}) template.Path = templatePath store.templates = append(store.templates, template) } return nil } // ValidateTemplates takes a list of templates and validates them // erroring out on discovering any faulty templates. func (store *Store) ValidateTemplates() error { templatePaths, errs := store.config.Catalog.GetTemplatesPath(store.finalTemplates) store.logErroredTemplates(errs) workflowPaths, errs := store.config.Catalog.GetTemplatesPath(store.finalWorkflows) store.logErroredTemplates(errs) templatePathsMap := make(map[string]struct{}, len(templatePaths)) for _, path := range templatePaths { templatePathsMap[path] = struct{}{} } workflowPathsMap := make(map[string]struct{}, len(workflowPaths)) for _, path := range workflowPaths { workflowPathsMap[path] = struct{}{} } if store.areTemplatesValid(templatePathsMap) && store.areWorkflowsValid(workflowPathsMap) { return nil } return errors.New("errors occurred during template validation") } func (store *Store) areWorkflowsValid(filteredWorkflowPaths map[string]struct{}) bool { return store.areWorkflowOrTemplatesValid(filteredWorkflowPaths, true, func(templatePath string, tagFilter *templates.TagFilter) (bool, error) { return store.config.ExecutorOptions.Parser.LoadWorkflow(templatePath, store.config.Catalog) }) } func (store *Store) areTemplatesValid(filteredTemplatePaths map[string]struct{}) bool { return store.areWorkflowOrTemplatesValid(filteredTemplatePaths, false, func(templatePath string, tagFilter *templates.TagFilter) (bool, error) { return store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, nil, store.config.Catalog) }) } func (store *Store) areWorkflowOrTemplatesValid(filteredTemplatePaths map[string]struct{}, isWorkflow bool, load func(templatePath string, tagFilter *templates.TagFilter) (bool, error)) bool { areTemplatesValid := true parsedCache := store.parserCacheOnce() for templatePath := range filteredTemplatePaths { if _, err := load(templatePath, store.tagFilter); err != nil { if isParsingError(store, "Error occurred loading template %s: %s\n", templatePath, err) { areTemplatesValid = false continue } } var template *templates.Template var err error if parsedCache != nil { if cachedTemplate, _, cacheErr := parsedCache.Has(templatePath); cacheErr == nil && cachedTemplate != nil { template = cachedTemplate } } if template == nil { template, err = templates.Parse(templatePath, store.preprocessor, store.config.ExecutorOptions) if err != nil { if isParsingError(store, "Error occurred parsing template %s: %s\n", templatePath, err) { areTemplatesValid = false continue } } } if template == nil { // NOTE(dwisiswant0): possibly global matchers template. // This could definitely be handled better, for example by returning an // `ErrGlobalMatchersTemplate` during `templates.Parse` and checking it // with `errors.Is`. // // However, I'm not sure if every reference to it should be handled // that way. Returning a `templates.Template` pointer would mean it's // an active template (sending requests), and adding a specific field // like `isGlobalMatchers` in `templates.Template` (then checking it // with a `*templates.Template.IsGlobalMatchersEnabled` method) would // just introduce more unknown issues - like during template // clustering, AFAIK. continue } else { if existingTemplatePath, found := templateIDPathMap[template.ID]; !found { templateIDPathMap[template.ID] = templatePath } else { // TODO: until https://github.com/projectdiscovery/nuclei-templates/issues/11324 is deployed // disable strict validation to allow GH actions to run // areTemplatesValid = false store.logger.Warning().Msgf("Found duplicate template ID during validation '%s' => '%s': %s\n", templatePath, existingTemplatePath, template.ID) } if !isWorkflow && template.HasWorkflows() { continue } } if isWorkflow { if !areWorkflowTemplatesValid(store, template.Workflows) { areTemplatesValid = false continue } } } return areTemplatesValid } func areWorkflowTemplatesValid(store *Store, workflows []*workflows.WorkflowTemplate) bool { for _, workflow := range workflows { if !areWorkflowTemplatesValid(store, workflow.Subtemplates) { return false } _, err := store.config.Catalog.GetTemplatePath(workflow.Template) if err != nil { if isParsingError(store, "Error occurred loading template %s: %s\n", workflow.Template, err) { return false } } } return true } func isParsingError(store *Store, message string, template string, err error) bool { if errors.Is(err, templates.ErrExcluded) { return false } if errors.Is(err, templates.ErrCreateTemplateExecutor) { return false } store.logger.Error().Msgf(message, template, err) return true } // LoadTemplates takes a list of templates and returns paths for them func (store *Store) LoadTemplates(templatesList []string) ([]*templates.Template, error) { return store.LoadTemplatesWithTags(templatesList, nil) } // LoadWorkflows takes a list of workflows and returns paths for them func (store *Store) LoadWorkflows(workflowsList []string) []*templates.Template { includedWorkflows, errs := store.config.Catalog.GetTemplatesPath(workflowsList) store.logErroredTemplates(errs) loadedWorkflows := make([]*templates.Template, 0, len(includedWorkflows)) for _, workflowPath := range includedWorkflows { loaded, err := store.config.ExecutorOptions.Parser.LoadWorkflow(workflowPath, store.config.Catalog) if err != nil { store.logger.Warning().Msgf("Could not load workflow %s: %s\n", workflowPath, err) } if loaded { parsed, err := templates.Parse(workflowPath, store.preprocessor, store.config.ExecutorOptions) if err != nil { store.logger.Warning().Msgf("Could not parse workflow %s: %s\n", workflowPath, err) } else if parsed != nil { loadedWorkflows = append(loadedWorkflows, parsed) } } } return loadedWorkflows } // LoadTemplatesWithTags takes a list of templates and extra tags // returning templates that match. // Returns an error if dialers are not initialized for the given execution ID. func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*templates.Template, error) { defer store.saveMetadataIndexOnce() indexFilter := store.indexFilter includedTemplates, errs := store.config.Catalog.GetTemplatesPath(templatesList) store.logErroredTemplates(errs) loadedTemplates := sliceutil.NewSyncSlice[*templates.Template]() loadedTemplateIDs := mapsutil.NewSyncLockMap[string, struct{}]() loadTemplate := func(tmpl *templates.Template) { if loadedTemplateIDs.Has(tmpl.ID) { store.logger.Debug().Msgf("Skipping duplicate template ID '%s' from path '%s'", tmpl.ID, tmpl.Path) return } _ = loadedTemplateIDs.Set(tmpl.ID, struct{}{}) loadedTemplates.Append(tmpl) // increment signed/unsigned counters if tmpl.Verified { if tmpl.TemplateVerifier == "" { templates.SignatureStats[keys.PDVerifier].Add(1) } else { templates.SignatureStats[tmpl.TemplateVerifier].Add(1) } } else { templates.SignatureStats[templates.Unsigned].Add(1) } } typesOpts := store.config.ExecutorOptions.Options concurrency := typesOpts.TemplateLoadingConcurrency if concurrency <= 0 { concurrency = types.DefaultTemplateLoadingConcurrency } wgLoadTemplates, errWg := syncutil.New(syncutil.WithSize(concurrency)) if errWg != nil { return nil, fmt.Errorf("could not create wait group: %w", errWg) } if typesOpts.ExecutionId == "" { typesOpts.ExecutionId = xid.New().String() } dialers := protocolstate.GetDialersWithId(typesOpts.ExecutionId) if dialers == nil { return nil, fmt.Errorf("dialers with executionId %s not found", typesOpts.ExecutionId) } for _, templatePath := range includedTemplates { wgLoadTemplates.Add() go func(templatePath string) { defer wgLoadTemplates.Done() var ( metadata *index.Metadata metadataCached bool ) if store.metadataIndex != nil { if cachedMetadata, found := store.metadataIndex.Get(templatePath); found { metadata = cachedMetadata if !indexFilter.Matches(metadata) { return } // NOTE(dwisiswant0): else, tagFilter probably exists (for // IncludeConditions), which still need to check via // LoadTemplate. metadataCached = true } } loaded, err := store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, tags, store.config.Catalog) if loaded { parsed, err := templates.Parse(templatePath, store.preprocessor, store.config.ExecutorOptions) if parsed != nil && !metadataCached { if store.metadataIndex != nil { metadata, _ = store.metadataIndex.SetFromTemplate(templatePath, parsed) } else { metadata = index.NewMetadataFromTemplate(templatePath, parsed) } if metadata != nil && !indexFilter.Matches(metadata) { return } } if err != nil { // exclude templates not compatible with offline matching from total runtime warning stats if !errors.Is(err, templates.ErrIncompatibleWithOfflineMatching) { stats.Increment(templates.RuntimeWarningsStats) } store.logger.Warning().Msgf("Could not parse template %s: %s\n", templatePath, err) } else if parsed != nil { if !parsed.Verified && typesOpts.DisableUnsignedTemplates { // skip unverified templates when prompted to stats.Increment(templates.SkippedUnsignedStats) return } if parsed.SelfContained && !typesOpts.EnableSelfContainedTemplates { stats.Increment(templates.ExcludedSelfContainedStats) return } if parsed.HasFileRequest() && !typesOpts.EnableFileTemplates { stats.Increment(templates.ExcludedFileStats) return } // if template has request signature like aws then only signed and verified templates are allowed if parsed.UsesRequestSignature() && !parsed.Verified { stats.Increment(templates.SkippedRequestSignatureStats) return } // DAST only templates // Skip DAST filter when loading auth templates if store.ID() != AuthStoreId && typesOpts.DAST { // check if the template is a DAST template // also allow global matchers template to be loaded if parsed.IsFuzzableRequest() || parsed.IsGlobalMatchersTemplate() { if parsed.HasHeadlessRequest() && !typesOpts.Headless { stats.Increment(templates.ExcludedHeadlessTmplStats) if config.DefaultConfig.LogAllEvents { store.logger.Print().Msgf("[%v] Headless flag is required for headless template '%s'.\n", aurora.Yellow("WRN").String(), templatePath) } } else { loadTemplate(parsed) } } } else if parsed.HasHeadlessRequest() && !typesOpts.Headless { // donot include headless template in final list if headless flag is not set stats.Increment(templates.ExcludedHeadlessTmplStats) if config.DefaultConfig.LogAllEvents { store.logger.Print().Msgf("[%v] Headless flag is required for headless template '%s'.\n", aurora.Yellow("WRN").String(), templatePath) } } else if parsed.HasCodeRequest() && !typesOpts.EnableCodeTemplates { // donot include 'Code' protocol custom template in final list if code flag is not set stats.Increment(templates.ExcludedCodeTmplStats) if config.DefaultConfig.LogAllEvents { store.logger.Print().Msgf("[%v] Code flag is required for code protocol template '%s'.\n", aurora.Yellow("WRN").String(), templatePath) } } else if parsed.HasCodeRequest() && !parsed.Verified && !parsed.HasWorkflows() { // donot include unverified 'Code' protocol custom template in final list stats.Increment(templates.SkippedCodeTmplTamperedStats) // these will be skipped so increment skip counter stats.Increment(templates.SkippedUnsignedStats) if config.DefaultConfig.LogAllEvents { store.logger.Print().Msgf("[%v] Tampered/Unsigned template at %v.\n", aurora.Yellow("WRN").String(), templatePath) } } else if parsed.IsFuzzableRequest() && !typesOpts.DAST { stats.Increment(templates.ExcludedDastTmplStats) if config.DefaultConfig.LogAllEvents { store.logger.Print().Msgf("[%v] -dast flag is required for DAST template '%s'.\n", aurora.Yellow("WRN").String(), templatePath) } } else { loadTemplate(parsed) } } } if err != nil { if strings.Contains(err.Error(), templates.ErrExcluded.Error()) { stats.Increment(templates.TemplatesExcludedStats) if config.DefaultConfig.LogAllEvents { store.logger.Print().Msgf("[%v] %v\n", aurora.Yellow("WRN").String(), err.Error()) } return } store.logger.Warning().Msg(err.Error()) } }(templatePath) } wgLoadTemplates.Wait() sort.SliceStable(loadedTemplates.Slice, func(i, j int) bool { return loadedTemplates.Slice[i].Path < loadedTemplates.Slice[j].Path }) return loadedTemplates.Slice, nil } // IsHTTPBasedProtocolUsed returns true if http/headless protocol is being used for // any templates. func IsHTTPBasedProtocolUsed(store *Store) bool { templates := append(store.Templates(), store.Workflows()...) for _, template := range templates { if template.HasHTTPRequest() || template.HasHeadlessRequest() { return true } if template.HasWorkflows() { if workflowContainsProtocol(template.Workflows) { return true } } } return false } func workflowContainsProtocol(workflow []*workflows.WorkflowTemplate) bool { for _, workflow := range workflow { for _, template := range workflow.Matchers { if workflowContainsProtocol(template.Subtemplates) { return true } } for _, template := range workflow.Subtemplates { if workflowContainsProtocol(template.Subtemplates) { return true } } for _, executer := range workflow.Executers { if executer.TemplateType == templateTypes.HTTPProtocol || executer.TemplateType == templateTypes.HeadlessProtocol { return true } } } return false } func (s *Store) logErroredTemplates(erred map[string]error) { for template, err := range erred { if s.NotFoundCallback == nil || !s.NotFoundCallback(template) { s.logger.Error().Msgf("Could not find template '%s': %s", template, err) } } } ================================================ FILE: pkg/catalog/loader/loader_bench_test.go ================================================ package loader_test import ( "testing" "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/loader/workflow" "github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity" "github.com/projectdiscovery/nuclei/v3/pkg/templates" templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types" "github.com/projectdiscovery/nuclei/v3/pkg/testutils" ) func BenchmarkStoreValidateTemplates(b *testing.B) { options := testutils.DefaultOptions.Copy() options.Logger = &gologger.Logger{} testutils.Init(options) catalog := disk.NewCatalog(config.DefaultConfig.TemplatesDirectory) executerOpts := testutils.NewMockExecuterOptions(options, nil) executerOpts.Parser = templates.NewParser() workflowLoader, err := workflow.NewLoader(executerOpts) if err != nil { b.Fatalf("could not create workflow loader: %s", err) } executerOpts.WorkflowLoader = workflowLoader loaderCfg := loader.NewConfig(options, catalog, executerOpts) store, err := loader.New(loaderCfg) if err != nil { b.Fatalf("could not create store: %s", err) } b.ResetTimer() b.ReportAllocs() for b.Loop() { _ = store.ValidateTemplates() } } func BenchmarkLoadTemplates(b *testing.B) { options := testutils.DefaultOptions.Copy() options.Logger = &gologger.Logger{} options.ExecutionId = "bench-load-templates" testutils.Init(options) catalog := disk.NewCatalog(config.DefaultConfig.TemplatesDirectory) executerOpts := testutils.NewMockExecuterOptions(options, nil) executerOpts.Parser = templates.NewParser() workflowLoader, err := workflow.NewLoader(executerOpts) if err != nil { b.Fatalf("could not create workflow loader: %s", err) } executerOpts.WorkflowLoader = workflowLoader b.Run("NoFilter", func(b *testing.B) { loaderCfg := loader.NewConfig(options, catalog, executerOpts) store, err := loader.New(loaderCfg) if err != nil { b.Fatalf("could not create store: %s", err) } b.ResetTimer() b.ReportAllocs() for b.Loop() { _, _ = store.LoadTemplates([]string{config.DefaultConfig.TemplatesDirectory}) } }) b.Run("FilterBySeverityCritical", func(b *testing.B) { opts := options.Copy() opts.Severities = severity.Severities{severity.Critical} loaderCfg := loader.NewConfig(opts, catalog, executerOpts) store, err := loader.New(loaderCfg) if err != nil { b.Fatalf("could not create store: %s", err) } b.ResetTimer() b.ReportAllocs() for b.Loop() { _, _ = store.LoadTemplates([]string{config.DefaultConfig.TemplatesDirectory}) } }) b.Run("FilterBySeverityHighCritical", func(b *testing.B) { opts := options.Copy() opts.Severities = severity.Severities{severity.High, severity.Critical} loaderCfg := loader.NewConfig(opts, catalog, executerOpts) store, err := loader.New(loaderCfg) if err != nil { b.Fatalf("could not create store: %s", err) } b.ResetTimer() b.ReportAllocs() for b.Loop() { _, _ = store.LoadTemplates([]string{config.DefaultConfig.TemplatesDirectory}) } }) b.Run("FilterByAuthor", func(b *testing.B) { opts := options.Copy() opts.Authors = []string{"pdteam"} loaderCfg := loader.NewConfig(opts, catalog, executerOpts) store, err := loader.New(loaderCfg) if err != nil { b.Fatalf("could not create store: %s", err) } b.ResetTimer() b.ReportAllocs() for b.Loop() { _, _ = store.LoadTemplates([]string{config.DefaultConfig.TemplatesDirectory}) } }) b.Run("FilterByTags", func(b *testing.B) { opts := options.Copy() opts.Tags = []string{"cve", "rce"} loaderCfg := loader.NewConfig(opts, catalog, executerOpts) store, err := loader.New(loaderCfg) if err != nil { b.Fatalf("could not create store: %s", err) } b.ResetTimer() b.ReportAllocs() for b.Loop() { _, _ = store.LoadTemplates([]string{config.DefaultConfig.TemplatesDirectory}) } }) b.Run("FilterByProtocol", func(b *testing.B) { opts := options.Copy() opts.Protocols = templateTypes.ProtocolTypes{templateTypes.HTTPProtocol} loaderCfg := loader.NewConfig(opts, catalog, executerOpts) store, err := loader.New(loaderCfg) if err != nil { b.Fatalf("could not create store: %s", err) } b.ResetTimer() b.ReportAllocs() for b.Loop() { _, _ = store.LoadTemplates([]string{config.DefaultConfig.TemplatesDirectory}) } }) b.Run("ComplexFilter", func(b *testing.B) { opts := options.Copy() opts.Severities = severity.Severities{severity.High, severity.Critical} opts.Authors = []string{"pdteam"} opts.Tags = []string{"cve"} loaderCfg := loader.NewConfig(opts, catalog, executerOpts) store, err := loader.New(loaderCfg) if err != nil { b.Fatalf("could not create store: %s", err) } b.ResetTimer() b.ReportAllocs() for b.Loop() { _, _ = store.LoadTemplates([]string{config.DefaultConfig.TemplatesDirectory}) } }) } func BenchmarkLoadTemplatesOnlyMetadata(b *testing.B) { options := testutils.DefaultOptions.Copy() options.Logger = &gologger.Logger{} options.ExecutionId = "bench-metadata" testutils.Init(options) catalog := disk.NewCatalog(config.DefaultConfig.TemplatesDirectory) executerOpts := testutils.NewMockExecuterOptions(options, nil) executerOpts.Parser = templates.NewParser() workflowLoader, err := workflow.NewLoader(executerOpts) if err != nil { b.Fatalf("could not create workflow loader: %s", err) } executerOpts.WorkflowLoader = workflowLoader b.Run("WithoutFilter", func(b *testing.B) { loaderCfg := loader.NewConfig(options, catalog, executerOpts) store, err := loader.New(loaderCfg) if err != nil { b.Fatalf("could not create store: %s", err) } // Pre-warm the cache _ = store.LoadTemplatesOnlyMetadata() b.ResetTimer() b.ReportAllocs() for b.Loop() { _ = store.LoadTemplatesOnlyMetadata() } }) b.Run("WithSeverityFilter", func(b *testing.B) { opts := options.Copy() opts.Severities = severity.Severities{severity.Critical} loaderCfg := loader.NewConfig(opts, catalog, executerOpts) store, err := loader.New(loaderCfg) if err != nil { b.Fatalf("could not create store: %s", err) } // Pre-warm the cache _ = store.LoadTemplatesOnlyMetadata() b.ResetTimer() b.ReportAllocs() for b.Loop() { _ = store.LoadTemplatesOnlyMetadata() } }) } ================================================ FILE: pkg/catalog/loader/loader_test.go ================================================ package loader import ( "reflect" "testing" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk" "github.com/stretchr/testify/require" ) func TestLoadTemplates(t *testing.T) { catalog := disk.NewCatalog("") store, err := New(&Config{ Templates: []string{"cves/CVE-2021-21315.yaml"}, Catalog: catalog, }) require.Nil(t, err, "could not load templates") require.Equal(t, []string{"cves/CVE-2021-21315.yaml"}, store.finalTemplates, "could not get correct templates") templatesDirectory := "/test" config.DefaultConfig.TemplatesDirectory = templatesDirectory t.Run("blank", func(t *testing.T) { store, err := New(&Config{ Catalog: catalog, }) require.Nil(t, err, "could not load templates") require.Equal(t, []string{templatesDirectory}, store.finalTemplates, "could not get correct templates") }) t.Run("only-tags", func(t *testing.T) { store, err := New(&Config{ Tags: []string{"cves"}, Catalog: catalog, }) require.Nil(t, err, "could not load templates") require.Equal(t, []string{templatesDirectory}, store.finalTemplates, "could not get correct templates") }) t.Run("tags-with-path", func(t *testing.T) { store, err := New(&Config{ Tags: []string{"cves"}, Catalog: catalog, }) require.Nil(t, err, "could not load templates") require.Equal(t, []string{templatesDirectory}, store.finalTemplates, "could not get correct templates") }) } func TestRemoteTemplates(t *testing.T) { catalog := disk.NewCatalog("") var nilStringSlice []string type args struct { config *Config } tests := []struct { name string args args want *Store wantErr bool }{ { name: "remote-templates-positive", args: args{ config: &Config{ TemplateURLs: []string{"https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/technologies/tech-detect.yaml"}, RemoteTemplateDomainList: []string{"localhost", "raw.githubusercontent.com"}, Catalog: catalog, }, }, want: &Store{ finalTemplates: []string{"https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/technologies/tech-detect.yaml"}, }, wantErr: false, }, { name: "remote-templates-negative", args: args{ config: &Config{ TemplateURLs: []string{"https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/technologies/tech-detect.yaml"}, RemoteTemplateDomainList: []string{"localhost"}, Catalog: catalog, }, }, want: &Store{ finalTemplates: nilStringSlice, }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := New(tt.args.config) if (err != nil) != tt.wantErr { t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got.finalTemplates, tt.want.finalTemplates) { t.Errorf("New() = %v, want %v", got.finalTemplates, tt.want.finalTemplates) } }) } } ================================================ FILE: pkg/catalog/loader/remote_loader.go ================================================ package loader import ( "bufio" "fmt" "net/url" "strings" "sync" "github.com/pkg/errors" "github.com/projectdiscovery/nuclei/v3/pkg/templates/extensions" "github.com/projectdiscovery/nuclei/v3/pkg/utils" "github.com/projectdiscovery/retryablehttp-go" sliceutil "github.com/projectdiscovery/utils/slice" stringsutil "github.com/projectdiscovery/utils/strings" syncutil "github.com/projectdiscovery/utils/sync" ) type ContentType string const ( Template ContentType = "Template" Workflow ContentType = "Workflow" ) type RemoteContent struct { Content []string Type ContentType Error error } func getRemoteTemplatesAndWorkflows(templateURLs, workflowURLs, remoteTemplateDomainList []string) ([]string, []string, error) { var ( err error muErr sync.Mutex ) remoteTemplateList := sliceutil.NewSyncSlice[string]() remoteWorkFlowList := sliceutil.NewSyncSlice[string]() awg, errAwg := syncutil.New(syncutil.WithSize(50)) if errAwg != nil { return nil, nil, errAwg } loadItem := func(URL string, contentType ContentType) { defer awg.Done() remoteContent := getRemoteContent(URL, remoteTemplateDomainList, contentType) if remoteContent.Error != nil { muErr.Lock() if err != nil { err = errors.New(remoteContent.Error.Error() + ": " + err.Error()) } else { err = remoteContent.Error } muErr.Unlock() } else { switch remoteContent.Type { case Template: remoteTemplateList.Append(remoteContent.Content...) case Workflow: remoteWorkFlowList.Append(remoteContent.Content...) } } } for _, templateURL := range templateURLs { awg.Add() go loadItem(templateURL, Template) } for _, workflowURL := range workflowURLs { awg.Add() go loadItem(workflowURL, Workflow) } awg.Wait() return remoteTemplateList.Slice, remoteWorkFlowList.Slice, err } func getRemoteContent(URL string, remoteTemplateDomainList []string, contentType ContentType) RemoteContent { if err := validateRemoteTemplateURL(URL, remoteTemplateDomainList); err != nil { return RemoteContent{Error: err} } if strings.HasPrefix(URL, "http") && stringsutil.HasSuffixAny(URL, extensions.YAML) { return RemoteContent{ Content: []string{URL}, Type: contentType, } } response, err := retryablehttp.DefaultClient().Get(URL) if err != nil { return RemoteContent{Error: err} } defer func() { _ = response.Body.Close() }() if response.StatusCode < 200 || response.StatusCode > 299 { return RemoteContent{Error: fmt.Errorf("get \"%s\": unexpected status %d", URL, response.StatusCode)} } scanner := bufio.NewScanner(response.Body) var templateList []string for scanner.Scan() { text := strings.TrimSpace(scanner.Text()) if text == "" { continue } if utils.IsURL(text) { if err := validateRemoteTemplateURL(text, remoteTemplateDomainList); err != nil { return RemoteContent{Error: err} } } templateList = append(templateList, text) } if err := scanner.Err(); err != nil { return RemoteContent{Error: errors.Wrap(err, "get \"%s\"")} } return RemoteContent{ Content: templateList, Type: contentType, } } func validateRemoteTemplateURL(inputURL string, remoteTemplateDomainList []string) error { parsedURL, err := url.Parse(inputURL) if err != nil { return err } if !utils.StringSliceContains(remoteTemplateDomainList, parsedURL.Host) { return errors.Errorf("Remote template URL host (%s) is not present in the `remote-template-domain` list in nuclei config", parsedURL.Host) } return nil } ================================================ FILE: pkg/core/engine.go ================================================ package core import ( "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/protocols" "github.com/projectdiscovery/nuclei/v3/pkg/types" ) // Engine is an executer for running Nuclei Templates/Workflows. // // The engine contains multiple thread pools which allow using different // concurrency values per protocol executed. // // The engine does most of the heavy lifting of execution, from clustering // templates to leading to the final execution by the work pool, it is // handled by the engine. type Engine struct { workPool *WorkPool options *types.Options executerOpts *protocols.ExecutorOptions Callback func(*output.ResultEvent) // Executed on results Logger *gologger.Logger } // New returns a new Engine instance func New(options *types.Options) *Engine { engine := &Engine{ options: options, Logger: options.Logger, } engine.workPool = engine.GetWorkPool() return engine } func (e *Engine) GetWorkPoolConfig() WorkPoolConfig { config := WorkPoolConfig{ InputConcurrency: e.options.BulkSize, TypeConcurrency: e.options.TemplateThreads, HeadlessInputConcurrency: e.options.HeadlessBulkSize, HeadlessTypeConcurrency: e.options.HeadlessTemplateThreads, } return config } // GetWorkPool returns a workpool from options func (e *Engine) GetWorkPool() *WorkPool { return NewWorkPool(e.GetWorkPoolConfig()) } // SetExecuterOptions sets the executer options for the engine. This is required // before using the engine to perform any execution. func (e *Engine) SetExecuterOptions(options *protocols.ExecutorOptions) { e.executerOpts = options } // ExecuterOptions returns protocols.ExecutorOptions for nuclei engine. func (e *Engine) ExecuterOptions() *protocols.ExecutorOptions { return e.executerOpts } // WorkPool returns the worker pool for the engine func (e *Engine) WorkPool() *WorkPool { // resize check point - nop if there are no changes e.workPool.RefreshWithConfig(e.GetWorkPoolConfig()) return e.workPool } ================================================ FILE: pkg/core/engine_test.go ================================================ package core ================================================ FILE: pkg/core/execute_options.go ================================================ package core import ( "context" "sync" "sync/atomic" "github.com/projectdiscovery/nuclei/v3/pkg/input/provider" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs" "github.com/projectdiscovery/nuclei/v3/pkg/templates" "github.com/projectdiscovery/nuclei/v3/pkg/templates/types" "github.com/projectdiscovery/nuclei/v3/pkg/types/scanstrategy" stringsutil "github.com/projectdiscovery/utils/strings" syncutil "github.com/projectdiscovery/utils/sync" ) // Execute takes a list of templates/workflows that have been compiled // and executes them based on provided concurrency options. // // All the execution logic for the templates/workflows happens in this part // of the engine. func (e *Engine) Execute(ctx context.Context, templates []*templates.Template, target provider.InputProvider) *atomic.Bool { return e.ExecuteScanWithOpts(ctx, templates, target, false) } // ExecuteWithResults a list of templates with results func (e *Engine) ExecuteWithResults(ctx context.Context, templatesList []*templates.Template, target provider.InputProvider, callback func(*output.ResultEvent)) *atomic.Bool { e.Callback = callback return e.ExecuteScanWithOpts(ctx, templatesList, target, false) } // ExecuteScanWithOpts executes scan with given scanStrategy func (e *Engine) ExecuteScanWithOpts(ctx context.Context, templatesList []*templates.Template, target provider.InputProvider, noCluster bool) *atomic.Bool { results := &atomic.Bool{} selfcontainedWg := &sync.WaitGroup{} totalReqBeforeCluster := getRequestCount(templatesList) * int(target.Count()) // attempt to cluster templates if noCluster is false var finalTemplates []*templates.Template clusterCount := 0 if !noCluster { var clusterMappings map[string][]string finalTemplates, clusterCount, clusterMappings = templates.ClusterTemplates(templatesList, e.executerOpts) // Store cluster mappings in executerOpts for SDK access (thread-safe) if clusterMappings != nil { e.executerOpts.ClusterMappings = types.NewClusterMappingsMap(clusterMappings) } } else { finalTemplates = templatesList } totalReqAfterClustering := getRequestCount(finalTemplates) * int(target.Count()) if !noCluster && totalReqAfterClustering < totalReqBeforeCluster { e.Logger.Info().Msgf("Templates clustered: %d (Reduced %d Requests)", clusterCount, totalReqBeforeCluster-totalReqAfterClustering) } // 0 matches means no templates were found in the directory if len(finalTemplates) == 0 { return &atomic.Bool{} } if e.executerOpts.Progress != nil { // Notes: // workflow requests are not counted as they can be conditional // templateList count is user requested templates count (before clustering) // totalReqAfterClustering is total requests count after clustering e.executerOpts.Progress.Init(target.Count(), len(templatesList), int64(totalReqAfterClustering)) } if stringsutil.EqualFoldAny(e.options.ScanStrategy, scanstrategy.Auto.String(), "") { // TODO: this is only a placeholder, auto scan strategy should choose scan strategy // based on no of hosts , templates , stream and other optimization parameters e.options.ScanStrategy = scanstrategy.TemplateSpray.String() } filtered := []*templates.Template{} selfContained := []*templates.Template{} // Filter Self Contained templates since they are not bound to target for _, v := range finalTemplates { if v.SelfContained { selfContained = append(selfContained, v) } else { filtered = append(filtered, v) } } // Execute All SelfContained in parallel e.executeAllSelfContained(ctx, selfContained, results, selfcontainedWg) strategyResult := &atomic.Bool{} switch e.options.ScanStrategy { case scanstrategy.TemplateSpray.String(): strategyResult = e.executeTemplateSpray(ctx, filtered, target) case scanstrategy.HostSpray.String(): strategyResult = e.executeHostSpray(ctx, filtered, target) } results.CompareAndSwap(false, strategyResult.Load()) selfcontainedWg.Wait() return results } // executeTemplateSpray executes scan using template spray strategy where targets are iterated over each template func (e *Engine) executeTemplateSpray(ctx context.Context, templatesList []*templates.Template, target provider.InputProvider) *atomic.Bool { results := &atomic.Bool{} // wp is workpool that contains different waitgroups for // headless and non-headless templates wp := e.GetWorkPool() defer wp.Wait() for _, template := range templatesList { select { case <-ctx.Done(): return results default: } // resize check point - nop if there are no changes wp.RefreshWithConfig(e.GetWorkPoolConfig()) templateType := template.Type() var wg *syncutil.AdaptiveWaitGroup if templateType == types.HeadlessProtocol { wg = wp.Headless } else { wg = wp.Default } wg.Add() go func(tpl *templates.Template) { defer wg.Done() // All other request types are executed here // Note: executeTemplateWithTargets creates goroutines and blocks // given template is executed on all targets e.executeTemplateWithTargets(ctx, tpl, target, results) }(template) } return results } // executeHostSpray executes scan using host spray strategy where templates are iterated over each target func (e *Engine) executeHostSpray(ctx context.Context, templatesList []*templates.Template, target provider.InputProvider) *atomic.Bool { results := &atomic.Bool{} wp, _ := syncutil.New(syncutil.WithSize(e.options.BulkSize + e.options.HeadlessBulkSize)) defer wp.Wait() target.Iterate(func(value *contextargs.MetaInput) bool { select { case <-ctx.Done(): return false default: } wp.Add() go func(targetval *contextargs.MetaInput) { defer wp.Done() e.executeTemplatesOnTarget(ctx, templatesList, targetval, results) }(value) return true }) return results } // returns total requests count func getRequestCount(templates []*templates.Template) int { count := 0 for _, template := range templates { // ignore requests in workflows as total requests in workflow // depends on what templates will be called in workflow if len(template.Workflows) > 0 { continue } count += template.TotalRequests } return count } ================================================ FILE: pkg/core/executors.go ================================================ package core import ( "context" "sync" "sync/atomic" "time" "github.com/projectdiscovery/nuclei/v3/pkg/input/provider" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs" "github.com/projectdiscovery/nuclei/v3/pkg/scan" "github.com/projectdiscovery/nuclei/v3/pkg/templates" "github.com/projectdiscovery/nuclei/v3/pkg/templates/types" generalTypes "github.com/projectdiscovery/nuclei/v3/pkg/types" syncutil "github.com/projectdiscovery/utils/sync" ) // Executors are low level executors that deals with template execution on a target // executeAllSelfContained executes all self contained templates that do not use `target` func (e *Engine) executeAllSelfContained(ctx context.Context, alltemplates []*templates.Template, results *atomic.Bool, sg *sync.WaitGroup) { for _, v := range alltemplates { sg.Add(1) go func(template *templates.Template) { defer sg.Done() var err error var match bool ctx := scan.NewScanContext(ctx, contextargs.New(ctx)) if e.Callback != nil { if results, err := template.Executer.ExecuteWithResults(ctx); err == nil { for _, result := range results { e.Callback(result) } } match = true } else { match, err = template.Executer.Execute(ctx) } if err != nil { e.options.Logger.Warning().Msgf("[%s] Could not execute step (self-contained): %s\n", e.executerOpts.Colorizer.BrightBlue(template.ID), err) } results.CompareAndSwap(false, match) }(v) } } // executeTemplateWithTargets executes a given template on x targets (with a internal targetpool(i.e concurrency)) func (e *Engine) executeTemplateWithTargets(ctx context.Context, template *templates.Template, target provider.InputProvider, results *atomic.Bool) { if e.workPool == nil { e.workPool = e.GetWorkPool() } // Bounded worker pool using input concurrency pool := e.workPool.InputPool(template.Type()) workerCount := 1 if pool != nil && pool.Size > 0 { workerCount = pool.Size } var ( index uint32 ) e.executerOpts.ResumeCfg.Lock() currentInfo, ok := e.executerOpts.ResumeCfg.Current[template.ID] if !ok { currentInfo = &generalTypes.ResumeInfo{} e.executerOpts.ResumeCfg.Current[template.ID] = currentInfo } currentInfo.InitInFlight() resumeFromInfo, ok := e.executerOpts.ResumeCfg.ResumeFrom[template.ID] if !ok { resumeFromInfo = &generalTypes.ResumeInfo{} e.executerOpts.ResumeCfg.ResumeFrom[template.ID] = resumeFromInfo } e.executerOpts.ResumeCfg.Unlock() // track progression cleanupInFlight := func(index uint32) { currentInfo.Lock() delete(currentInfo.InFlight, index) currentInfo.Unlock() } // task represents a single target execution unit type task struct { index uint32 skip bool value *contextargs.MetaInput } tasks := make(chan task) var workersWg sync.WaitGroup workersWg.Add(workerCount) for i := 0; i < workerCount; i++ { go func() { defer workersWg.Done() for t := range tasks { func() { defer cleanupInFlight(t.index) select { case <-ctx.Done(): return default: } if t.skip { return } match, err := e.executeTemplateOnInput(ctx, template, t.value) if err != nil { e.options.Logger.Warning().Msgf("[%s] Could not execute step on %s: %s\n", template.ID, t.value.Input, err) } results.CompareAndSwap(false, match) }() } }() } target.Iterate(func(scannedValue *contextargs.MetaInput) bool { select { case <-ctx.Done(): return false // exit default: } // Best effort to track the host progression // skips indexes lower than the minimum in-flight at interruption time var skip bool if resumeFromInfo.IsCompleted() { // the template was completed e.options.Logger.Debug().Msgf("[%s] Skipping \"%s\": Resume - Template already completed", template.ID, scannedValue.Input) skip = true } else if index < resumeFromInfo.GetSkipUnder() { // index lower than the sliding window (bulk-size) e.options.Logger.Debug().Msgf("[%s] Skipping \"%s\": Resume - Target already processed", template.ID, scannedValue.Input) skip = true } else if resumeFromInfo.IsInFlight(index) { // the target wasn't completed successfully e.options.Logger.Debug().Msgf("[%s] Repeating \"%s\": Resume - Target wasn't completed", template.ID, scannedValue.Input) // skip is already false, but leaving it here for clarity skip = false } else if index > resumeFromInfo.GetDoAbove() { // index above the sliding window (bulk-size) // skip is already false - but leaving it here for clarity skip = false } currentInfo.Lock() currentInfo.InFlight[index] = struct{}{} currentInfo.Unlock() // Skip if the host has had errors if e.executerOpts.HostErrorsCache != nil && e.executerOpts.HostErrorsCache.Check(e.executerOpts.ProtocolType.String(), contextargs.NewWithMetaInput(ctx, scannedValue)) { skipEvent := &output.ResultEvent{ TemplateID: template.ID, TemplatePath: template.Path, Info: template.Info, Type: e.executerOpts.ProtocolType.String(), Host: scannedValue.Input, MatcherStatus: false, Error: "host was skipped as it was found unresponsive", Timestamp: time.Now(), } if e.Callback != nil { e.Callback(skipEvent) } else if e.executerOpts.Output != nil { _ = e.executerOpts.Output.Write(skipEvent) } return true } tasks <- task{index: index, skip: skip, value: scannedValue} index++ return true }) close(tasks) workersWg.Wait() // on completion marks the template as completed currentInfo.Lock() currentInfo.Completed = true currentInfo.Unlock() } // executeTemplatesOnTarget execute given templates on given single target func (e *Engine) executeTemplatesOnTarget(ctx context.Context, alltemplates []*templates.Template, target *contextargs.MetaInput, results *atomic.Bool) { // all templates are executed on single target // wp is workpool that contains different waitgroups for // headless and non-headless templates // global waitgroup should not be used here wp := e.GetWorkPool() defer wp.Wait() for _, tpl := range alltemplates { select { case <-ctx.Done(): return default: } // Check whether the target has already been marked as permanently // unresponsive by HostErrorsCache before spawning another goroutine. if e.executerOpts.HostErrorsCache != nil && e.executerOpts.HostErrorsCache.Check(e.executerOpts.ProtocolType.String(), contextargs.NewWithMetaInput(ctx, target)) { skipEvent := &output.ResultEvent{ TemplateID: tpl.ID, TemplatePath: tpl.Path, Info: tpl.Info, Type: e.executerOpts.ProtocolType.String(), Host: target.Input, MatcherStatus: false, Error: "host was skipped as it was found unresponsive", Timestamp: time.Now(), } if e.Callback != nil { e.Callback(skipEvent) } else if e.executerOpts.Output != nil { _ = e.executerOpts.Output.Write(skipEvent) } break } // resize check point - nop if there are no changes wp.RefreshWithConfig(e.GetWorkPoolConfig()) var sg *syncutil.AdaptiveWaitGroup if tpl.Type() == types.HeadlessProtocol { sg = wp.Headless } else { sg = wp.Default } sg.Add() go func(template *templates.Template, value *contextargs.MetaInput, wg *syncutil.AdaptiveWaitGroup) { defer wg.Done() match, err := e.executeTemplateOnInput(ctx, template, value) if err != nil { e.options.Logger.Warning().Msgf("[%s] Could not execute step on %s: %s\n", template.ID, value.Input, err) } results.CompareAndSwap(false, match) }(tpl, target, sg) } } // executeTemplateOnInput performs template execution for a single input and returns match status and error func (e *Engine) executeTemplateOnInput(ctx context.Context, template *templates.Template, value *contextargs.MetaInput) (bool, error) { ctxArgs := contextargs.New(ctx) ctxArgs.MetaInput = value scanCtx := scan.NewScanContext(ctx, ctxArgs) switch template.Type() { case types.WorkflowProtocol: return e.executeWorkflow(scanCtx, template.CompiledWorkflow), nil default: if e.Callback != nil { results, err := template.Executer.ExecuteWithResults(scanCtx) if err != nil { return false, err } for _, result := range results { e.Callback(result) } return len(results) > 0, nil } return template.Executer.Execute(scanCtx) } } ================================================ FILE: pkg/core/executors_test.go ================================================ package core import ( "context" "fmt" "sync/atomic" "testing" "time" inputtypes "github.com/projectdiscovery/nuclei/v3/pkg/input/types" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/protocols" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs" "github.com/projectdiscovery/nuclei/v3/pkg/scan" "github.com/projectdiscovery/nuclei/v3/pkg/templates" tmpltypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types" "github.com/projectdiscovery/nuclei/v3/pkg/types" ) // fakeExecuter is a simple stub for protocols.Executer used to test executeTemplateOnInput type fakeExecuter struct { withResults bool } func (f *fakeExecuter) Compile() error { return nil } func (f *fakeExecuter) Requests() int { return 1 } func (f *fakeExecuter) Execute(ctx *scan.ScanContext) (bool, error) { return !f.withResults, nil } func (f *fakeExecuter) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error) { if !f.withResults { return nil, nil } return []*output.ResultEvent{{Host: "h"}}, nil } // newTestEngine creates a minimal Engine for tests func newTestEngine() *Engine { return New(&types.Options{}) } func Test_executeTemplateOnInput_CallbackPath(t *testing.T) { e := newTestEngine() called := 0 e.Callback = func(*output.ResultEvent) { called++ } tpl := &templates.Template{} tpl.Executer = &fakeExecuter{withResults: true} ok, err := e.executeTemplateOnInput(context.Background(), tpl, &contextargs.MetaInput{Input: "x"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if !ok { t.Fatalf("expected match true") } if called == 0 { t.Fatalf("expected callback to be called") } } func Test_executeTemplateOnInput_ExecutePath(t *testing.T) { e := newTestEngine() tpl := &templates.Template{} tpl.Executer = &fakeExecuter{withResults: false} ok, err := e.executeTemplateOnInput(context.Background(), tpl, &contextargs.MetaInput{Input: "x"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if !ok { t.Fatalf("expected match true from Execute path") } } type fakeExecuterErr struct{} func (f *fakeExecuterErr) Compile() error { return nil } func (f *fakeExecuterErr) Requests() int { return 1 } func (f *fakeExecuterErr) Execute(ctx *scan.ScanContext) (bool, error) { return false, nil } func (f *fakeExecuterErr) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error) { return nil, fmt.Errorf("boom") } func Test_executeTemplateOnInput_CallbackErrorPropagates(t *testing.T) { e := newTestEngine() e.Callback = func(*output.ResultEvent) {} tpl := &templates.Template{} tpl.Executer = &fakeExecuterErr{} ok, err := e.executeTemplateOnInput(context.Background(), tpl, &contextargs.MetaInput{Input: "x"}) if err == nil { t.Fatalf("expected error to propagate") } if ok { t.Fatalf("expected match to be false on error") } } type fakeTargetProvider struct { values []*contextargs.MetaInput } func (f *fakeTargetProvider) Count() int64 { return int64(len(f.values)) } func (f *fakeTargetProvider) Iterate(cb func(value *contextargs.MetaInput) bool) { for _, v := range f.values { if !cb(v) { return } } } func (f *fakeTargetProvider) Set(string, string) {} func (f *fakeTargetProvider) SetWithProbe(string, string, inputtypes.InputLivenessProbe) error { return nil } func (f *fakeTargetProvider) SetWithExclusions(string, string) error { return nil } func (f *fakeTargetProvider) InputType() string { return "test" } func (f *fakeTargetProvider) Close() {} type slowExecuter struct{} func (s *slowExecuter) Compile() error { return nil } func (s *slowExecuter) Requests() int { return 1 } func (s *slowExecuter) Execute(ctx *scan.ScanContext) (bool, error) { select { case <-ctx.Context().Done(): return false, ctx.Context().Err() case <-time.After(200 * time.Millisecond): return true, nil } } func (s *slowExecuter) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error) { return nil, nil } func Test_executeTemplateWithTargets_RespectsCancellation(t *testing.T) { e := newTestEngine() e.SetExecuterOptions(&protocols.ExecutorOptions{Logger: e.Logger, ResumeCfg: types.NewResumeCfg(), ProtocolType: tmpltypes.HTTPProtocol}) tpl := &templates.Template{} tpl.Executer = &slowExecuter{} targets := &fakeTargetProvider{values: []*contextargs.MetaInput{{Input: "a"}, {Input: "b"}, {Input: "c"}}} ctx, cancel := context.WithCancel(context.Background()) cancel() var matched atomic.Bool e.executeTemplateWithTargets(ctx, tpl, targets, &matched) } ================================================ FILE: pkg/core/workflow_execute.go ================================================ package core import ( "fmt" "net/http/cookiejar" "sync/atomic" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs" "github.com/projectdiscovery/nuclei/v3/pkg/scan" "github.com/projectdiscovery/nuclei/v3/pkg/workflows" syncutil "github.com/projectdiscovery/utils/sync" ) const workflowStepExecutionError = "[%s] Could not execute workflow step: %s\n" // executeWorkflow runs a workflow on an input and returns true or false func (e *Engine) executeWorkflow(ctx *scan.ScanContext, w *workflows.Workflow) bool { results := &atomic.Bool{} // at this point we should be at the start root execution of a workflow tree, hence we create global shared instances workflowCookieJar, _ := cookiejar.New(nil) ctxArgs := contextargs.New(ctx.Context()) ctxArgs.MetaInput = ctx.Input.MetaInput ctxArgs.CookieJar = workflowCookieJar // we can know the nesting level only at runtime, so the best we can do here is increase template threads by one unit in case it's equal to 1 to allow // at least one subtemplate to go through, which it's idempotent to one in-flight template as the parent one is in an idle state templateThreads := w.Options.Options.TemplateThreads if templateThreads == 1 { templateThreads++ } swg, _ := syncutil.New(syncutil.WithSize(templateThreads)) for _, template := range w.Workflows { newCtx := scan.NewScanContext(ctx.Context(), ctx.Input.Clone()) if err := e.runWorkflowStep(template, newCtx, results, swg, w); err != nil { gologger.Warning().Msgf(workflowStepExecutionError, template.Template, err) } } swg.Wait() return results.Load() } // runWorkflowStep runs a workflow step for the workflow. It executes the workflow // in a recursive manner running all subtemplates and matchers. func (e *Engine) runWorkflowStep(template *workflows.WorkflowTemplate, ctx *scan.ScanContext, results *atomic.Bool, swg *syncutil.AdaptiveWaitGroup, w *workflows.Workflow) error { var firstMatched bool var err error var mainErr error if len(template.Matchers) == 0 { for _, executer := range template.Executers { executer.Options.Progress.AddToTotal(int64(executer.Executer.Requests())) // Don't print results with subtemplates, only print results on template. if len(template.Subtemplates) > 0 { ctx.OnResult = func(result *output.InternalWrappedEvent) { if result.OperatorsResult == nil { return } if len(result.Results) > 0 { firstMatched = true } if result.OperatorsResult != nil && result.OperatorsResult.Extracts != nil { for k, v := range result.OperatorsResult.Extracts { // normalize items: switch len(v) { case 0, 1: // - key:[item] => key: item ctx.Input.Set(k, v[0]) default: // - key:[item_0, ..., item_n] => key0:item_0, keyn:item_n for vIdx, vVal := range v { normalizedKIdx := fmt.Sprintf("%s%d", k, vIdx) ctx.Input.Set(normalizedKIdx, vVal) } // also add the original name with full slice ctx.Input.Set(k, v) } } } } _, err = executer.Executer.ExecuteWithResults(ctx) } else { var matched bool matched, err = executer.Executer.Execute(ctx) if matched { firstMatched = true } } if w.Options.HostErrorsCache != nil { w.Options.HostErrorsCache.MarkFailedOrRemove(w.Options.ProtocolType.String(), ctx.Input, err) } if err != nil { if len(template.Executers) == 1 { mainErr = err } else { gologger.Warning().Msgf(workflowStepExecutionError, template.Template, err) } continue } } } if len(template.Subtemplates) == 0 { results.CompareAndSwap(false, firstMatched) } if len(template.Matchers) > 0 { for _, executer := range template.Executers { executer.Options.Progress.AddToTotal(int64(executer.Executer.Requests())) ctx.OnResult = func(event *output.InternalWrappedEvent) { if event.OperatorsResult == nil { return } if event.OperatorsResult.Extracts != nil { for k, v := range event.OperatorsResult.Extracts { ctx.Input.Set(k, v) } } for _, matcher := range template.Matchers { if !matcher.Match(event.OperatorsResult) { continue } for _, subtemplate := range matcher.Subtemplates { swg.Add() go func(subtemplate *workflows.WorkflowTemplate) { defer swg.Done() // create a new context with the same input but with unset callbacks // clone the Input so that other parallel executions won't overwrite the shared variables when subsequent templates are running subCtx := scan.NewScanContext(ctx.Context(), ctx.Input.Clone()) if err := e.runWorkflowStep(subtemplate, subCtx, results, swg, w); err != nil { gologger.Warning().Msgf(workflowStepExecutionError, subtemplate.Template, err) } }(subtemplate) } } } _, err := executer.Executer.ExecuteWithResults(ctx) if err != nil { if len(template.Executers) == 1 { mainErr = err } else { gologger.Warning().Msgf(workflowStepExecutionError, template.Template, err) } continue } } return mainErr } if len(template.Subtemplates) > 0 && firstMatched { for _, subtemplate := range template.Subtemplates { swg.Add() go func(template *workflows.WorkflowTemplate) { // create a new context with the same input but with unset callbacks subCtx := scan.NewScanContext(ctx.Context(), ctx.Input) if err := e.runWorkflowStep(template, subCtx, results, swg, w); err != nil { gologger.Warning().Msgf(workflowStepExecutionError, template.Template, err) } swg.Done() }(subtemplate) } } return mainErr } ================================================ FILE: pkg/core/workflow_execute_test.go ================================================ package core import ( "context" "testing" "github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice" "github.com/projectdiscovery/nuclei/v3/pkg/operators" "github.com/projectdiscovery/nuclei/v3/pkg/output" "github.com/projectdiscovery/nuclei/v3/pkg/progress" "github.com/projectdiscovery/nuclei/v3/pkg/protocols" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs" "github.com/projectdiscovery/nuclei/v3/pkg/scan" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/nuclei/v3/pkg/workflows" "github.com/stretchr/testify/require" ) func TestWorkflowsSimple(t *testing.T) { progressBar, _ := progress.NewStatsTicker(0, false, false, false, 0) workflow := &workflows.Workflow{Options: &protocols.ExecutorOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{ {Executers: []*workflows.ProtocolExecuterPair{{ Executer: &mockExecuter{result: true}, Options: &protocols.ExecutorOptions{Progress: progressBar}}, }}, }} engine := &Engine{} input := contextargs.NewWithInput(context.Background(), "https://test.com") ctx := scan.NewScanContext(context.Background(), input) matched := engine.executeWorkflow(ctx, workflow) require.True(t, matched, "could not get correct match value") } func TestWorkflowsSimpleMultiple(t *testing.T) { progressBar, _ := progress.NewStatsTicker(0, false, false, false, 0) var firstInput, secondInput string workflow := &workflows.Workflow{Options: &protocols.ExecutorOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{ {Executers: []*workflows.ProtocolExecuterPair{{ Executer: &mockExecuter{result: true, executeHook: func(input *contextargs.MetaInput) { firstInput = input.Input }}, Options: &protocols.ExecutorOptions{Progress: progressBar}}, }}, {Executers: []*workflows.ProtocolExecuterPair{{ Executer: &mockExecuter{result: true, executeHook: func(input *contextargs.MetaInput) { secondInput = input.Input }}, Options: &protocols.ExecutorOptions{Progress: progressBar}}, }}, }} engine := &Engine{} input := contextargs.NewWithInput(context.Background(), "https://test.com") ctx := scan.NewScanContext(context.Background(), input) matched := engine.executeWorkflow(ctx, workflow) require.True(t, matched, "could not get correct match value") require.Equal(t, "https://test.com", firstInput, "could not get correct first input") require.Equal(t, "https://test.com", secondInput, "could not get correct second input") } func TestWorkflowsSubtemplates(t *testing.T) { progressBar, _ := progress.NewStatsTicker(0, false, false, false, 0) var firstInput, secondInput string workflow := &workflows.Workflow{Options: &protocols.ExecutorOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{ {Executers: []*workflows.ProtocolExecuterPair{{ Executer: &mockExecuter{result: true, executeHook: func(input *contextargs.MetaInput) { firstInput = input.Input }, outputs: []*output.InternalWrappedEvent{ {OperatorsResult: &operators.Result{}, Results: []*output.ResultEvent{{}}}, }}, Options: &protocols.ExecutorOptions{Progress: progressBar}}, }, Subtemplates: []*workflows.WorkflowTemplate{{Executers: []*workflows.ProtocolExecuterPair{{ Executer: &mockExecuter{result: true, executeHook: func(input *contextargs.MetaInput) { secondInput = input.Input }}, Options: &protocols.ExecutorOptions{Progress: progressBar}}, }}}}, }} engine := &Engine{} input := contextargs.NewWithInput(context.Background(), "https://test.com") ctx := scan.NewScanContext(context.Background(), input) matched := engine.executeWorkflow(ctx, workflow) require.True(t, matched, "could not get correct match value") require.Equal(t, "https://test.com", firstInput, "could not get correct first input") require.Equal(t, "https://test.com", secondInput, "could not get correct second input") } func TestWorkflowsSubtemplatesNoMatch(t *testing.T) { progressBar, _ := progress.NewStatsTicker(0, false, false, false, 0) var firstInput, secondInput string workflow := &workflows.Workflow{Options: &protocols.ExecutorOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{ {Executers: []*workflows.ProtocolExecuterPair{{ Executer: &mockExecuter{result: false, executeHook: func(input *contextargs.MetaInput) { firstInput = input.Input }}, Options: &protocols.ExecutorOptions{Progress: progressBar}}, }, Subtemplates: []*workflows.WorkflowTemplate{{Executers: []*workflows.ProtocolExecuterPair{{ Executer: &mockExecuter{result: true, executeHook: func(input *contextargs.MetaInput) { secondInput = input.Input }}, Options: &protocols.ExecutorOptions{Progress: progressBar}}, }}}}, }} engine := &Engine{} input := contextargs.NewWithInput(context.Background(), "https://test.com") ctx := scan.NewScanContext(context.Background(), input) matched := engine.executeWorkflow(ctx, workflow) require.False(t, matched, "could not get correct match value") require.Equal(t, "https://test.com", firstInput, "could not get correct first input") require.Equal(t, "", secondInput, "could not get correct second input") } func TestWorkflowsSubtemplatesWithMatcher(t *testing.T) { progressBar, _ := progress.NewStatsTicker(0, false, false, false, 0) var firstInput, secondInput string workflow := &workflows.Workflow{Options: &protocols.ExecutorOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{ {Executers: []*workflows.ProtocolExecuterPair{{ Executer: &mockExecuter{result: true, executeHook: func(input *contextargs.MetaInput) { firstInput = input.Input }, outputs: []*output.InternalWrappedEvent{ {OperatorsResult: &operators.Result{ Matches: map[string][]string{"tomcat": {}}, Extracts: map[string][]string{}, }}, }}, Options: &protocols.ExecutorOptions{Progress: progressBar}}, }, Matchers: []*workflows.Matcher{{Name: stringslice.StringSlice{Value: "tomcat"}, Subtemplates: []*workflows.WorkflowTemplate{{Executers: []*workflows.ProtocolExecuterPair{{ Executer: &mockExecuter{result: true, executeHook: func(input *contextargs.MetaInput) { secondInput = input.Input }}, Options: &protocols.ExecutorOptions{Progress: progressBar}}, }}}}}}, }} engine := &Engine{} input := contextargs.NewWithInput(context.Background(), "https://test.com") ctx := scan.NewScanContext(context.Background(), input) matched := engine.executeWorkflow(ctx, workflow) require.True(t, matched, "could not get correct match value") require.Equal(t, "https://test.com", firstInput, "could not get correct first input") require.Equal(t, "https://test.com", secondInput, "could not get correct second input") } func TestWorkflowsSubtemplatesWithMatcherNoMatch(t *testing.T) { progressBar, _ := progress.NewStatsTicker(0, false, false, false, 0) var firstInput, secondInput string workflow := &workflows.Workflow{Options: &protocols.ExecutorOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{ {Executers: []*workflows.ProtocolExecuterPair{{ Executer: &mockExecuter{result: true, executeHook: func(input *contextargs.MetaInput) { firstInput = input.Input }, outputs: []*output.InternalWrappedEvent{ {OperatorsResult: &operators.Result{ Matches: map[string][]string{"tomcat": {}}, Extracts: map[string][]string{}, }}, }}, Options: &protocols.ExecutorOptions{Progress: progressBar}}, }, Matchers: []*workflows.Matcher{{Name: stringslice.StringSlice{Value: "apache"}, Subtemplates: []*workflows.WorkflowTemplate{{Executers: []*workflows.ProtocolExecuterPair{{ Executer: &mockExecuter{result: true, executeHook: func(input *contextargs.MetaInput) { secondInput = input.Input }}, Options: &protocols.ExecutorOptions{Progress: progressBar}}, }}}}}}, }} engine := &Engine{} input := contextargs.NewWithInput(context.Background(), "https://test.com") ctx := scan.NewScanContext(context.Background(), input) matched := engine.executeWorkflow(ctx, workflow) require.False(t, matched, "could not get correct match value") require.Equal(t, "https://test.com", firstInput, "could not get correct first input") require.Equal(t, "", secondInput, "could not get correct second input") } type mockExecuter struct { result bool executeHook func(input *contextargs.MetaInput) outputs []*output.InternalWrappedEvent } // Compile compiles the execution generators preparing any requests possible. func (m *mockExecuter) Compile() error { return nil } // Requests returns the total number of requests the rule will perform func (m *mockExecuter) Requests() int { return 1 } // Execute executes the protocol group and returns true or false if results were found. func (m *mockExecuter) Execute(ctx *scan.ScanContext) (bool, error) { if m.executeHook != nil { m.executeHook(ctx.Input.MetaInput) } return m.result, nil } // ExecuteWithResults executes the protocol requests and returns results instead of writing them. func (m *mockExecuter) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error) { if m.executeHook != nil { m.executeHook(ctx.Input.MetaInput) } for _, output := range m.outputs { ctx.LogEvent(output) } return ctx.GenerateResult(), nil } ================================================ FILE: pkg/core/workpool.go ================================================ package core import ( "context" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/templates/types" syncutil "github.com/projectdiscovery/utils/sync" ) // WorkPool implements an execution pool for executing different // types of task with different concurrency requirements. // // It also allows Configuration of such requirements. This is used // for per-module like separate headless concurrency etc. type WorkPool struct { Headless *syncutil.AdaptiveWaitGroup Default *syncutil.AdaptiveWaitGroup config WorkPoolConfig } // WorkPoolConfig is the configuration for work pool type WorkPoolConfig struct { // InputConcurrency is the concurrency for inputs values. InputConcurrency int // TypeConcurrency is the concurrency for the request type templates. TypeConcurrency int // HeadlessInputConcurrency is the concurrency for headless inputs values. HeadlessInputConcurrency int // TypeConcurrency is the concurrency for the headless request type templates. HeadlessTypeConcurrency int } // NewWorkPool returns a new WorkPool instance func NewWorkPool(config WorkPoolConfig) *WorkPool { headlessWg, _ := syncutil.New(syncutil.WithSize(config.HeadlessTypeConcurrency)) defaultWg, _ := syncutil.New(syncutil.WithSize(config.TypeConcurrency)) return &WorkPool{ config: config, Headless: headlessWg, Default: defaultWg, } } // Wait waits for all the work pool wait groups to finish func (w *WorkPool) Wait() { w.Default.Wait() w.Headless.Wait() } // InputPool returns a work pool for an input type func (w *WorkPool) InputPool(templateType types.ProtocolType) *syncutil.AdaptiveWaitGroup { var count int if templateType == types.HeadlessProtocol { count = w.config.HeadlessInputConcurrency } else { count = w.config.InputConcurrency } swg, _ := syncutil.New(syncutil.WithSize(count)) return swg } func (w *WorkPool) RefreshWithConfig(config WorkPoolConfig) { if w.config.TypeConcurrency != config.TypeConcurrency { w.config.TypeConcurrency = config.TypeConcurrency } if w.config.HeadlessTypeConcurrency != config.HeadlessTypeConcurrency { w.config.HeadlessTypeConcurrency = config.HeadlessTypeConcurrency } if w.config.InputConcurrency != config.InputConcurrency { w.config.InputConcurrency = config.InputConcurrency } if w.config.HeadlessInputConcurrency != config.HeadlessInputConcurrency { w.config.HeadlessInputConcurrency = config.HeadlessInputConcurrency } w.Refresh(context.Background()) } func (w *WorkPool) Refresh(ctx context.Context) { if w.Default.Size != w.config.TypeConcurrency { if err := w.Default.Resize(ctx, w.config.TypeConcurrency); err != nil { gologger.Warning().Msgf("Could not resize workpool: %s\n", err) } } if w.Headless.Size != w.config.HeadlessTypeConcurrency { if err := w.Headless.Resize(ctx, w.config.HeadlessTypeConcurrency); err != nil { gologger.Warning().Msgf("Could not resize workpool: %s\n", err) } } } ================================================ FILE: pkg/external/customtemplates/azure_blob.go ================================================ package customtemplates import ( "bytes" "context" "os" "path/filepath" "strings" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/utils/errkit" ) var _ Provider = &customTemplateAzureBlob{} type customTemplateAzureBlob struct { azureBlobClient *azblob.Client containerName string } // NewAzureProviders creates a new Azure Blob Storage provider for downloading custom templates func NewAzureProviders(options *types.Options) ([]*customTemplateAzureBlob, error) { providers := []*customTemplateAzureBlob{} if options.AzureContainerName != "" && !options.AzureTemplateDisableDownload { // Establish a connection to Azure and build a client object with which to download templates from Azure Blob Storage azClient, err := getAzureBlobClient(options.AzureTenantID, options.AzureClientID, options.AzureClientSecret, options.AzureServiceURL) if err != nil { errx := errkit.FromError(err) errx.Msgf("Error establishing Azure Blob client for %s", options.AzureContainerName) return nil, errx } // Create a new Azure Blob Storage container object azTemplateContainer := &customTemplateAzureBlob{ azureBlobClient: azClient, containerName: options.AzureContainerName, } // Add the Azure Blob Storage container object to the list of custom templates providers = append(providers, azTemplateContainer) } return providers, nil } func getAzureBlobClient(tenantID string, clientID string, clientSecret string, serviceURL string) (*azblob.Client, error) { // Create an Azure credential using the provided credentials credentials, err := azidentity.NewClientSecretCredential(tenantID, clientID, clientSecret, nil) if err != nil { gologger.Error().Msgf("Invalid Azure credentials: %v", err) return nil, err } // Create a client to manage Azure Blob Storage client, err := azblob.NewClient(serviceURL, credentials, nil) if err != nil { gologger.Error().Msgf("Error creating Azure Blob client: %v", err) return nil, err } return client, nil } func (bk *customTemplateAzureBlob) Download(ctx context.Context) { // Set an incrementer for the number of templates downloaded var templatesDownloaded = 0 // Define the local path to which the templates will be downloaded downloadPath := filepath.Join(config.DefaultConfig.CustomAzureTemplatesDirectory, bk.containerName) // Get the list of all templates from the container pager := bk.azureBlobClient.NewListBlobsFlatPager(bk.containerName, &azblob.ListBlobsFlatOptions{ // Don't include previous versions of the templates if versioning is enabled on the container Include: azblob.ListBlobsInclude{Snapshots: false, Versions: false}, }) // Loop through the list of blobs in the container and determine if they should be added to the list of templates // to be returned, and subsequently downloaded for pager.More() { resp, err := pager.NextPage(context.TODO()) if err != nil { gologger.Error().Msgf("Error listing templates in Azure Blob container: %v", err) return } for _, blob := range resp.Segment.BlobItems { // If the blob is a .yaml download the file to the local filesystem if strings.HasSuffix(*blob.Name, ".yaml") { // Download the template to the local filesystem at the downloadPath err := downloadTemplate(bk.azureBlobClient, bk.containerName, *blob.Name, filepath.Join(downloadPath, *blob.Name), ctx) if err != nil { gologger.Error().Msgf("Error downloading template: %v", err) } else { // Increment the number of templates downloaded templatesDownloaded++ } } } } // Log the number of templates downloaded gologger.Info().Msgf("Downloaded %d templates from Azure Blob Storage container '%s' to: %s", templatesDownloaded, bk.containerName, downloadPath) } // Update updates the templates from the Azure Blob Storage container to the local filesystem. This is effectively a // wrapper of the Download function which downloads of all templates from the container and doesn't manage a // differential update. func (bk *customTemplateAzureBlob) Update(ctx context.Context) { // Treat the update as a download of all templates from the container bk.Download(ctx) } // downloadTemplate downloads a template from the Azure Blob Storage container to the local filesystem with the provided // blob path and outputPath. func downloadTemplate(client *azblob.Client, containerName string, path string, outputPath string, ctx context.Context) error { // Download the blob as a byte stream get, err := client.DownloadStream(ctx, containerName, path, nil) if err != nil { gologger.Error().Msgf("Error downloading template: %v", err) return err } downloadedData := bytes.Buffer{} retryReader := get.NewRetryReader(ctx, &azblob.RetryReaderOptions{}) _, err = downloadedData.ReadFrom(retryReader) if err != nil { gologger.Error().Msgf("Error reading template: %v", err) return err } err = retryReader.Close() if err != nil { gologger.Error().Msgf("Error closing template filestream: %v", err) return err } // Ensure the directory exists err = os.MkdirAll(filepath.Dir(outputPath), 0755) if err != nil { gologger.Error().Msgf("Error creating directory: %v", err) return err } // Write the downloaded template to the local filesystem at the outputPath with the filename of the blob name err = os.WriteFile(outputPath, downloadedData.Bytes(), 0644) return err } ================================================ FILE: pkg/external/customtemplates/github.go ================================================ package customtemplates import ( "context" httpclient "net/http" "path/filepath" "strings" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/google/go-github/github" "github.com/pkg/errors" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/utils/errkit" fileutil "github.com/projectdiscovery/utils/file" folderutil "github.com/projectdiscovery/utils/folder" "golang.org/x/oauth2" ) var _ Provider = &customTemplateGitHubRepo{} type customTemplateGitHubRepo struct { owner string reponame string gitCloneURL string githubToken string } // This function download the custom github template repository func (customTemplate *customTemplateGitHubRepo) Download(ctx context.Context) { clonePath := customTemplate.getLocalRepoClonePath(config.DefaultConfig.CustomGitHubTemplatesDirectory) if !fileutil.FolderExists(clonePath) { err := customTemplate.cloneRepo(clonePath, customTemplate.githubToken) if err != nil { gologger.Error().Msgf("%s", err) } else { gologger.Info().Msgf("Repo %s/%s cloned successfully at %s", customTemplate.owner, customTemplate.reponame, clonePath) } return } } func (customTemplate *customTemplateGitHubRepo) Update(ctx context.Context) { downloadPath := config.DefaultConfig.CustomGitHubTemplatesDirectory clonePath := customTemplate.getLocalRepoClonePath(downloadPath) // If folder does not exist then clone/download the repo if !fileutil.FolderExists(clonePath) { customTemplate.Download(ctx) return } // Attempt to pull changes and handle the result customTemplate.handlePullChanges(clonePath) } // handlePullChanges attempts to pull changes and logs the appropriate message func (customTemplate *customTemplateGitHubRepo) handlePullChanges(clonePath string) { err := customTemplate.pullChanges(clonePath, customTemplate.githubToken) switch { case err == nil: customTemplate.logPullSuccess() case errors.Is(err, git.NoErrAlreadyUpToDate): customTemplate.logAlreadyUpToDate(err) default: customTemplate.logPullError(err) } } // logPullSuccess logs a success message when changes are pulled func (customTemplate *customTemplateGitHubRepo) logPullSuccess() { gologger.Info().Msgf("Repo %s/%s successfully pulled the changes.\n", customTemplate.owner, customTemplate.reponame) } // logAlreadyUpToDate logs an info message when repo is already up to date func (customTemplate *customTemplateGitHubRepo) logAlreadyUpToDate(err error) { gologger.Info().Msgf("%s", err) } // logPullError logs an error message when pull fails func (customTemplate *customTemplateGitHubRepo) logPullError(err error) { gologger.Error().Msgf("%s", err) } // NewGitHubProviders returns new instance of GitHub providers for downloading custom templates func NewGitHubProviders(options *types.Options) ([]*customTemplateGitHubRepo, error) { providers := []*customTemplateGitHubRepo{} gitHubClient := getGHClientIncognito() if options.GitHubTemplateDisableDownload { return providers, nil } for _, repoName := range options.GitHubTemplateRepo { owner, repo, err := getOwnerAndRepo(repoName) if err != nil { gologger.Error().Msgf("%s", err) continue } githubRepo, err := getGitHubRepo(gitHubClient, owner, repo, options.GitHubToken) if err != nil { gologger.Error().Msgf("%s", err) continue } customTemplateRepo := &customTemplateGitHubRepo{ owner: owner, reponame: repo, gitCloneURL: githubRepo.GetCloneURL(), githubToken: options.GitHubToken, } providers = append(providers, customTemplateRepo) customTemplateRepo.restructureRepoDir() } return providers, nil } func (customTemplateRepo *customTemplateGitHubRepo) restructureRepoDir() { customGitHubTemplatesDirectory := config.DefaultConfig.CustomGitHubTemplatesDirectory oldRepoClonePath := filepath.Join(customGitHubTemplatesDirectory, customTemplateRepo.reponame+"-"+customTemplateRepo.owner) newRepoClonePath := customTemplateRepo.getLocalRepoClonePath(customGitHubTemplatesDirectory) if fileutil.FolderExists(oldRepoClonePath) && !fileutil.FolderExists(newRepoClonePath) { _ = folderutil.SyncDirectory(oldRepoClonePath, newRepoClonePath) } } // getOwnerAndRepo returns the owner, repo, err from the given string // e.g., it takes input projectdiscovery/nuclei-templates and // returns owner => projectdiscovery, repo => nuclei-templates func getOwnerAndRepo(reponame string) (owner string, repo string, err error) { s := strings.Split(reponame, "/") if len(s) != 2 { err = errors.Errorf("wrong Repo name: %s", reponame) return } owner = s[0] repo = s[1] return } // returns *github.Repository if passed github repo name func getGitHubRepo(gitHubClient *github.Client, repoOwner, repoName, githubToken string) (*github.Repository, error) { var retried bool getRepo: repo, _, err := gitHubClient.Repositories.Get(context.Background(), repoOwner, repoName) if err != nil { // retry with authentication if gitHubClient = getGHClientWithToken(githubToken); gitHubClient != nil && !retried { retried = true goto getRepo } return nil, err } if repo == nil { return nil, errors.Errorf("problem getting repository: %s/%s", repoOwner, repoName) } return repo, nil } // download the git repo to a given path func (ctr *customTemplateGitHubRepo) cloneRepo(clonePath, githubToken string) error { cloneOpts := &git.CloneOptions{ URL: ctr.gitCloneURL, Auth: getAuth(ctr.owner, githubToken), SingleBranch: true, Depth: 1, } err := cloneOpts.Validate() if err != nil { return err } r, err := git.PlainClone(clonePath, false, cloneOpts) if err != nil { return errors.Errorf("%s/%s: %s", ctr.owner, ctr.reponame, err.Error()) } // Add the user as well in the config. By default, user is not set config, _ := r.Storer.Config() config.User.Name = ctr.owner return r.SetConfig(config) } // performs the git pull on given repo func (ctr *customTemplateGitHubRepo) pullChanges(repoPath, githubToken string) error { pullOpts := &git.PullOptions{ RemoteName: "origin", Auth: getAuth(ctr.owner, githubToken), SingleBranch: true, Depth: 1, } err := pullOpts.Validate() if err != nil { return err } r, err := git.PlainOpen(repoPath) if err != nil { return err } w, err := r.Worktree() if err != nil { return err } err = w.Pull(pullOpts) if err != nil { return errkit.Wrapf(err, "%s/%s", ctr.owner, ctr.reponame) } return nil } // All Custom github repos are cloned in the format of 'owner/reponame' for uniqueness func (ctr *customTemplateGitHubRepo) getLocalRepoClonePath(downloadPath string) string { return filepath.Join(downloadPath, ctr.owner, ctr.reponame) } // returns the auth object with username and github token as password func getAuth(username, password string) *http.BasicAuth { if username != "" && password != "" { return &http.BasicAuth{Username: username, Password: password} } return nil } func getGHClientWithToken(token string) *github.Client { if token != "" { ctx := context.Background() ts := oauth2.StaticTokenSource( &oauth2.Token{AccessToken: token}, ) oauthClient := oauth2.NewClient(ctx, ts) return github.NewClient(oauthClient) } return nil } func getGHClientIncognito() *github.Client { var tc *httpclient.Client return github.NewClient(tc) } ================================================ FILE: pkg/external/customtemplates/github_test.go ================================================ package customtemplates import ( "bytes" "context" "path/filepath" "strings" "testing" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/gologger/levels" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/testutils" "github.com/projectdiscovery/nuclei/v3/pkg/utils" "github.com/stretchr/testify/require" ) func TestDownloadCustomTemplatesFromGitHub(t *testing.T) { // Capture output to check for rate limit errors outputBuffer := &bytes.Buffer{} gologger.DefaultLogger.SetWriter(&utils.CaptureWriter{Buffer: outputBuffer}) gologger.DefaultLogger.SetMaxLevel(levels.LevelDebug) templatesDirectory := t.TempDir() config.DefaultConfig.SetTemplatesDir(templatesDirectory) options := testutils.DefaultOptions options.GitHubTemplateRepo = []string{"projectdiscovery/nuclei-templates-test"} ctm, err := NewCustomTemplatesManager(options) require.Nil(t, err, "could not create custom templates manager") ctm.Download(context.Background()) // Check if output contains rate limit error and skip test if so output := outputBuffer.String() if strings.Contains(output, "API rate limit exceeded") { t.Skip("GitHub API rate limit exceeded, skipping test") } require.DirExists(t, filepath.Join(templatesDirectory, "github", "projectdiscovery", "nuclei-templates-test"), "cloned directory does not exists") } ================================================ FILE: pkg/external/customtemplates/gitlab.go ================================================ package customtemplates import ( "context" "encoding/base64" "os" "path/filepath" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/utils/errkit" gitlab "gitlab.com/gitlab-org/api/client-go" ) var _ Provider = &customTemplateGitLabRepo{} type customTemplateGitLabRepo struct { gitLabClient *gitlab.Client serverURL string projectIDs []int } // NewGitLabProviders returns a new list of GitLab providers for downloading custom templates func NewGitLabProviders(options *types.Options) ([]*customTemplateGitLabRepo, error) { providers := []*customTemplateGitLabRepo{} if options.GitLabToken != "" && !options.GitLabTemplateDisableDownload { // Establish a connection to GitLab and build a client object with which to download templates from GitLab gitLabClient, err := getGitLabClient(options.GitLabServerURL, options.GitLabToken) if err != nil { errx := errkit.FromError(err) errx.Msgf("Error establishing GitLab client for %s %s", options.GitLabServerURL, err) return nil, errx } // Create a new GitLab service client gitLabContainer := &customTemplateGitLabRepo{ gitLabClient: gitLabClient, serverURL: options.GitLabServerURL, projectIDs: options.GitLabTemplateRepositoryIDs, } // Add the GitLab service client to the list of custom templates providers = append(providers, gitLabContainer) } return providers, nil } // Download downloads all .yaml files from a GitLab repository func (bk *customTemplateGitLabRepo) Download(_ context.Context) { // Define the project and template count var projectCount = 0 var templateCount = 0 // Append the GitLab directory to the location location := config.DefaultConfig.CustomGitLabTemplatesDirectory // Ensure the CustomGitLabTemplateDirectory directory exists or create it if it doesn't yet exist err := os.MkdirAll(filepath.Dir(location), 0755) if err != nil { gologger.Error().Msgf("Error creating directory: %v", err) return } // Get the projects from the GitLab serverURL for _, projectID := range bk.projectIDs { // Get the project information from the GitLab serverURL to get the default branch and the project name project, _, err := bk.gitLabClient.Projects.GetProject(projectID, nil) if err != nil { gologger.Error().Msgf("error retrieving GitLab project: %s %s", project, err) return } // Add a subdirectory with the project ID as the subdirectory within the location projectOutputPath := filepath.Join(location, project.Path) // Ensure the subdirectory exists or create it if it doesn't yet exist err = os.MkdirAll(projectOutputPath, 0755) if err != nil { gologger.Error().Msgf("Error creating subdirectory: %v", err) return } // Get the directory listing for the files in the project tree, _, err := bk.gitLabClient.Repositories.ListTree(projectID, &gitlab.ListTreeOptions{ Ref: gitlab.Ptr(project.DefaultBranch), Recursive: gitlab.Ptr(true), }) if err != nil { gologger.Error().Msgf("error retrieving files from GitLab project: %s (%d) %s", project.Name, projectID, err) } // Loop through the tree and download the files for _, file := range tree { // If the object is not a file or file extension is not .yaml, skip it if file.Type == "blob" && filepath.Ext(file.Path) == ".yaml" { gf := &gitlab.GetFileOptions{ Ref: gitlab.Ptr(project.DefaultBranch), } f, _, err := bk.gitLabClient.RepositoryFiles.GetFile(projectID, file.Path, gf) if err != nil { gologger.Error().Msgf("error retrieving GitLab project file: %d %s", projectID, err) return } // Decode the file content from base64 into bytes so that it can be written to the local filesystem contents, err := base64.StdEncoding.DecodeString(f.Content) if err != nil { gologger.Error().Msgf("error decoding GitLab project (%s) file: %s %s", project.Name, f.FileName, err) return } // Write the downloaded template to the local filesystem at the location with the filename of the blob name err = os.WriteFile(filepath.Join(projectOutputPath, f.FileName), contents, 0644) if err != nil { gologger.Error().Msgf("error writing GitLab project (%s) file: %s %s", project.Name, f.FileName, err) return } // Increment the number of templates downloaded templateCount++ } } // Increment the number of projects downloaded projectCount++ gologger.Info().Msgf("GitLab project '%s' (%d) cloned successfully", project.Name, projectID) } // Print the number of projects and templates downloaded gologger.Info().Msgf("%d templates downloaded from %d GitLab project(s) to: %s", templateCount, projectCount, location) } // Update is a wrapper around Download since it doesn't maintain a diff of the templates downloaded versus in the // repository for simplicity. func (bk *customTemplateGitLabRepo) Update(ctx context.Context) { if len(bk.projectIDs) == 0 { // No projects to download or update return } bk.Download(ctx) } // getGitLabClient returns a GitLab client for the given serverURL and token func getGitLabClient(server string, token string) (*gitlab.Client, error) { client, err := gitlab.NewClient(token, gitlab.WithBaseURL(server)) return client, err } ================================================ FILE: pkg/external/customtemplates/s3.go ================================================ package customtemplates import ( "context" "os" "path/filepath" "strings" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/feature/s3/manager" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/projectdiscovery/gologger" nucleiConfig "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/utils/errkit" stringsutil "github.com/projectdiscovery/utils/strings" ) var _ Provider = &customTemplateS3Bucket{} type customTemplateS3Bucket struct { s3Client *s3.Client bucketName string prefix string Location string } // Download retrieves all custom templates from s3 bucket func (bk *customTemplateS3Bucket) Download(ctx context.Context) { downloadPath := filepath.Join(nucleiConfig.DefaultConfig.CustomS3TemplatesDirectory, bk.bucketName) s3Manager := manager.NewDownloader(bk.s3Client) paginator := s3.NewListObjectsV2Paginator(bk.s3Client, &s3.ListObjectsV2Input{ Bucket: &bk.bucketName, Prefix: &bk.prefix, }) for paginator.HasMorePages() { page, err := paginator.NextPage(context.TODO()) if err != nil { gologger.Error().Msgf("error downloading s3 bucket %s %s", bk.bucketName, err) return } for _, obj := range page.Contents { if err := downloadToFile(s3Manager, downloadPath, bk.bucketName, aws.ToString(obj.Key)); err != nil { gologger.Error().Msgf("error downloading s3 bucket %s %s", bk.bucketName, err) return } } } gologger.Info().Msgf("AWS bucket %s was cloned successfully at %s", bk.bucketName, downloadPath) } // Update downloads custom templates from s3 bucket func (bk *customTemplateS3Bucket) Update(ctx context.Context) { bk.Download(ctx) } // NewS3Providers returns a new instances of a s3 providers for downloading custom templates func NewS3Providers(options *types.Options) ([]*customTemplateS3Bucket, error) { providers := []*customTemplateS3Bucket{} if options.AwsBucketName != "" && !options.AwsTemplateDisableDownload { s3c, err := getS3Client(context.TODO(), options.AwsAccessKey, options.AwsSecretKey, options.AwsRegion, options.AwsProfile) if err != nil { errx := errkit.FromError(err) errx.Msgf("error downloading s3 bucket %s", options.AwsBucketName) return nil, errx } ctBucket := &customTemplateS3Bucket{ bucketName: options.AwsBucketName, s3Client: s3c, } if strings.Contains(options.AwsBucketName, "/") { bPath := strings.SplitN(options.AwsBucketName, "/", 2) ctBucket.bucketName = bPath[0] ctBucket.prefix = bPath[1] } providers = append(providers, ctBucket) } return providers, nil } func downloadToFile(downloader *manager.Downloader, targetDirectory, bucket, key string) error { // Create the directories in the path file := filepath.Join(targetDirectory, key) // If empty dir in s3 if stringsutil.HasSuffixI(key, "/") { return os.MkdirAll(file, 0775) } if err := os.MkdirAll(filepath.Dir(file), 0775); err != nil { return err } // Set up the local file fd, err := os.Create(file) if err != nil { return err } defer func() { _ = fd.Close() }() // Download the file using the AWS SDK for Go _, err = downloader.Download(context.TODO(), fd, &s3.GetObjectInput{Bucket: &bucket, Key: &key}) return err } func getS3Client(ctx context.Context, accessKey string, secretKey string, region string, profile string) (*s3.Client, error) { var cfg aws.Config var err error if profile != "" { cfg, err = config.LoadDefaultConfig(ctx, config.WithSharedConfigProfile(profile)) if err != nil { return nil, err } } else if accessKey != "" && secretKey != "" { cfg, err = config.LoadDefaultConfig(ctx, config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accessKey, secretKey, "")), config.WithRegion(region)) if err != nil { return nil, err } } else { cfg, err = config.LoadDefaultConfig(ctx) if err != nil { return nil, err } } return s3.NewFromConfig(cfg), nil } ================================================ FILE: pkg/external/customtemplates/templates_provider.go ================================================ package customtemplates import ( "context" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/utils/errkit" ) type Provider interface { Download(ctx context.Context) Update(ctx context.Context) } // CustomTemplatesManager is a manager for custom templates type CustomTemplatesManager struct { providers []Provider } // Download downloads the custom templates func (c *CustomTemplatesManager) Download(ctx context.Context) { for _, provider := range c.providers { provider.Download(ctx) } } // Update updates the custom templates func (c *CustomTemplatesManager) Update(ctx context.Context) { for _, provider := range c.providers { provider.Update(ctx) } } // NewCustomTemplatesManager returns a new instance of a custom templates manager func NewCustomTemplatesManager(options *types.Options) (*CustomTemplatesManager, error) { ctm := &CustomTemplatesManager{providers: []Provider{}} // Add GitHub providers githubProviders, err := NewGitHubProviders(options) if err != nil { errx := errkit.FromError(err) errx.Msgf("could not create github providers for custom templates") return nil, errx } for _, v := range githubProviders { ctm.providers = append(ctm.providers, v) } // Add AWS S3 providers s3Providers, err := NewS3Providers(options) if err != nil { errx := errkit.FromError(err) errx.Msgf("could not create s3 providers for custom templates") return nil, errx } for _, v := range s3Providers { ctm.providers = append(ctm.providers, v) } // Add Azure providers azureProviders, err := NewAzureProviders(options) if err != nil { errx := errkit.FromError(err) errx.Msgf("could not create azure providers for custom templates") return nil, errx } for _, v := range azureProviders { ctm.providers = append(ctm.providers, v) } // Add GitLab providers gitlabProviders, err := NewGitLabProviders(options) if err != nil { errx := errkit.FromError(err) errx.Msgf("could not create gitlab providers for custom templates") return nil, errx } for _, v := range gitlabProviders { ctm.providers = append(ctm.providers, v) } return ctm, nil } ================================================ FILE: pkg/fuzz/analyzers/analyzers.go ================================================ package analyzers import ( "math/rand" "strconv" "strings" "time" "github.com/projectdiscovery/nuclei/v3/pkg/fuzz" "github.com/projectdiscovery/retryablehttp-go" ) // Analyzer is an interface for all the analyzers // that can be used for the fuzzer type Analyzer interface { // Name returns the name of the analyzer Name() string // ApplyTransformation applies the transformation to the initial payload. ApplyInitialTransformation(data string, params map[string]interface{}) string // Analyze is the main function for the analyzer Analyze(options *Options) (bool, string, error) } // AnalyzerTemplate is the template for the analyzer type AnalyzerTemplate struct { // description: | // Name is the name of the analyzer to use // values: // - time_delay Name string `json:"name" yaml:"name"` // description: | // 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. Parameters map[string]interface{} `json:"parameters" yaml:"parameters"` } var ( analyzers map[string]Analyzer ) // RegisterAnalyzer registers a new analyzer func RegisterAnalyzer(name string, analyzer Analyzer) { analyzers[name] = analyzer } // GetAnalyzer returns the analyzer for a given name func GetAnalyzer(name string) Analyzer { return analyzers[name] } func init() { analyzers = make(map[string]Analyzer) } // Options contains the options for the analyzer type Options struct { FuzzGenerated fuzz.GeneratedRequest HttpClient *retryablehttp.Client ResponseTimeDelay time.Duration AnalyzerParameters map[string]interface{} } var ( random = rand.New(rand.NewSource(time.Now().UnixNano())) ) // ApplyPayloadTransformations applies the payload transformations to the payload // It supports the below payloads - // - [RANDNUM] => random number between 1000 and 9999 // - [RANDSTR] => random string of 4 characters func ApplyPayloadTransformations(value string) string { randomInt := GetRandomInteger() randomStr := randStringBytesMask(4) value = strings.ReplaceAll(value, "[RANDNUM]", strconv.Itoa(randomInt)) value = strings.ReplaceAll(value, "[RANDSTR]", randomStr) return value } const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" func randStringBytesMask(n int) string { b := make([]byte, n) for i := range b { b[i] = letterBytes[random.Intn(len(letterBytes))] } return string(b) } // GetRandomInteger returns a random integer between 1000 and 9999 func GetRandomInteger() int { return random.Intn(9000) + 1000 } ================================================ FILE: pkg/fuzz/analyzers/time/analyzer.go ================================================ package time import ( "fmt" "io" "net/http/httptrace" "strconv" "strings" "time" "github.com/pkg/errors" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/analyzers" "github.com/projectdiscovery/retryablehttp-go" ) // Analyzer is a time delay analyzer for the fuzzer type Analyzer struct { } const ( DefaultSleepDuration = int(7) DefaultRequestsLimit = int(4) DefaultTimeCorrelationErrorRange = float64(0.15) DefaultTimeSlopeErrorRange = float64(0.30) DefaultLowSleepTimeSeconds = float64(3) defaultSleepTimeDuration = 7 * time.Second ) var _ analyzers.Analyzer = &Analyzer{} func init() { analyzers.RegisterAnalyzer("time_delay", &Analyzer{}) } // Name is the name of the analyzer func (a *Analyzer) Name() string { return "time_delay" } // ApplyInitialTransformation applies the transformation to the initial payload. // // It supports the below payloads - // - [SLEEPTIME] => sleep_duration // - [INFERENCE] => Inference payload for time delay analyzer // // It also applies the payload transformations to the payload // which includes [RANDNUM] and [RANDSTR] func (a *Analyzer) ApplyInitialTransformation(data string, params map[string]interface{}) string { duration := DefaultSleepDuration if len(params) > 0 { if v, ok := params["sleep_duration"]; ok { duration, ok = v.(int) if !ok { duration = DefaultSleepDuration gologger.Warning().Msgf("Invalid sleep_duration parameter type, using default value: %d", duration) } } } data = strings.ReplaceAll(data, "[SLEEPTIME]", strconv.Itoa(duration)) data = analyzers.ApplyPayloadTransformations(data) // Also support [INFERENCE] for the time delay analyzer if strings.Contains(data, "[INFERENCE]") { randInt := analyzers.GetRandomInteger() data = strings.ReplaceAll(data, "[INFERENCE]", fmt.Sprintf("%d=%d", randInt, randInt)) } return data } func (a *Analyzer) parseAnalyzerParameters(params map[string]interface{}) (int, int, float64, float64, error) { requestsLimit := DefaultRequestsLimit sleepDuration := DefaultSleepDuration timeCorrelationErrorRange := DefaultTimeCorrelationErrorRange timeSlopeErrorRange := DefaultTimeSlopeErrorRange if len(params) == 0 { return requestsLimit, sleepDuration, timeCorrelationErrorRange, timeSlopeErrorRange, nil } var ok bool for k, v := range params { switch k { case "sleep_duration": sleepDuration, ok = v.(int) case "requests_limit": requestsLimit, ok = v.(int) case "time_correlation_error_range": timeCorrelationErrorRange, ok = v.(float64) case "time_slope_error_range": timeSlopeErrorRange, ok = v.(float64) } if !ok { return 0, 0, 0, 0, errors.Errorf("invalid parameter type for %s", k) } } return requestsLimit, sleepDuration, timeCorrelationErrorRange, timeSlopeErrorRange, nil } // Analyze is the main function for the analyzer func (a *Analyzer) Analyze(options *analyzers.Options) (bool, string, error) { if options.ResponseTimeDelay < defaultSleepTimeDuration { return false, "", nil } // Parse parameters for this analyzer if any or use default values requestsLimit, sleepDuration, timeCorrelationErrorRange, timeSlopeErrorRange, err := a.parseAnalyzerParameters(options.AnalyzerParameters) if err != nil { return false, "", err } reqSender := func(delay int) (float64, error) { gr := options.FuzzGenerated replaced := strings.ReplaceAll(gr.OriginalPayload, "[SLEEPTIME]", strconv.Itoa(delay)) replaced = a.ApplyInitialTransformation(replaced, options.AnalyzerParameters) if err := gr.Component.SetValue(gr.Key, replaced); err != nil { return 0, errors.Wrap(err, "could not set value in component") } rebuilt, err := gr.Component.Rebuild() if err != nil { return 0, errors.Wrap(err, "could not rebuild request") } gologger.Verbose().Msgf("[%s] Sending request with %d delay for: %s", a.Name(), delay, rebuilt.String()) timeTaken, err := doHTTPRequestWithTimeTracing(rebuilt, options.HttpClient) if err != nil { return 0, errors.Wrap(err, "could not do request with time tracing") } return timeTaken, nil } // Check the baseline delay of the request by doing two requests baselineDelay, err := getBaselineDelay(reqSender) if err != nil { return false, "", err } matched, matchReason, err := checkTimingDependency( requestsLimit, sleepDuration, timeCorrelationErrorRange, timeSlopeErrorRange, baselineDelay, reqSender, ) if err != nil { return false, "", err } if matched { return true, matchReason, nil } return false, "", nil } func getBaselineDelay(reqSender timeDelayRequestSender) (float64, error) { var delays []float64 // Use zero or a very small delay to measure baseline for i := 0; i < 3; i++ { delay, err := reqSender(0) if err != nil { return 0, errors.Wrap(err, "could not get baseline delay") } delays = append(delays, delay) } var total float64 for _, d := range delays { total += d } avg := total / float64(len(delays)) return avg, nil } // doHTTPRequestWithTimeTracing does a http request with time tracing func doHTTPRequestWithTimeTracing(req *retryablehttp.Request, httpclient *retryablehttp.Client) (float64, error) { var serverTime time.Duration var wroteRequest time.Time trace := &httptrace.ClientTrace{ WroteHeaders: func() { wroteRequest = time.Now() }, GotFirstResponseByte: func() { serverTime = time.Since(wroteRequest) }, } req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace)) resp, err := httpclient.Do(req) if err != nil { return 0, errors.Wrap(err, "could not do request") } _, err = io.ReadAll(resp.Body) if err != nil { return 0, errors.Wrap(err, "could not read response body") } return serverTime.Seconds(), nil } ================================================ FILE: pkg/fuzz/analyzers/time/time_delay.go ================================================ // Package time implements a time delay analyzer using linear // regression heuristics inspired from ZAP to discover time // based issues. // // The approach is the one used in ZAP for timing based checks. // Advantages of this approach are many compared to the old approach of // heuristics of sleep time. // // NOTE: This algorithm has been heavily modified after being introduced // in nuclei. Now the logic has sever bug fixes and improvements and // has been evolving to be more stable. // // As we are building a statistical model, we can predict if the delay // is random or not very quickly. Also, the payloads are alternated to send // a very high sleep and a very low sleep. This way the comparison is // faster to eliminate negative cases. Only legitimate cases are sent for // more verification. // // For more details on the algorithm, follow the links below: // - https://groups.google.com/g/zaproxy-develop/c/KGSkNHlLtqk // - https://github.com/zaproxy/zap-extensions/pull/5053 // // This file has been implemented from its original version. It was originally licensed under the Apache License 2.0 (see LICENSE file for details). // The original algorithm is implemented in ZAP Active Scanner. package time import ( "errors" "fmt" "math" "strings" ) type timeDelayRequestSender func(delay int) (float64, error) // requestsSentMetadata is used to store the delay requested // and delay received for each request type requestsSentMetadata struct { delay int delayReceived float64 } // checkTimingDependency checks the timing dependency for a given request // // It alternates and sends first a high request, then a low request. Each time // it checks if the delay of the application can be predictably controlled. func checkTimingDependency( requestsLimit int, highSleepTimeSeconds int, correlationErrorRange float64, slopeErrorRange float64, baselineDelay float64, requestSender timeDelayRequestSender, ) (bool, string, error) { if requestsLimit < 2 { return false, "", errors.New("requests limit should be at least 2") } regression := newSimpleLinearRegression() requestsLeft := requestsLimit var requestsSent []requestsSentMetadata for requestsLeft > 0 { isCorrelationPossible, delayReceived, err := sendRequestAndTestConfidence(regression, highSleepTimeSeconds, requestSender, baselineDelay) if err != nil { return false, "", err } if !isCorrelationPossible { return false, "", nil } // Check the delay is greater than baseline by seconds requested if delayReceived < baselineDelay+float64(highSleepTimeSeconds)*0.8 { return false, "", nil } requestsSent = append(requestsSent, requestsSentMetadata{ delay: highSleepTimeSeconds, delayReceived: delayReceived, }) isCorrelationPossibleSecond, delayReceivedSecond, err := sendRequestAndTestConfidence(regression, int(DefaultLowSleepTimeSeconds), requestSender, baselineDelay) if err != nil { return false, "", err } if !isCorrelationPossibleSecond { return false, "", nil } if delayReceivedSecond < baselineDelay+float64(DefaultLowSleepTimeSeconds)*0.8 { return false, "", nil } requestsLeft = requestsLeft - 2 requestsSent = append(requestsSent, requestsSentMetadata{ delay: int(DefaultLowSleepTimeSeconds), delayReceived: delayReceivedSecond, }) } result := regression.IsWithinConfidence(correlationErrorRange, 1.0, slopeErrorRange) if result { var resultReason strings.Builder fmt.Fprintf(&resultReason, "[time_delay] made %d requests (baseline: %.2fs) successfully, with a regression slope of %.2f and correlation %.2f", requestsLimit, baselineDelay, regression.slope, regression.correlation) for _, request := range requestsSent { fmt.Fprintf(&resultReason, "\n - delay: %ds, delayReceived: %fs", request.delay, request.delayReceived) } return result, resultReason.String(), nil } return result, "", nil } // sendRequestAndTestConfidence sends a request and tests the confidence of delay func sendRequestAndTestConfidence( regression *simpleLinearRegression, delay int, requestSender timeDelayRequestSender, baselineDelay float64, ) (bool, float64, error) { delayReceived, err := requestSender(delay) if err != nil { return false, 0, err } if delayReceived < float64(delay) { return false, 0, nil } regression.AddPoint(float64(delay), delayReceived-baselineDelay) if !regression.IsWithinConfidence(0.3, 1.0, 0.5) { return false, delayReceived, nil } return true, delayReceived, nil } type simpleLinearRegression struct { count float64 sumX float64 sumY float64 sumXX float64 sumYY float64 sumXY float64 slope float64 intercept float64 correlation float64 } func newSimpleLinearRegression() *simpleLinearRegression { return &simpleLinearRegression{ // Start everything at zero until we have data slope: 0.0, intercept: 0.0, correlation: 0.0, } } func (o *simpleLinearRegression) AddPoint(x, y float64) { o.count += 1 o.sumX += x o.sumY += y o.sumXX += x * x o.sumYY += y * y o.sumXY += x * y // Need at least two points for meaningful calculation if o.count < 2 { return } n := o.count meanX := o.sumX / n meanY := o.sumY / n // Compute sample variances and covariance varX := (o.sumXX - n*meanX*meanX) / (n - 1) varY := (o.sumYY - n*meanY*meanY) / (n - 1) covXY := (o.sumXY - n*meanX*meanY) / (n - 1) // If varX is zero, slope cannot be computed meaningfully. // This would mean all X are the same, so handle that edge case. if varX == 0 { o.slope = 0.0 o.intercept = meanY // Just the mean o.correlation = 0.0 // No correlation since all X are identical return } o.slope = covXY / varX o.intercept = meanY - o.slope*meanX // If varX or varY are zero, we cannot compute correlation properly. if varX > 0 && varY > 0 { o.correlation = covXY / (math.Sqrt(varX) * math.Sqrt(varY)) } else { o.correlation = 0.0 } } func (o *simpleLinearRegression) Predict(x float64) float64 { return o.slope*x + o.intercept } func (o *simpleLinearRegression) IsWithinConfidence(correlationErrorRange float64, expectedSlope float64, slopeErrorRange float64) bool { if o.count < 2 { return true } // Check if slope is within error range of expected slope // Also consider cases where slope is approximately 2x of expected slope // as this can happen with time-based responses slopeDiff := math.Abs(expectedSlope - o.slope) slope2xDiff := math.Abs(expectedSlope*2 - o.slope) if slopeDiff > slopeErrorRange && slope2xDiff > slopeErrorRange { return false } return o.correlation > 1.0-correlationErrorRange } ================================================ FILE: pkg/fuzz/analyzers/time/time_delay_test.go ================================================ // Tests ported from ZAP Java version of the algorithm package time import ( "math/rand" "reflect" "testing" "time" ) // This test suite verifies the timing dependency detection algorithm by testing various scenarios: // // Test Categories: // 1. Perfect Linear Cases // - TestPerfectLinear: Basic case with slope=1, no noise // - TestPerfectLinearSlopeOne_NoNoise: Similar to above but with different parameters // - TestPerfectLinearSlopeTwo_NoNoise: Tests detection of slope=2 relationship // // 2. Noisy Cases // - TestLinearWithNoise: Verifies detection works with moderate noise (±0.2s) // - TestNoisyLinear: Similar but with different noise parameters // - TestHighNoiseConcealsSlope: Verifies detection fails with extreme noise (±5s) // // 3. No Correlation Cases // - TestNoCorrelation: Basic case where delay has no effect // - TestNoCorrelationHighBaseline: High baseline (~15s) masks any delay effect // - TestNegativeSlopeScenario: Verifies detection rejects negative correlations // // 4. Edge Cases // - TestMinimalData: Tests behavior with minimal data points (2 requests) // - TestLargeNumberOfRequests: Tests stability with many data points (20 requests) // - TestChangingBaseline: Tests detection with shifting baseline mid-test // - TestHighBaselineLowSlope: Tests detection of subtle correlations (slope=0.85) // // ZAP Test Cases: // // 1. Alternating Sequence Tests // - TestAlternatingSequences: Verifies correct alternation between high and low delays // // 2. Non-Injectable Cases // - TestNonInjectableQuickFail: Tests quick failure when response time < requested delay // - TestSlowNonInjectableCase: Tests early termination with consistently high response times // - TestRealWorldNonInjectableCase: Tests behavior with real-world response patterns // // 3. Error Tolerance Tests // - TestSmallErrorDependence: Verifies detection works with small random variations // // Key Parameters Tested: // - requestsLimit: Number of requests to make (2-20) // - highSleepTimeSeconds: Maximum delay to test (typically 5s) // - correlationErrorRange: Acceptable deviation from perfect correlation (0.05-0.3) // - slopeErrorRange: Acceptable deviation from expected slope (0.1-1.5) // // The test suite uses various mock senders (perfectLinearSender, noCorrelationSender, etc.) // to simulate different timing behaviors and verify the detection algorithm works correctly // across a wide range of scenarios. // Mock request sender that simulates a perfect linear relationship: // Observed delay = baseline + requested_delay func perfectLinearSender(baseline float64) func(delay int) (float64, error) { return func(delay int) (float64, error) { // simulate some processing time time.Sleep(10 * time.Millisecond) // just a small artificial sleep to mimic network return baseline + float64(delay), nil } } // Mock request sender that simulates no correlation: // The response time is random around a certain constant baseline, ignoring requested delay. func noCorrelationSender(baseline, noiseAmplitude float64) func(int) (float64, error) { return func(delay int) (float64, error) { time.Sleep(10 * time.Millisecond) noise := 0.0 if noiseAmplitude > 0 { noise = (rand.Float64()*2 - 1) * noiseAmplitude } return baseline + noise, nil } } // Mock request sender that simulates partial linearity but with some noise. func noisyLinearSender(baseline float64) func(delay int) (float64, error) { return func(delay int) (float64, error) { time.Sleep(10 * time.Millisecond) // Add some noise (±0.2s) to a linear relationship noise := 0.2 return baseline + float64(delay) + noise, nil } } func TestPerfectLinear(t *testing.T) { // Expect near-perfect correlation and slope ~ 1.0 requestsLimit := 6 // 3 pairs: enough data for stable regression highSleepTimeSeconds := 5 corrErrRange := 0.1 slopeErrRange := 0.2 baseline := 5.0 sender := perfectLinearSender(5.0) // baseline 5s, observed = 5s + requested_delay match, reason, err := checkTimingDependency( requestsLimit, highSleepTimeSeconds, corrErrRange, slopeErrRange, baseline, sender, ) if err != nil { t.Fatalf("Unexpected error: %v", err) } if !match { t.Fatalf("Expected a match but got none. Reason: %s", reason) } } func TestNoCorrelation(t *testing.T) { // Expect no match because requested delay doesn't influence observed delay requestsLimit := 6 highSleepTimeSeconds := 5 corrErrRange := 0.1 slopeErrRange := 0.5 baseline := 8.0 sender := noCorrelationSender(8.0, 0.1) match, reason, err := checkTimingDependency( requestsLimit, highSleepTimeSeconds, corrErrRange, slopeErrRange, baseline, sender, ) if err != nil { t.Fatalf("Unexpected error: %v", err) } if match { t.Fatalf("Expected no match but got one. Reason: %s", reason) } } func TestNoisyLinear(t *testing.T) { // Even with some noise, it should detect a strong positive correlation if // we allow a slightly bigger margin for slope/correlation. requestsLimit := 10 // More requests to average out noise highSleepTimeSeconds := 5 corrErrRange := 0.2 // allow some lower correlation due to noise slopeErrRange := 0.5 // slope may deviate slightly baseline := 2.0 sender := noisyLinearSender(2.0) // baseline 2s, observed ~ 2s + requested_delay ±0.2 match, reason, err := checkTimingDependency( requestsLimit, highSleepTimeSeconds, corrErrRange, slopeErrRange, baseline, sender, ) if err != nil { t.Fatalf("Unexpected error: %v", err) } // We expect a match since it's still roughly linear. The slope should be close to 1. if !match { t.Fatalf("Expected a match in noisy linear test but got none. Reason: %s", reason) } } func TestMinimalData(t *testing.T) { // With too few requests, correlation might not be stable. // Here, we send only 2 requests (1 pair) and see if the logic handles it gracefully. requestsLimit := 2 highSleepTimeSeconds := 5 corrErrRange := 0.3 slopeErrRange := 0.5 baseline := 5.0 // Perfect linear sender again sender := perfectLinearSender(5.0) match, reason, err := checkTimingDependency( requestsLimit, highSleepTimeSeconds, corrErrRange, slopeErrRange, baseline, sender, ) if err != nil { t.Fatalf("Unexpected error: %v", err) } if !match { t.Fatalf("Expected match but got none. Reason: %s", reason) } } // Utility functions to generate different behaviors // linearSender returns a sender that calculates observed delay as: // observed = baseline + slope * requested_delay + noise func linearSender(baseline, slope, noiseAmplitude float64) func(int) (float64, error) { return func(delay int) (float64, error) { time.Sleep(10 * time.Millisecond) noise := 0.0 if noiseAmplitude > 0 { noise = (rand.Float64()*2 - 1) * noiseAmplitude // random noise in [-noiseAmplitude, noiseAmplitude] } return baseline + slope*float64(delay) + noise, nil } } // negativeSlopeSender just for completeness - higher delay = less observed time func negativeSlopeSender(baseline float64) func(int) (float64, error) { return func(delay int) (float64, error) { time.Sleep(10 * time.Millisecond) return baseline - float64(delay)*2.0, nil } } func TestPerfectLinearSlopeOne_NoNoise(t *testing.T) { baseline := 2.0 match, reason, err := checkTimingDependency( 10, // requestsLimit 5, // highSleepTimeSeconds 0.1, // correlationErrorRange 0.2, // slopeErrorRange (allowing slope between 0.8 and 1.2) baseline, linearSender(baseline, 1.0, 0.0), ) if err != nil { t.Fatalf("Unexpected error: %v", err) } if !match { t.Fatalf("Expected a match for perfect linear slope=1. Reason: %s", reason) } } func TestPerfectLinearSlopeTwo_NoNoise(t *testing.T) { baseline := 2.0 // slope=2 means observed = baseline + 2*requested_delay match, reason, err := checkTimingDependency( 10, 5, 0.1, // correlation must still be good 1.5, // allow slope in range (0.5 to 2.5), we should be close to 2.0 anyway baseline, linearSender(baseline, 2.0, 0.0), ) if err != nil { t.Fatalf("Error: %v", err) } if !match { t.Fatalf("Expected a match for slope=2. Reason: %s", reason) } } func TestLinearWithNoise(t *testing.T) { baseline := 5.0 // slope=1 but with noise ±0.2 seconds match, reason, err := checkTimingDependency( 12, 5, 0.2, // correlationErrorRange relaxed to account for noise 0.5, // slopeErrorRange also relaxed baseline, linearSender(baseline, 1.0, 0.2), ) if err != nil { t.Fatalf("Error: %v", err) } if !match { t.Fatalf("Expected a match for noisy linear data. Reason: %s", reason) } } func TestNoCorrelationHighBaseline(t *testing.T) { baseline := 15.0 // baseline ~15s, requested delays won't matter match, reason, err := checkTimingDependency( 10, 5, 0.1, // correlation should be near zero, so no match expected 0.5, baseline, noCorrelationSender(baseline, 0.1), ) if err != nil { t.Fatalf("Error: %v", err) } if match { t.Fatalf("Expected no match for no correlation scenario. Got: %s", reason) } } func TestNegativeSlopeScenario(t *testing.T) { baseline := 10.0 // Increasing delay decreases observed time match, reason, err := checkTimingDependency( 10, 5, 0.2, 0.5, baseline, negativeSlopeSender(baseline), ) if err != nil { t.Fatalf("Error: %v", err) } if match { t.Fatalf("Expected no match in negative slope scenario. Reason: %s", reason) } } func TestLargeNumberOfRequests(t *testing.T) { baseline := 1.0 // 20 requests, slope=1.0, no noise. Should be very stable and produce a very high correlation. match, reason, err := checkTimingDependency( 20, 5, 0.05, // very strict correlation requirement 0.1, // very strict slope range baseline, linearSender(baseline, 1.0, 0.0), ) if err != nil { t.Fatalf("Error: %v", err) } if !match { t.Fatalf("Expected a strong match with many requests and perfect linearity. Reason: %s", reason) } } func TestHighBaselineLowSlope(t *testing.T) { baseline := 15.0 match, reason, err := checkTimingDependency( 10, 5, 0.2, 0.2, // expecting slope around 0.5, allow range ~0.4 to 0.6 baseline, linearSender(baseline, 0.85, 0.0), ) if err != nil { t.Fatalf("Error: %v", err) } if !match { t.Fatalf("Expected a match for slope=0.5 linear scenario. Reason: %s", reason) } } func TestHighNoiseConcealsSlope(t *testing.T) { baseline := 5.0 // slope=1, but noise=5 seconds is huge and might conceal the correlation. // With large noise, the test may fail to detect correlation. match, reason, err := checkTimingDependency( 12, 5, 0.1, // still strict 0.2, // still strict baseline, linearSender(baseline, 1.0, 5.0), ) if err != nil { t.Fatalf("Error: %v", err) } // Expect no match because the noise level is too high to establish a reliable correlation. if match { t.Fatalf("Expected no match due to extreme noise. Reason: %s", reason) } } func TestAlternatingSequences(t *testing.T) { baseline := 0.0 var generatedDelays []float64 reqSender := func(delay int) (float64, error) { generatedDelays = append(generatedDelays, float64(delay)) return float64(delay), nil } match, reason, err := checkTimingDependency( 4, // requestsLimit 15, // highSleepTimeSeconds 0.1, // correlationErrorRange 0.2, // slopeErrorRange baseline, reqSender, ) if err != nil { t.Fatalf("Unexpected error: %v", err) } if !match { t.Fatalf("Expected a match but got none. Reason: %s", reason) } // Verify alternating sequence of delays expectedDelays := []float64{15, 3, 15, 3} if !reflect.DeepEqual(generatedDelays, expectedDelays) { t.Fatalf("Expected delays %v but got %v", expectedDelays, generatedDelays) } } func TestNonInjectableQuickFail(t *testing.T) { baseline := 0.5 var timesCalled int reqSender := func(delay int) (float64, error) { timesCalled++ return 0.5, nil // Return value less than delay } match, _, err := checkTimingDependency( 4, // requestsLimit 15, // highSleepTimeSeconds 0.1, // correlationErrorRange 0.2, // slopeErrorRange baseline, reqSender, ) if err != nil { t.Fatalf("Unexpected error: %v", err) } if match { t.Fatal("Expected no match for non-injectable case") } if timesCalled != 1 { t.Fatalf("Expected quick fail after 1 call, got %d calls", timesCalled) } } func TestSlowNonInjectableCase(t *testing.T) { baseline := 10.0 rng := rand.New(rand.NewSource(time.Now().UnixNano())) var timesCalled int reqSender := func(delay int) (float64, error) { timesCalled++ return 10 + rng.Float64()*0.5, nil } match, _, err := checkTimingDependency( 4, // requestsLimit 15, // highSleepTimeSeconds 0.1, // correlationErrorRange 0.2, // slopeErrorRange baseline, reqSender, ) if err != nil { t.Fatalf("Unexpected error: %v", err) } if match { t.Fatal("Expected no match for slow non-injectable case") } if timesCalled > 3 { t.Fatalf("Expected early termination (≤3 calls), got %d calls", timesCalled) } } func TestRealWorldNonInjectableCase(t *testing.T) { baseline := 0.0 var iteration int counts := []float64{11, 21, 11, 21, 11} reqSender := func(delay int) (float64, error) { iteration++ return counts[iteration-1], nil } match, _, err := checkTimingDependency( 4, // requestsLimit 15, // highSleepTimeSeconds 0.1, // correlationErrorRange 0.2, // slopeErrorRange baseline, reqSender, ) if err != nil { t.Fatalf("Unexpected error: %v", err) } if match { t.Fatal("Expected no match for real-world non-injectable case") } if iteration > 4 { t.Fatalf("Expected ≤4 iterations, got %d", iteration) } } func TestSmallErrorDependence(t *testing.T) { baseline := 0.0 rng := rand.New(rand.NewSource(time.Now().UnixNano())) reqSender := func(delay int) (float64, error) { return float64(delay) + rng.Float64()*0.5, nil } match, reason, err := checkTimingDependency( 4, // requestsLimit 15, // highSleepTimeSeconds 0.1, // correlationErrorRange 0.2, // slopeErrorRange baseline, reqSender, ) if err != nil { t.Fatalf("Unexpected error: %v", err) } if !match { t.Fatalf("Expected match for small error case. Reason: %s", reason) } } ================================================ FILE: pkg/fuzz/component/body.go ================================================ package component import ( "bytes" "context" "io" "strconv" "strings" "github.com/pkg/errors" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat" "github.com/projectdiscovery/retryablehttp-go" ) // Body is a component for a request body type Body struct { value *Value req *retryablehttp.Request } var _ Component = &Body{} // NewBody creates a new body component func NewBody() *Body { return &Body{} } // Name returns the name of the component func (b *Body) Name() string { return RequestBodyComponent } // Parse parses the component and returns the // parsed component func (b *Body) Parse(req *retryablehttp.Request) (bool, error) { if req.Body == nil { return false, nil } b.req = req contentType := req.Header.Get("Content-Type") data, err := io.ReadAll(req.Body) if err != nil { return false, errors.Wrap(err, "could not read body") } req.Body = io.NopCloser(bytes.NewReader(data)) dataStr := string(data) if dataStr == "" { return false, nil } b.value = NewValue(dataStr) tmp := b.value.Parsed() if !tmp.IsNIL() { return true, nil } switch { case strings.Contains(contentType, "application/json") && tmp.IsNIL(): return b.parseBody(dataformat.JSONDataFormat, req) case strings.Contains(contentType, "application/xml") && tmp.IsNIL(): return b.parseBody(dataformat.XMLDataFormat, req) case strings.Contains(contentType, "multipart/form-data") && tmp.IsNIL(): return b.parseBody(dataformat.MultiPartFormDataFormat, req) } parsed, err := b.parseBody(dataformat.FormDataFormat, req) if err != nil { gologger.Warning().Msgf("Could not parse body as form data: %s\n", err) return b.parseBody(dataformat.RawDataFormat, req) } return parsed, err } // parseBody parses a body with a custom decoder func (b *Body) parseBody(decoderName string, req *retryablehttp.Request) (bool, error) { decoder := dataformat.Get(decoderName) if decoderName == dataformat.MultiPartFormDataFormat { // set content type to extract boundary if err := decoder.(*dataformat.MultiPartForm).ParseBoundary(req.Header.Get("Content-Type")); err != nil { return false, errors.Wrap(err, "could not parse boundary") } } decoded, err := decoder.Decode(b.value.String()) if err != nil { return false, errors.Wrap(err, "could not decode raw") } b.value.SetParsed(decoded, decoder.Name()) return true, nil } // Iterate iterates through the component func (b *Body) Iterate(callback func(key string, value interface{}) error) (errx error) { b.value.parsed.Iterate(func(key string, value any) bool { if strings.HasPrefix(key, "#_") { return true } if err := callback(key, value); err != nil { errx = err return false } return true }) return } // SetValue sets a value in the component func (b *Body) SetValue(key string, value string) error { if !b.value.SetParsedValue(key, value) { return ErrSetValue } return nil } // Delete deletes a key from the component func (b *Body) Delete(key string) error { if !b.value.Delete(key) { return ErrKeyNotFound } return nil } // Rebuild returns a new request with the // component rebuilt func (b *Body) Rebuild() (*retryablehttp.Request, error) { encoded, err := b.value.Encode() if err != nil { return nil, errors.Wrap(err, "could not encode body") } cloned := b.req.Clone(context.Background()) err = cloned.SetBodyString(encoded) if err != nil { return nil, errors.Wrap(err, "could not set body") } cloned.Header.Set("Content-Length", strconv.Itoa(len(encoded))) return cloned, nil } func (b *Body) Clone() Component { return &Body{ value: b.value.Clone(), req: b.req.Clone(context.Background()), } } ================================================ FILE: pkg/fuzz/component/body_test.go ================================================ package component import ( "bytes" "io" "mime/multipart" "strings" "testing" "github.com/projectdiscovery/retryablehttp-go" urlutil "github.com/projectdiscovery/utils/url" "github.com/stretchr/testify/require" ) func TestBodyComponent(t *testing.T) { req, err := retryablehttp.NewRequest("POST", "https://example.com", strings.NewReader(`{"foo":"bar"}`)) if err != nil { t.Fatal(err) } req.Header.Set("Content-Type", "application/json") body := New(RequestBodyComponent) _, err = body.Parse(req) if err != nil { t.Fatal(err) } var keys []string var values []string _ = body.Iterate(func(key string, value interface{}) error { keys = append(keys, key) values = append(values, value.(string)) return nil }) require.Equal(t, []string{"foo"}, keys, "unexpected keys") require.Equal(t, []string{"bar"}, values, "unexpected values") _ = body.SetValue("foo", "baz") rebuilt, err := body.Rebuild() if err != nil { t.Fatal(err) } newBody, err := io.ReadAll(rebuilt.Body) if err != nil { t.Fatal(err) } require.Equal(t, `{"foo":"baz"}`, string(newBody), "unexpected body") } func TestBodyXMLComponent(t *testing.T) { var body = "11" req, err := retryablehttp.NewRequest("POST", "https://example.com", strings.NewReader(body)) if err != nil { t.Fatal(err) } req.Header.Set("Content-Type", "application/xml") bodyComponent := New(RequestBodyComponent) parsed, err := bodyComponent.Parse(req) if err != nil { t.Fatal(err) } require.True(t, parsed, "could not parse body") _ = bodyComponent.SetValue("stockCheck~productId", "2'6842") rebuilt, err := bodyComponent.Rebuild() if err != nil { t.Fatal(err) } newBody, err := io.ReadAll(rebuilt.Body) if err != nil { t.Fatal(err) } require.Equal(t, "2'68421", string(newBody), "unexpected body") } func TestBodyFormComponent(t *testing.T) { formData := urlutil.NewOrderedParams() formData.Set("key1", "value1") formData.Set("key2", "value2") req, err := retryablehttp.NewRequest("POST", "https://example.com", strings.NewReader(formData.Encode())) if err != nil { t.Fatal(err) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") body := New(RequestBodyComponent) _, err = body.Parse(req) if err != nil { t.Fatal(err) } var keys []string var values []string _ = body.Iterate(func(key string, value interface{}) error { keys = append(keys, key) values = append(values, value.(string)) return nil }) require.ElementsMatch(t, []string{"key1", "key2"}, keys, "unexpected keys") require.ElementsMatch(t, []string{"value1", "value2"}, values, "unexpected values") _ = body.SetValue("key1", "updatedValue1") rebuilt, err := body.Rebuild() if err != nil { t.Fatal(err) } newBody, err := io.ReadAll(rebuilt.Body) if err != nil { t.Fatal(err) } require.Equal(t, "key1=updatedValue1&key2=value2", string(newBody), "unexpected body") } func TestMultiPartFormComponent(t *testing.T) { formData := &bytes.Buffer{} writer := multipart.NewWriter(formData) // Hypothetical form fields _ = writer.WriteField("username", "testuser") _ = writer.WriteField("password", "testpass") contentType := writer.FormDataContentType() _ = writer.Close() req, err := retryablehttp.NewRequest("POST", "https://example.com", formData) if err != nil { t.Fatal(err) } req.Header.Set("Content-Type", contentType) body := New(RequestBodyComponent) _, err = body.Parse(req) if err != nil { t.Fatal(err) } var keys []string var values []string _ = body.Iterate(func(key string, value interface{}) error { keys = append(keys, key) values = append(values, value.(string)) return nil }) require.ElementsMatch(t, []string{"username", "password"}, keys, "unexpected keys") require.ElementsMatch(t, []string{"testuser", "testpass"}, values, "unexpected values") // Update a value in the form _ = body.SetValue("password", "updatedTestPass") rebuilt, err := body.Rebuild() if err != nil { t.Fatal(err) } newBody, err := io.ReadAll(rebuilt.Body) if err != nil { t.Fatal(err) } // Check if the body contains the updated multipart form data require.Contains(t, string(newBody), "updatedTestPass", "unexpected body content") require.Contains(t, string(newBody), "username", "unexpected body content") require.Contains(t, string(newBody), "testuser", "unexpected body content") } ================================================ FILE: pkg/fuzz/component/component.go ================================================ package component import ( "errors" "strings" "github.com/leslie-qiwa/flat" "github.com/projectdiscovery/retryablehttp-go" ) // ErrSetValue is a error raised when a value cannot be set var ErrSetValue = errors.New("could not set value") func IsErrSetValue(err error) bool { if err == nil { return false } return strings.Contains(err.Error(), "could not set value") } // ErrKeyNotFound is a error raised when a key is not found var ErrKeyNotFound = errors.New("key not found") // Component is a component for a request type Component interface { // Name returns the name of the component Name() string // Parse parses the component and returns the // parsed component Parse(req *retryablehttp.Request) (bool, error) // Iterate iterates over all values of a component // ex in case of query component, it will iterate over each query parameter // depending on the rule if mode is single // request is rebuilt for each value in this callback // and in case of multiple, request will be rebuilt after iteration of all values Iterate(func(key string, value interface{}) error) error // SetValue sets a value in the component // for a key // // After calling setValue for mutation, the value must be // called again so as to reset the body to its original state. SetValue(key string, value string) error // Delete deletes a key from the component // If it is applicable Delete(key string) error // Rebuild returns a new request with the // component rebuilt Rebuild() (*retryablehttp.Request, error) // Clones current state of this component Clone() Component } const ( // RequestBodyComponent is the name of the request body component RequestBodyComponent = "body" // RequestQueryComponent is the name of the request query component RequestQueryComponent = "query" // RequestPathComponent is the name of the request url component RequestPathComponent = "path" // RequestHeaderComponent is the name of the request header component RequestHeaderComponent = "header" // RequestCookieComponent is the name of the request cookie component RequestCookieComponent = "cookie" ) // Components is a list of all available components var Components = []string{ RequestBodyComponent, RequestQueryComponent, RequestHeaderComponent, RequestPathComponent, RequestCookieComponent, } // New creates a new component for a componentType func New(componentType string) Component { switch componentType { case "body": return NewBody() case "query": return NewQuery() case "path": return NewPath() case "header": return NewHeader() case "cookie": return NewCookie() } return nil } var ( flatOpts = &flat.Options{ Safe: true, Delimiter: "~", } ) ================================================ FILE: pkg/fuzz/component/cookie.go ================================================ package component import ( "context" "fmt" "net/http" "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat" "github.com/projectdiscovery/retryablehttp-go" mapsutil "github.com/projectdiscovery/utils/maps" ) // Cookie is a component for a request cookie type Cookie struct { value *Value req *retryablehttp.Request } var _ Component = &Cookie{} // NewCookie creates a new cookie component func NewCookie() *Cookie { return &Cookie{} } // Name returns the name of the component func (c *Cookie) Name() string { return RequestCookieComponent } // Parse parses the component and returns the // parsed component func (c *Cookie) Parse(req *retryablehttp.Request) (bool, error) { if len(req.Cookies()) == 0 { return false, nil } c.req = req c.value = NewValue("") parsedCookies := mapsutil.NewOrderedMap[string, any]() for _, cookie := range req.Cookies() { parsedCookies.Set(cookie.Name, cookie.Value) } if parsedCookies.Len() == 0 { return false, nil } c.value.SetParsed(dataformat.KVOrderedMap(&parsedCookies), "") return true, nil } // Iterate iterates through the component func (c *Cookie) Iterate(callback func(key string, value interface{}) error) (err error) { c.value.parsed.Iterate(func(key string, value any) bool { if errx := callback(key, value); errx != nil { err = errx return false } return true }) return } // SetValue sets a value in the component // for a key func (c *Cookie) SetValue(key string, value string) error { if !c.value.SetParsedValue(key, value) { return ErrSetValue } return nil } // Delete deletes a key from the component func (c *Cookie) Delete(key string) error { if !c.value.Delete(key) { return ErrKeyNotFound } return nil } // Rebuild returns a new request with the // component rebuilt func (c *Cookie) Rebuild() (*retryablehttp.Request, error) { // TODO: Fix cookie duplication with auth-file cloned := c.req.Clone(context.Background()) cloned.Header.Del("Cookie") c.value.parsed.Iterate(func(key string, value any) bool { cookie := &http.Cookie{ Name: key, Value: fmt.Sprint(value), // Assume the value is always a string for cookies } cloned.AddCookie(cookie) return true }) return cloned, nil } // Clone clones current state of this component func (c *Cookie) Clone() Component { return &Cookie{ value: c.value.Clone(), req: c.req.Clone(context.Background()), } } ================================================ FILE: pkg/fuzz/component/cookie_test.go ================================================ package component import ( "net/http" "testing" "github.com/projectdiscovery/retryablehttp-go" "github.com/stretchr/testify/require" ) func TestCookieComponent(t *testing.T) { req, err := retryablehttp.NewRequest(http.MethodGet, "https://example.com", nil) if err != nil { t.Fatal(err) } cookie := &http.Cookie{ Name: "session", Value: "test-session", } req.AddCookie(cookie) cookieComponent := NewCookie() // Assuming you have a function like this for creating a new cookie component _, err = cookieComponent.Parse(req) if err != nil { t.Fatal(err) } var cookieNames []string var cookieValues []string _ = cookieComponent.Iterate(func(key string, value interface{}) error { cookieNames = append(cookieNames, key) switch v := value.(type) { case string: cookieValues = append(cookieValues, v) case []string: cookieValues = append(cookieValues, v...) } return nil }) require.Equal(t, []string{"session"}, cookieNames, "unexpected cookie names") require.Equal(t, []string{"test-session"}, cookieValues, "unexpected cookie values") err = cookieComponent.SetValue("session", "new-session") if err != nil { t.Fatal(err) } rebuilt, err := cookieComponent.Rebuild() if err != nil { t.Fatal(err) } // Assuming the Rebuild function will reconstruct the entire request and also set the modified cookies newCookie, _ := rebuilt.Cookie("session") require.Equal(t, "new-session", newCookie.Value, "unexpected cookie value") } ================================================ FILE: pkg/fuzz/component/headers.go ================================================ package component import ( "context" "strings" "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat" "github.com/projectdiscovery/retryablehttp-go" ) // Header is a component for a request header type Header struct { value *Value req *retryablehttp.Request } var _ Component = &Header{} // NewHeader creates a new header component func NewHeader() *Header { return &Header{} } // Name returns the name of the component func (q *Header) Name() string { return RequestHeaderComponent } // Parse parses the component and returns the // parsed component func (q *Header) Parse(req *retryablehttp.Request) (bool, error) { q.req = req q.value = NewValue("") parsedHeaders := make(map[string]interface{}) for key, value := range req.Header { if len(value) == 1 { parsedHeaders[key] = value[0] continue } parsedHeaders[key] = value } q.value.SetParsed(dataformat.KVMap(parsedHeaders), "") return true, nil } // Iterate iterates through the component func (q *Header) Iterate(callback func(key string, value interface{}) error) (errx error) { q.value.parsed.Iterate(func(key string, value any) bool { // Skip ignored headers if _, ok := defaultIgnoredHeaderKeys[key]; ok { return ok } if err := callback(key, value); err != nil { errx = err return false } return true }) return } // SetValue sets a value in the component // for a key func (q *Header) SetValue(key string, value string) error { if !q.value.SetParsedValue(key, value) { return ErrSetValue } return nil } // Delete deletes a key from the component func (q *Header) Delete(key string) error { if !q.value.Delete(key) { return ErrKeyNotFound } return nil } // Rebuild returns a new request with the // component rebuilt func (q *Header) Rebuild() (*retryablehttp.Request, error) { cloned := q.req.Clone(context.Background()) q.value.parsed.Iterate(func(key string, value any) bool { if strings.TrimSpace(key) == "" { return true } if strings.EqualFold(key, "Host") { return true } if vx, ok := IsTypedSlice(value); ok { // convert to []interface{} value = vx } if v, ok := value.([]interface{}); ok { for _, vv := range v { cloned.Header.Add(key, vv.(string)) } return true } cloned.Header.Set(key, value.(string)) return true }) return cloned, nil } // Clones current state of this component func (q *Header) Clone() Component { return &Header{ value: q.value.Clone(), req: q.req.Clone(context.Background()), } } // A list of headers that are essential to the request and // must not be fuzzed. var defaultIgnoredHeaderKeys = map[string]struct{}{ "Accept-Charset": {}, "Accept-Datetime": {}, "Accept-Encoding": {}, "Accept-Language": {}, "Accept": {}, "Access-Control-Request-Headers": {}, "Access-Control-Request-Method": {}, "Authorization": {}, "Cache-Control": {}, "Connection": {}, "Cookie": {}, "Content-Length": {}, "Content-Type": {}, "Date": {}, "Dnt": {}, "Expect": {}, "Forwarded": {}, "From": {}, "Host": {}, "If-Match": {}, "If-Modified-Since": {}, "If-None-Match": {}, "If-Range": {}, "If-Unmodified-Since": {}, "Max-Forwards": {}, "Pragma": {}, "Priority": {}, "Proxy-Authorization": {}, "Range": {}, "Sec-Ch-Ua": {}, "Sec-Ch-Ua-Mobile": {}, "Sec-Ch-Ua-Platform": {}, "Sec-Fetch-Dest": {}, "Sec-Fetch-Mode": {}, "Sec-Fetch-Site": {}, "Sec-Fetch-User": {}, "TE": {}, "Upgrade": {}, "Via": {}, "Warning": {}, "Upgrade-Insecure-Requests": {}, "X-CSRF-Token": {}, "X-Requested-With": {}, "Strict-Transport-Security": {}, "Content-Security-Policy": {}, "X-Content-Type-Options": {}, "X-Frame-Options": {}, "X-XSS-Protection": {}, "Public-Key-Pins": {}, "Referrer-Policy": {}, "Access-Control-Allow-Origin": {}, "Access-Control-Allow-Credentials": {}, "Access-Control-Expose-Headers": {}, "Access-Control-Max-Age": {}, "Access-Control-Allow-Methods": {}, "Access-Control-Allow-Headers": {}, "Server": {}, "X-Powered-By": {}, "X-AspNet-Version": {}, "X-AspNetMvc-Version": {}, "ETag": {}, "Vary": {}, "Expires": {}, "Last-Modified": {}, "X-Cache": {}, "X-Proxy-ID": {}, "CF-Ray": {}, // Cloudflare "X-Served-By": {}, // Varnish, etc. "X-Cache-Hits": {}, "Content-Encoding": {}, "Transfer-Encoding": {}, "Location": {}, "WWW-Authenticate": {}, "Proxy-Authenticate": {}, "X-Access-Token": {}, "X-Refresh-Token": {}, "Link": {}, "X-Content-Duration": {}, "X-UA-Compatible": {}, "X-RateLimit-Limit": {}, // Rate limiting header "X-RateLimit-Remaining": {}, // Rate limiting header "X-RateLimit-Reset": {}, // Rate limiting header } ================================================ FILE: pkg/fuzz/component/headers_test.go ================================================ package component import ( "net/http" "testing" "github.com/projectdiscovery/retryablehttp-go" "github.com/stretchr/testify/require" ) func TestHeaderComponent(t *testing.T) { req, err := retryablehttp.NewRequest(http.MethodGet, "https://example.com", nil) if err != nil { t.Fatal(err) } req.Header.Set("User-Agent", "test-agent") header := NewHeader() _, err = header.Parse(req) if err != nil { t.Fatal(err) } var keys []string var values []string _ = header.Iterate(func(key string, value interface{}) error { keys = append(keys, key) switch v := value.(type) { case string: values = append(values, v) case []string: values = append(values, v...) } return nil }) require.Equal(t, []string{"User-Agent"}, keys, "unexpected keys") require.Equal(t, []string{"test-agent"}, values, "unexpected values") err = header.SetValue("User-Agent", "new-agent") if err != nil { t.Fatal(err) } rebuilt, err := header.Rebuild() if err != nil { t.Fatal(err) } require.Equal(t, "new-agent", rebuilt.Header.Get("User-Agent"), "unexpected header value") } ================================================ FILE: pkg/fuzz/component/path.go ================================================ package component import ( "context" "strconv" "strings" "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat" "github.com/projectdiscovery/retryablehttp-go" urlutil "github.com/projectdiscovery/utils/url" ) // Path is a component for a request Path type Path struct { value *Value originalPath string req *retryablehttp.Request } var _ Component = &Path{} // NewPath creates a new URL component func NewPath() *Path { return &Path{} } // Name returns the name of the component func (q *Path) Name() string { return RequestPathComponent } // Parse parses the component and returns the // parsed component func (q *Path) Parse(req *retryablehttp.Request) (bool, error) { q.req = req q.originalPath = req.Path q.value = NewValue("") split := strings.Split(req.Path, "/") values := make(map[string]interface{}) for i, segment := range split { if segment == "" && i == 0 { // Skip the first empty segment from leading "/" continue } if segment == "" { // Skip any other empty segments continue } // Use 1-based indexing and store individual segments key := strconv.Itoa(len(values) + 1) values[key] = segment } q.value.SetParsed(dataformat.KVMap(values), "") return true, nil } // Iterate iterates through the component func (q *Path) Iterate(callback func(key string, value interface{}) error) (err error) { q.value.parsed.Iterate(func(key string, value any) bool { if errx := callback(key, value); errx != nil { err = errx return false } return true }) return } // SetValue sets a value in the component // for a key func (q *Path) SetValue(key string, value string) error { escaped := urlutil.PathEncode(value) if !q.value.SetParsedValue(key, escaped) { return ErrSetValue } return nil } // Delete deletes a key from the component func (q *Path) Delete(key string) error { if !q.value.Delete(key) { return ErrKeyNotFound } return nil } // Rebuild returns a new request with the // component rebuilt func (q *Path) Rebuild() (*retryablehttp.Request, error) { // Use the snapshot of the original path to avoid mutation from Clone/UpdateRelPath originalSplit := strings.Split(q.originalPath, "/") // Create a new slice to hold the rebuilt segments rebuiltSegments := make([]string, 0, len(originalSplit)) // Add the first empty segment (from leading "/") if len(originalSplit) > 0 && originalSplit[0] == "" { rebuiltSegments = append(rebuiltSegments, "") } // Process each segment segmentIndex := 1 // 1-based indexing for our stored values for i := 1; i < len(originalSplit); i++ { originalSegment := originalSplit[i] if originalSegment == "" { // Skip empty segments continue } // Check if we have a replacement for this segment key := strconv.Itoa(segmentIndex) if newValue, exists := q.value.parsed.Map.GetOrDefault(key, "").(string); exists && newValue != "" { rebuiltSegments = append(rebuiltSegments, newValue) } else { rebuiltSegments = append(rebuiltSegments, originalSegment) } segmentIndex++ } // Join the segments back into a path rebuiltPath := strings.Join(rebuiltSegments, "/") if unescaped, err := urlutil.PathDecode(rebuiltPath); err == nil { // this is handle the case where anyportion of path has url encoded data // by default the http/request official library will escape/encode special characters in path // to avoid double encoding we unescape/decode already encoded value // // if there is a invalid url encoded value like %99 then it will still be encoded as %2599 and not %99 // the only way to make sure it stays as %99 is to implement raw request and unsafe for fuzzing as well rebuiltPath = unescaped } // Clone the request and update the path cloned := q.req.Clone(context.Background()) if err := cloned.UpdateRelPath(rebuiltPath, true); err != nil { cloned.RawPath = rebuiltPath } return cloned, nil } // Clones current state to a new component func (q *Path) Clone() Component { return &Path{ value: q.value.Clone(), originalPath: q.originalPath, req: q.req.Clone(context.Background()), } } ================================================ FILE: pkg/fuzz/component/path_test.go ================================================ package component import ( "net/http" "testing" "github.com/projectdiscovery/retryablehttp-go" "github.com/stretchr/testify/require" ) func TestURLComponent(t *testing.T) { req, err := retryablehttp.NewRequest(http.MethodGet, "https://example.com/testpath", nil) if err != nil { t.Fatal(err) } urlComponent := NewPath() _, err = urlComponent.Parse(req) if err != nil { t.Fatal(err) } var keys []string var values []string _ = urlComponent.Iterate(func(key string, value interface{}) error { keys = append(keys, key) values = append(values, value.(string)) return nil }) require.Equal(t, []string{"1"}, keys, "unexpected keys") require.Equal(t, []string{"testpath"}, values, "unexpected values") err = urlComponent.SetValue("1", "newpath") if err != nil { t.Fatal(err) } rebuilt, err := urlComponent.Rebuild() if err != nil { t.Fatal(err) } require.Equal(t, "/newpath", rebuilt.Path, "unexpected URL path") require.Equal(t, "https://example.com/newpath", rebuilt.String(), "unexpected full URL") } func TestURLComponent_NestedPaths(t *testing.T) { path := NewPath() req, err := retryablehttp.NewRequest(http.MethodGet, "https://example.com/user/753/profile", nil) if err != nil { t.Fatal(err) } found, err := path.Parse(req) if err != nil { t.Fatal(err) } if !found { t.Fatal("expected path to be found") } isSet := false _ = path.Iterate(func(key string, value interface{}) error { t.Logf("Key: %s, Value: %s", key, value.(string)) if !isSet && value.(string) == "753" { isSet = true if setErr := path.SetValue(key, "753'"); setErr != nil { t.Fatal(setErr) } } return nil }) newReq, err := path.Rebuild() if err != nil { t.Fatal(err) } if newReq.Path != "/user/753'/profile" { t.Fatalf("expected path to be '/user/753'/profile', got '%s'", newReq.Path) } } // TestPathComponent_RebuildDoesNotMutateOriginal verifies that Rebuild() // does not mutate the original request path, which caused numeric path // segments to be skipped when fuzzing in single mode (issue #6398). func TestPathComponent_RebuildDoesNotMutateOriginal(t *testing.T) { path := NewPath() req, err := retryablehttp.NewRequest(http.MethodGet, "https://example.com/user/55/profile", nil) require.NoError(t, err) found, err := path.Parse(req) require.NoError(t, err) require.True(t, found) // Simulate single-mode fuzzing: fuzz each segment one at a time, // rebuilding after each and then resetting. segments := map[string]string{} _ = path.Iterate(func(key string, value interface{}) error { segments[key] = value.(string) return nil }) var fuzzedPaths []string for key, original := range segments { err := path.SetValue(key, original+"%20FUZZED") require.NoError(t, err) rebuilt, err := path.Rebuild() require.NoError(t, err) fuzzedPaths = append(fuzzedPaths, rebuilt.Path) // Reset value back to original err = path.SetValue(key, original) require.NoError(t, err) } // All three segments must have been fuzzed require.Len(t, fuzzedPaths, 3, "expected 3 fuzzed paths for 3 segments") // Verify that each segment was individually fuzzed require.Contains(t, fuzzedPaths, "/user FUZZED/55/profile") require.Contains(t, fuzzedPaths, "/user/55 FUZZED/profile") require.Contains(t, fuzzedPaths, "/user/55/profile FUZZED") } func TestPathComponent_SQLInjection(t *testing.T) { path := NewPath() req, err := retryablehttp.NewRequest(http.MethodGet, "https://example.com/user/55/profile", nil) if err != nil { t.Fatal(err) } found, err := path.Parse(req) if err != nil { t.Fatal(err) } if !found { t.Fatal("expected path to be found") } t.Logf("Original path: %s", req.Path) // Let's see what path segments are available for fuzzing err = path.Iterate(func(key string, value interface{}) error { t.Logf("Key: %s, Value: %s", key, value.(string)) // Try fuzzing the "55" segment specifically (which should be key "2") if value.(string) == "55" { if setErr := path.SetValue(key, "55 OR True"); setErr != nil { t.Fatal(setErr) } } return nil }) if err != nil { t.Fatal(err) } newReq, err := path.Rebuild() if err != nil { t.Fatal(err) } t.Logf("Modified path: %s", newReq.Path) // Now with PathEncode, spaces are preserved correctly for SQL injection if newReq.Path != "/user/55 OR True/profile" { t.Fatalf("expected path to be '/user/55 OR True/profile', got '%s'", newReq.Path) } // Let's also test what the actual URL looks like t.Logf("Full URL: %s", newReq.String()) } ================================================ FILE: pkg/fuzz/component/query.go ================================================ package component import ( "context" "github.com/pkg/errors" "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat" "github.com/projectdiscovery/retryablehttp-go" urlutil "github.com/projectdiscovery/utils/url" ) // Query is a component for a request query type Query struct { value *Value req *retryablehttp.Request } var _ Component = &Query{} // NewQuery creates a new query component func NewQuery() *Query { return &Query{} } // Name returns the name of the component func (q *Query) Name() string { return RequestQueryComponent } // Parse parses the component and returns the // parsed component func (q *Query) Parse(req *retryablehttp.Request) (bool, error) { if req.URL.Query().IsEmpty() { return false, nil } q.req = req q.value = NewValue(req.URL.Query().Encode()) parsed, err := dataformat.Get(dataformat.FormDataFormat).Decode(q.value.String()) if err != nil { return false, err } q.value.SetParsed(parsed, dataformat.FormDataFormat) return true, nil } // Iterate iterates through the component func (q *Query) Iterate(callback func(key string, value interface{}) error) (errx error) { q.value.parsed.Iterate(func(key string, value interface{}) bool { if err := callback(key, value); err != nil { errx = err return false } return true }) return } // SetValue sets a value in the component // for a key func (q *Query) SetValue(key string, value string) error { // Is this safe? if !q.value.SetParsedValue(key, value) { return ErrSetValue } return nil } // Delete deletes a key from the component func (q *Query) Delete(key string) error { if !q.value.Delete(key) { return ErrKeyNotFound } return nil } // Rebuild returns a new request with the // component rebuilt func (q *Query) Rebuild() (*retryablehttp.Request, error) { encoded, err := q.value.Encode() if err != nil { return nil, errors.Wrap(err, "could not encode query") } cloned := q.req.Clone(context.Background()) cloned.RawQuery = encoded // Clear the query parameters and re-add them cloned.Params = nil cloned.Params = urlutil.NewOrderedParams() cloned.Params.Decode(encoded) cloned.Update() return cloned, nil } // Clones current state to a new component func (q *Query) Clone() Component { return &Query{ value: q.value.Clone(), req: q.req.Clone(context.Background()), } } ================================================ FILE: pkg/fuzz/component/query_test.go ================================================ package component import ( "net/http" "testing" "github.com/projectdiscovery/retryablehttp-go" "github.com/stretchr/testify/require" ) func TestQueryComponent(t *testing.T) { req, err := retryablehttp.NewRequest(http.MethodGet, "https://example.com?foo=bar", nil) if err != nil { t.Fatal(err) } query := NewQuery() _, err = query.Parse(req) if err != nil { t.Fatal(err) } var keys []string var values []string _ = query.Iterate(func(key string, value interface{}) error { keys = append(keys, key) values = append(values, value.(string)) return nil }) require.Equal(t, []string{"foo"}, keys, "unexpected keys") require.Equal(t, []string{"bar"}, values, "unexpected values") err = query.SetValue("foo", "baz") if err != nil { t.Fatal(err) } rebuilt, err := query.Rebuild() if err != nil { t.Fatal(err) } require.Equal(t, "foo=baz", rebuilt.RawQuery, "unexpected query string") require.Equal(t, "https://example.com?foo=baz", rebuilt.String(), "unexpected url") } ================================================ FILE: pkg/fuzz/component/value.go ================================================ package component import ( "reflect" "strconv" "github.com/leslie-qiwa/flat" "github.com/logrusorgru/aurora" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat" ) // Value is a value component containing a single // parameter for the component // // It is a type of container that is used to represent // all the data values that are used in a request. type Value struct { data string parsed dataformat.KV dataFormat string } // NewValue returns a new value component func NewValue(data string) *Value { if data == "" { return &Value{} } v := &Value{data: data} // Do any dataformat decoding on the data if needed decodedDataformat, err := dataformat.Decode(data) if err == nil && decodedDataformat != nil { v.SetParsed(decodedDataformat.Data, decodedDataformat.DataFormat) } return v } // Clones current state of this value func (v *Value) Clone() *Value { return &Value{ data: v.data, parsed: v.parsed.Clone(), dataFormat: v.dataFormat, } } // String returns the string representation of the value func (v *Value) String() string { return v.data } // Parsed returns the parsed value func (v *Value) Parsed() dataformat.KV { return v.parsed } // SetParsed sets the parsed value map func (v *Value) SetParsed(data dataformat.KV, dataFormat string) { v.dataFormat = dataFormat if data.OrderedMap != nil { v.parsed = data return } parsed := data.Map flattened, err := flat.Flatten(parsed, flatOpts) if err == nil { v.parsed = dataformat.KVMap(flattened) } else { v.parsed = dataformat.KVMap(parsed) } } // SetParsedValue sets the parsed value for a key // in the parsed map func (v *Value) SetParsedValue(key, value string) bool { if key == "" { return false } origValue := v.parsed.Get(key) if origValue == nil { v.parsed.Set(key, value) return true } // TODO(dwisiswant0): I'm sure that this can be simplified because // `dataformat.KV.*` is a type of `mapsutil.*` where the value is `any`. So, // it looks like we won't type conversion here or even have its own methods // inside `dataformat.KV`. // If the value is a list, append to it // otherwise replace it switch v := origValue.(type) { case []interface{}: // update last value if len(v) > 0 { v[len(v)-1] = value } origValue = v case string: origValue = value case int, int32, int64, float32, float64: parsed, err := strconv.ParseInt(value, 10, 64) if err != nil { return false } origValue = parsed case bool: parsed, err := strconv.ParseBool(value) if err != nil { return false } origValue = parsed default: // explicitly check for typed slice if val, ok := IsTypedSlice(v); ok { if len(val) > 0 { val[len(val)-1] = value } origValue = val } else { // make it default warning instead of error gologger.DefaultLogger.Print().Msgf("[%v] unknown type %T for value %s", aurora.BrightYellow("WARN"), v, v) } } v.parsed.Set(key, origValue) return true } // Delete removes a key from the parsed value func (v *Value) Delete(key string) bool { return v.parsed.Delete(key) } // Encode encodes the value into a string // using the dataformat and encoding func (v *Value) Encode() (string, error) { toEncodeStr := v.data if v.parsed.OrderedMap != nil { // flattening orderedmap not supported if v.dataFormat != "" { dataformatStr, err := dataformat.Encode(v.parsed, v.dataFormat) if err != nil { return "", err } toEncodeStr = dataformatStr } return toEncodeStr, nil } nested, err := flat.Unflatten(v.parsed.Map, flatOpts) if err != nil { return "", err } if v.dataFormat != "" { dataformatStr, err := dataformat.Encode(dataformat.KVMap(nested), v.dataFormat) if err != nil { return "", err } toEncodeStr = dataformatStr } return toEncodeStr, nil } // In go, []int, []string are not implictily converted to []interface{} // when using type assertion and they need to be handled separately. func IsTypedSlice(v interface{}) ([]interface{}, bool) { if reflect.ValueOf(v).Kind() == reflect.Slice { // iterate and convert to []interface{} slice := reflect.ValueOf(v) interfaceSlice := make([]interface{}, slice.Len()) for i := 0; i < slice.Len(); i++ { interfaceSlice[i] = slice.Index(i).Interface() } return interfaceSlice, true } return nil, false } ================================================ FILE: pkg/fuzz/component/value_test.go ================================================ package component import ( "testing" "github.com/leslie-qiwa/flat" "github.com/stretchr/testify/require" ) func TestFlatMap_FlattenUnflatten(t *testing.T) { data := map[string]interface{}{ "foo": "bar", "bar": map[string]interface{}{ "baz": "foo", }, "slice": []interface{}{ "foo", "bar", }, "with.dot": map[string]interface{}{ "foo": "bar", }, } opts := &flat.Options{ Safe: true, Delimiter: "~", } flattened, err := flat.Flatten(data, opts) if err != nil { t.Fatal(err) } nested, err := flat.Unflatten(flattened, opts) if err != nil { t.Fatal(err) } require.Equal(t, data, nested, "unexpected data") } func TestAnySlice(t *testing.T) { data := []any{} data = append(data, []int{1, 2, 3}) data = append(data, []string{"foo", "bar"}) data = append(data, []bool{true, false}) data = append(data, []float64{1.1, 2.2, 3.3}) for _, d := range data { val, ok := IsTypedSlice(d) require.True(t, ok, "expected slice") require.True(t, val != nil, "expected value but got nil") } } ================================================ FILE: pkg/fuzz/dataformat/dataformat.go ================================================ package dataformat import ( "errors" "fmt" ) // dataformats is a list of dataformats var dataformats map[string]DataFormat const ( // DefaultKey is the key i.e used when given // data is not of k-v type DefaultKey = "value" ) func init() { dataformats = make(map[string]DataFormat) // register the default data formats RegisterDataFormat(NewJSON()) RegisterDataFormat(NewXML()) RegisterDataFormat(NewRaw()) RegisterDataFormat(NewForm()) RegisterDataFormat(NewMultiPartForm()) } const ( // JSONDataFormat is the name of the JSON data format JSONDataFormat = "json" // XMLDataFormat is the name of the XML data format XMLDataFormat = "xml" // RawDataFormat is the name of the Raw data format RawDataFormat = "raw" // FormDataFormat is the name of the Form data format FormDataFormat = "form" // MultiPartFormDataFormat is the name of the MultiPartForm data format MultiPartFormDataFormat = "multipart/form-data" ) // Get returns the dataformat by name func Get(name string) DataFormat { return dataformats[name] } // RegisterEncoder registers an encoder func RegisterDataFormat(dataformat DataFormat) { dataformats[dataformat.Name()] = dataformat } // DataFormat is an interface for encoding and decoding type DataFormat interface { // IsType returns true if the data is of the type IsType(data string) bool // Name returns the name of the encoder Name() string // Encode encodes the data into a format Encode(data KV) (string, error) // Decode decodes the data from a format Decode(input string) (KV, error) } // Decoded is a decoded data format type Decoded struct { // DataFormat is the data format DataFormat string // Data is the decoded data Data KV } // Decode decodes the data from a format func Decode(data string) (*Decoded, error) { for _, dataformat := range dataformats { if dataformat.IsType(data) { decoded, err := dataformat.Decode(data) if err != nil { return nil, err } value := &Decoded{ DataFormat: dataformat.Name(), Data: decoded, } return value, nil } } return nil, nil } // Encode encodes the data into a format func Encode(data KV, dataformat string) (string, error) { if dataformat == "" { return "", errors.New("dataformat is required") } if encoder, ok := dataformats[dataformat]; ok { return encoder.Encode(data) } return "", fmt.Errorf("dataformat %s is not supported", dataformat) } ================================================ FILE: pkg/fuzz/dataformat/dataformat_test.go ================================================ package dataformat import ( "testing" ) func TestDataformatDecodeEncode_JSON(t *testing.T) { obj := `{"foo":"bar"}` decoded, err := Decode(obj) if err != nil { t.Fatal(err) } if decoded.DataFormat != "json" { t.Fatal("unexpected data format") } if decoded.Data.Get("foo") != "bar" { t.Fatal("unexpected data") } encoded, err := Encode(decoded.Data, decoded.DataFormat) if err != nil { t.Fatal(err) } if encoded != obj { t.Fatal("unexpected data") } } func TestDataformatDecodeEncode_XML(t *testing.T) { obj := `bar` decoded, err := Decode(obj) if err != nil { t.Fatal(err) } if decoded.DataFormat != "xml" { t.Fatal("unexpected data format") } fooValue := decoded.Data.Get("foo") if fooValue == nil { t.Fatal("key 'foo' not found") } fooMap, ok := fooValue.(map[string]interface{}) if !ok { t.Fatal("type assertion to map[string]interface{} failed") } if fooMap["#text"] != "bar" { t.Fatal("unexpected data for '#text'") } if fooMap["-attr"] != "baz" { t.Fatal("unexpected data for '-attr'") } encoded, err := Encode(decoded.Data, decoded.DataFormat) if err != nil { t.Fatal(err) } if encoded != obj { t.Fatal("unexpected data") } } ================================================ FILE: pkg/fuzz/dataformat/form.go ================================================ package dataformat import ( "fmt" "regexp" "strconv" "strings" "github.com/projectdiscovery/gologger" mapsutil "github.com/projectdiscovery/utils/maps" urlutil "github.com/projectdiscovery/utils/url" ) const ( normalizedRegex = `_(\d+)$` ) var ( reNormalized = regexp.MustCompile(normalizedRegex) ) // == Handling Duplicate Query Parameters / Form Data == // Nuclei supports fuzzing duplicate query parameters by internally normalizing // them and denormalizing them back when creating request this normalization // can be leveraged to specify custom fuzzing behaviour in template as well // if a query like `?foo=bar&foo=baz&foo=fuzzz` is provided, it will be normalized to // foo_1=bar , foo_2=baz , foo=fuzzz (i.e last value is given original key which is usual behaviour in HTTP and its implementations) // this way this change does not break any existing rules in template given by keys-regex or keys // At same time if user wants to specify 2nd or 1st duplicate value in template, they can use foo_1 or foo_2 in keys-regex or keys // Note: By default all duplicate query parameters are fuzzed type Form struct{} var ( _ DataFormat = &Form{} ) // NewForm returns a new Form encoder func NewForm() *Form { return &Form{} } // IsType returns true if the data is Form encoded func (f *Form) IsType(data string) bool { return false } // Encode encodes the data into Form format func (f *Form) Encode(data KV) (string, error) { params := urlutil.NewOrderedParams() data.Iterate(func(key string, value any) bool { params.Add(key, fmt.Sprint(value)) return true }) normalized := map[string]map[string]string{} // Normalize the data for _, origKey := range data.OrderedMap.GetKeys() { // here origKey is base key without _1, _2 etc. if origKey != "" && !reNormalized.MatchString(origKey) { params.Iterate(func(key string, value []string) bool { if strings.HasPrefix(key, origKey) && reNormalized.MatchString(key) { m := map[string]string{} if normalized[origKey] != nil { m = normalized[origKey] } if len(value) == 1 { m[key] = value[0] } else { m[key] = "" } normalized[origKey] = m params.Del(key) } return true }) } } if len(normalized) > 0 { for k, v := range normalized { maxIndex := -1 for key := range v { matches := reNormalized.FindStringSubmatch(key) if len(matches) == 2 { dataIdx, err := strconv.Atoi(matches[1]) if err != nil { gologger.Verbose().Msgf("error converting normalized index(%v) to integer: %v", matches[1], err) continue } if dataIdx > maxIndex { maxIndex = dataIdx } } } if maxIndex >= 0 { // Ensure the slice is only created if maxIndex is valid data := make([]string, maxIndex+1) // Ensure the slice is large enough for key, value := range v { matches := reNormalized.FindStringSubmatch(key) if len(matches) == 2 { dataIdx, err := strconv.Atoi(matches[1]) // Error already checked above if err != nil { gologger.Verbose().Msgf("error converting data index to integer: %v", err) continue } // Validate dataIdx to avoid index out of range errors if dataIdx > 0 && dataIdx <= len(data) { data[dataIdx-1] = value // Use dataIdx-1 since slice is 0-indexed } else { gologger.Verbose().Msgf("data index out of range: %d", dataIdx) } } } if len(params.Get(k)) > 0 { data[maxIndex] = fmt.Sprint(params.Get(k)) // Use maxIndex which is the last index } // remove existing params.Del(k) if len(data) > 0 { params.Add(k, data...) } } } } encoded := params.Encode() return encoded, nil } // Decode decodes the data from Form format func (f *Form) Decode(data string) (KV, error) { ordered_params := urlutil.NewOrderedParams() ordered_params.Merge(data) values := mapsutil.NewOrderedMap[string, any]() ordered_params.Iterate(func(key string, value []string) bool { if len(value) == 1 { values.Set(key, value[0]) } else { // in case of multiple query params in form data // last value is considered and previous values are exposed with _1, _2, _3 etc. // note that last value will not be included in _1, _2, _3 etc. for i := 0; i < len(value)-1; i++ { values.Set(key+"_"+strconv.Itoa(i+1), value[i]) } values.Set(key, value[len(value)-1]) } return true }) return KVOrderedMap(&values), nil } // Name returns the name of the encoder func (f *Form) Name() string { return FormDataFormat } ================================================ FILE: pkg/fuzz/dataformat/json.go ================================================ package dataformat import ( "strings" jsoniter "github.com/json-iterator/go" ) // JSON is a JSON encoder // // For now JSON only supports objects as the root data type // and not arrays // // TODO: Support arrays + other JSON oddities by // adding more attributes to the map[string]interface{} type JSON struct{} var ( _ DataFormat = &JSON{} ) // NewJSON returns a new JSON encoder func NewJSON() *JSON { return &JSON{} } // IsType returns true if the data is JSON encoded func (j *JSON) IsType(data string) bool { return strings.HasPrefix(data, "{") && strings.HasSuffix(data, "}") } // Encode encodes the data into JSON format func (j *JSON) Encode(data KV) (string, error) { encoded, err := jsoniter.Marshal(data.Map) return string(encoded), err } // Decode decodes the data from JSON format func (j *JSON) Decode(data string) (KV, error) { var decoded map[string]interface{} err := jsoniter.Unmarshal([]byte(data), &decoded) return KVMap(decoded), err } // Name returns the name of the encoder func (j *JSON) Name() string { return JSONDataFormat } ================================================ FILE: pkg/fuzz/dataformat/kv.go ================================================ package dataformat import ( mapsutil "github.com/projectdiscovery/utils/maps" "golang.org/x/exp/maps" ) // KV is a key-value struct // that is implemented or used by fuzzing package // to represent a key-value pair // sometimes order or key-value pair is important (query params) // so we use ordered map to represent the data // if it's not important/significant (ex: json,xml) we use map // this also allows us to iteratively implement ordered map type KV struct { Map mapsutil.Map[string, any] OrderedMap *mapsutil.OrderedMap[string, any] } // Clones the current state of the KV struct func (kv *KV) Clone() KV { newKV := KV{} if kv.OrderedMap == nil { newKV.Map = maps.Clone(kv.Map) return newKV } clonedOrderedMap := kv.OrderedMap.Clone() newKV.OrderedMap = &clonedOrderedMap return newKV } // IsNIL returns true if the KV struct is nil func (kv *KV) IsNIL() bool { return kv.Map == nil && kv.OrderedMap == nil } // IsOrderedMap returns true if the KV struct is an ordered map func (kv *KV) IsOrderedMap() bool { return kv.OrderedMap != nil } // Set sets a value in the KV struct func (kv *KV) Set(key string, value any) { if kv.OrderedMap != nil { kv.OrderedMap.Set(key, value) return } if kv.Map == nil { kv.Map = make(map[string]interface{}) } kv.Map[key] = value } // Get gets a value from the KV struct func (kv *KV) Get(key string) interface{} { if kv.OrderedMap != nil { value, ok := kv.OrderedMap.Get(key) if !ok { return nil } return value } return kv.Map[key] } // Iterate iterates over the KV struct in insertion order func (kv *KV) Iterate(f func(key string, value any) bool) { if kv.OrderedMap != nil { kv.OrderedMap.Iterate(func(key string, value any) bool { return f(key, value) }) return } for key, value := range kv.Map { if !f(key, value) { break } } } // Delete deletes a key from the KV struct func (kv *KV) Delete(key string) bool { if kv.OrderedMap != nil { _, ok := kv.OrderedMap.Get(key) if !ok { return false } kv.OrderedMap.Delete(key) return true } _, ok := kv.Map[key] if !ok { return false } delete(kv.Map, key) return true } // KVMap returns a new KV struct with the given map func KVMap(data map[string]interface{}) KV { return KV{Map: data} } // KVOrderedMap returns a new KV struct with the given ordered map func KVOrderedMap(data *mapsutil.OrderedMap[string, any]) KV { return KV{OrderedMap: data} } // ToMap converts the ordered map to a map func ToMap(m *mapsutil.OrderedMap[string, any]) map[string]interface{} { data := make(map[string]interface{}) m.Iterate(func(key string, value any) bool { data[key] = value return true }) return data } // ToOrderedMap converts the map to an ordered map func ToOrderedMap(data map[string]interface{}) *mapsutil.OrderedMap[string, any] { m := mapsutil.NewOrderedMap[string, any]() for key, value := range data { m.Set(key, value) } return &m } ================================================ FILE: pkg/fuzz/dataformat/multipart.go ================================================ package dataformat import ( "bytes" "fmt" "io" "mime" "mime/multipart" "net/textproto" mapsutil "github.com/projectdiscovery/utils/maps" ) type MultiPartForm struct { boundary string filesMetadata map[string]FileMetadata } type FileMetadata struct { ContentType string Filename string } var ( _ DataFormat = &MultiPartForm{} ) // NewMultiPartForm returns a new MultiPartForm encoder func NewMultiPartForm() *MultiPartForm { return &MultiPartForm{ filesMetadata: make(map[string]FileMetadata), } } // SetFileMetadata sets the file metadata for a given field name func (m *MultiPartForm) SetFileMetadata(fieldName string, metadata FileMetadata) { if m.filesMetadata == nil { m.filesMetadata = make(map[string]FileMetadata) } m.filesMetadata[fieldName] = metadata } // GetFileMetadata gets the file metadata for a given field name func (m *MultiPartForm) GetFileMetadata(fieldName string) (FileMetadata, bool) { if m.filesMetadata == nil { return FileMetadata{}, false } metadata, exists := m.filesMetadata[fieldName] return metadata, exists } // IsType returns true if the data is MultiPartForm encoded func (m *MultiPartForm) IsType(data string) bool { // This method should be implemented to detect if the data is multipart form encoded return false } // Encode encodes the data into MultiPartForm format func (m *MultiPartForm) Encode(data KV) (string, error) { var b bytes.Buffer w := multipart.NewWriter(&b) if err := w.SetBoundary(m.boundary); err != nil { return "", err } var Itererr error data.Iterate(func(key string, value any) bool { var fw io.Writer var err error if fileMetadata, ok := m.filesMetadata[key]; ok { if filesArray, isArray := value.([]any); isArray { for _, file := range filesArray { h := make(textproto.MIMEHeader) h.Set("Content-Disposition", fmt.Sprintf(`form-data; name=%q; filename=%q`, key, fileMetadata.Filename)) h.Set("Content-Type", fileMetadata.ContentType) if fw, err = w.CreatePart(h); err != nil { Itererr = err return false } if _, err = fw.Write([]byte(file.(string))); err != nil { Itererr = err return false } } return true } } // Add field var values []string switch v := value.(type) { case nil: values = []string{""} case string: values = []string{v} case []string: values = v case []any: values = make([]string, len(v)) for i, item := range v { if item == nil { values[i] = "" } else { values[i] = fmt.Sprint(item) } } default: values = []string{fmt.Sprintf("%v", v)} } for _, val := range values { if fw, err = w.CreateFormField(key); err != nil { Itererr = err return false } if _, err = fw.Write([]byte(val)); err != nil { Itererr = err return false } } return true }) if Itererr != nil { return "", Itererr } _ = w.Close() return b.String(), nil } // ParseBoundary parses the boundary from the content type func (m *MultiPartForm) ParseBoundary(contentType string) error { _, params, err := mime.ParseMediaType(contentType) if err != nil { return err } m.boundary = params["boundary"] if m.boundary == "" { return fmt.Errorf("no boundary found in the content type") } // NOTE(dwisiswant0): boundary cannot exceed 70 characters according to // RFC-2046. if len(m.boundary) > 70 { return fmt.Errorf("boundary exceeds maximum length of 70 characters") } return nil } // Decode decodes the data from MultiPartForm format func (m *MultiPartForm) Decode(data string) (KV, error) { if m.boundary == "" { return KV{}, fmt.Errorf("boundary not set, call ParseBoundary first") } // Create a buffer from the string data b := bytes.NewBufferString(data) r := multipart.NewReader(b, m.boundary) form, err := r.ReadForm(32 << 20) // 32MB is the max memory used to parse the form if err != nil { return KV{}, err } defer func() { _ = form.RemoveAll() }() result := mapsutil.NewOrderedMap[string, any]() for key, values := range form.Value { if len(values) > 1 { result.Set(key, values) } else { result.Set(key, values[0]) } } if m.filesMetadata == nil { m.filesMetadata = make(map[string]FileMetadata) } for key, files := range form.File { fileContents := []interface{}{} var fileMetadataList []FileMetadata for _, fileHeader := range files { file, err := fileHeader.Open() if err != nil { return KV{}, err } buffer := new(bytes.Buffer) if _, err := buffer.ReadFrom(file); err != nil { _ = file.Close() return KV{}, err } _ = file.Close() fileContents = append(fileContents, buffer.String()) fileMetadataList = append(fileMetadataList, FileMetadata{ ContentType: fileHeader.Header.Get("Content-Type"), Filename: fileHeader.Filename, }) } result.Set(key, fileContents) // NOTE(dwisiswant0): store the first file's metadata instead of the // last one if len(fileMetadataList) > 0 { m.filesMetadata[key] = fileMetadataList[0] } } return KVOrderedMap(&result), nil } // Name returns the name of the encoder func (m *MultiPartForm) Name() string { return "multipart/form-data" } ================================================ FILE: pkg/fuzz/dataformat/multipart_test.go ================================================ package dataformat import ( "testing" mapsutil "github.com/projectdiscovery/utils/maps" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestMultiPartFormEncode(t *testing.T) { tests := []struct { name string fields map[string]any wantErr bool expected map[string]any }{ { name: "duplicate fields ([]string) - checkbox scenario", fields: map[string]any{ "interests": []string{"sports", "music", "reading"}, "colors": []string{"red", "blue"}, }, expected: map[string]any{ "interests": []string{"sports", "music", "reading"}, "colors": []string{"red", "blue"}, }, }, { name: "single string fields - backward compatibility", fields: map[string]any{ "username": "john", "email": "john@example.com", }, expected: map[string]any{ "username": "john", "email": "john@example.com", }, }, { name: "mixed types", fields: map[string]any{ "string": "text", "array": []string{"item1", "item2"}, "number": 42, // tests fmt.Sprint fallback "float": 3.14, // tests float conversion "boolean": true, // tests boolean conversion "zero": 0, // tests zero value "emptyStr": "", // tests empty string "negative": -123, // tests negative number "nil": nil, // tests nil value "mixedArray": []any{"str", 123, false, nil}, // tests mixed type array }, expected: map[string]any{ "string": "text", "array": []string{"item1", "item2"}, "number": "42", // numbers are converted to strings in multipart "float": "3.14", // floats are converted to strings "boolean": "true", // booleans are converted to strings "zero": "0", // zero value converted to string "emptyStr": "", // empty string remains empty "negative": "-123", // negative numbers converted to strings "nil": "", // nil values converted to "" string "mixedArray": []string{"str", "123", "false", ""}, // mixed array converted to string array }, }, { name: "empty array - should not appear in output", fields: map[string]any{ "emptyArray": []string{}, "normalField": "value", }, expected: map[string]any{ "normalField": "value", // emptyArray should not appear in decoded output }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { defer func() { if r := recover(); r != nil { t.Errorf("Test panicked: %v", r) } }() form := NewMultiPartForm() form.boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW" kv := mapsutil.NewOrderedMap[string, any]() for k, v := range tt.fields { kv.Set(k, v) } encoded, err := form.Encode(KVOrderedMap(&kv)) if tt.wantErr { require.Error(t, err) return } require.NoError(t, err) // Decode the encoded multipart data decoded, err := form.Decode(encoded) require.NoError(t, err) // Compare decoded values with expected values for expectedKey, expectedValue := range tt.expected { actualValue := decoded.Get(expectedKey) switch expected := expectedValue.(type) { case []string: actual, ok := actualValue.([]string) require.True(t, ok, "Expected []string for key %s, got %T", expectedKey, actualValue) assert.ElementsMatch(t, expected, actual, "Values mismatch for key %s", expectedKey) case []any: actual, ok := actualValue.([]any) require.True(t, ok, "Expected []any for key %s, got %T", expectedKey, actualValue) assert.ElementsMatch(t, expected, actual, "Values mismatch for key %s", expectedKey) case string: actual, ok := actualValue.(string) require.True(t, ok, "Expected string for key %s, got %T", expectedKey, actualValue) assert.Equal(t, expected, actual, "Values mismatch for key %s", expectedKey) default: assert.Equal(t, expected, actualValue, "Values mismatch for key %s", expectedKey) } } // Ensure no unexpected keys are present in decoded output decoded.Iterate(func(key string, value any) bool { _, exists := tt.expected[key] assert.True(t, exists, "Unexpected key %s found in decoded output", key) return true }) t.Logf("Encoded output:\n%s", encoded) }) } } func TestMultiPartFormRoundTrip(t *testing.T) { defer func() { if r := recover(); r != nil { t.Errorf("Test panicked: %v", r) } }() form := NewMultiPartForm() form.boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW" original := mapsutil.NewOrderedMap[string, any]() original.Set("username", "john") original.Set("interests", []string{"sports", "music", "reading"}) encoded, err := form.Encode(KVOrderedMap(&original)) require.NoError(t, err) decoded, err := form.Decode(encoded) require.NoError(t, err) assert.Equal(t, "john", decoded.Get("username")) assert.ElementsMatch(t, []string{"sports", "music", "reading"}, decoded.Get("interests")) t.Logf("Encoded output:\n%s", encoded) } func TestMultiPartFormFileUpload(t *testing.T) { defer func() { if r := recover(); r != nil { t.Errorf("Test panicked: %v", r) } }() // Test decoding of a manually crafted multipart form with files form := NewMultiPartForm() form.boundary = "----WebKitFormBoundaryFileUploadTest" // Manually craft a multipart form with file uploads multipartData := `------WebKitFormBoundaryFileUploadTest Content-Disposition: form-data; name="name" John Doe ------WebKitFormBoundaryFileUploadTest Content-Disposition: form-data; name="email" john@example.com ------WebKitFormBoundaryFileUploadTest Content-Disposition: form-data; name="profile_picture"; filename="profile.jpg" Content-Type: image/jpeg fake_jpeg_binary_data_here ------WebKitFormBoundaryFileUploadTest Content-Disposition: form-data; name="documents"; filename="resume.pdf" Content-Type: application/pdf fake_pdf_content_1 ------WebKitFormBoundaryFileUploadTest Content-Disposition: form-data; name="documents"; filename="cover_letter.pdf" Content-Type: application/pdf fake_pdf_content_2 ------WebKitFormBoundaryFileUploadTest Content-Disposition: form-data; name="skills" Go ------WebKitFormBoundaryFileUploadTest Content-Disposition: form-data; name="skills" JavaScript ------WebKitFormBoundaryFileUploadTest Content-Disposition: form-data; name="skills" Python ------WebKitFormBoundaryFileUploadTest-- ` // Test decoding decoded, err := form.Decode(multipartData) require.NoError(t, err) // Verify regular fields assert.Equal(t, "John Doe", decoded.Get("name")) assert.Equal(t, "john@example.com", decoded.Get("email")) assert.Equal(t, []string{"Go", "JavaScript", "Python"}, decoded.Get("skills")) // Verify file fields profilePicture := decoded.Get("profile_picture") require.NotNil(t, profilePicture) profileArray, ok := profilePicture.([]interface{}) require.True(t, ok, "Expected []interface{} for profile_picture") require.Len(t, profileArray, 1) assert.Equal(t, "fake_jpeg_binary_data_here", profileArray[0]) documents := decoded.Get("documents") require.NotNil(t, documents) documentsArray, ok := documents.([]interface{}) require.True(t, ok, "Expected []interface{} for documents") require.Len(t, documentsArray, 2) assert.Contains(t, documentsArray, "fake_pdf_content_1") assert.Contains(t, documentsArray, "fake_pdf_content_2") } func TestMultiPartForm_SetGetFileMetadata(t *testing.T) { form := NewMultiPartForm() metadata := FileMetadata{ ContentType: "image/jpeg", Filename: "test.jpg", } form.SetFileMetadata("avatar", metadata) // Test GetFileMetadata for existing field retrievedMetadata, exists := form.GetFileMetadata("avatar") assert.True(t, exists) assert.Equal(t, metadata.ContentType, retrievedMetadata.ContentType) assert.Equal(t, metadata.Filename, retrievedMetadata.Filename) // Test GetFileMetadata for non-existing field _, exists = form.GetFileMetadata("nonexistent") assert.False(t, exists) } func TestMultiPartForm_FilesMetadataInitialization(t *testing.T) { form := NewMultiPartForm() assert.NotNil(t, form.filesMetadata) metadata := FileMetadata{ ContentType: "text/plain", Filename: "test.txt", } form.SetFileMetadata("file", metadata) retrievedMetadata, exists := form.GetFileMetadata("file") assert.True(t, exists) assert.Equal(t, metadata, retrievedMetadata) } func TestMultiPartForm_BoundaryValidation(t *testing.T) { form := NewMultiPartForm() // Test valid boundary err := form.ParseBoundary("multipart/form-data; boundary=testboundary") assert.NoError(t, err) assert.Equal(t, "testboundary", form.boundary) // Test missing boundary err = form.ParseBoundary("multipart/form-data") assert.Error(t, err) assert.Contains(t, err.Error(), "no boundary found") // Test boundary too long (over 70 characters) longBoundary := "multipart/form-data; boundary=" + string(make([]byte, 71)) for i := range longBoundary[len("multipart/form-data; boundary="):] { longBoundary = longBoundary[:len("multipart/form-data; boundary=")+i] + "a" + longBoundary[len("multipart/form-data; boundary=")+i+1:] } err = form.ParseBoundary(longBoundary) assert.Error(t, err) assert.Contains(t, err.Error(), "boundary exceeds maximum length") } func TestMultiPartForm_DecodeRequiresBoundary(t *testing.T) { form := NewMultiPartForm() // Decode should fail if boundary is not set _, err := form.Decode("some data") assert.Error(t, err) assert.Contains(t, err.Error(), "boundary not set") } func TestMultiPartForm_MultipleFilesMetadata(t *testing.T) { form := NewMultiPartForm() form.boundary = "----WebKitFormBoundaryMultiFileTest" // Test with multiple files having the same field name multipartData := `------WebKitFormBoundaryMultiFileTest Content-Disposition: form-data; name="documents"; filename="file1.txt" Content-Type: text/plain content1 ------WebKitFormBoundaryMultiFileTest Content-Disposition: form-data; name="documents"; filename="file2.txt" Content-Type: text/plain content2 ------WebKitFormBoundaryMultiFileTest-- ` decoded, err := form.Decode(multipartData) require.NoError(t, err) // Verify files are decoded correctly documents := decoded.Get("documents") require.NotNil(t, documents) documentsArray, ok := documents.([]interface{}) require.True(t, ok) require.Len(t, documentsArray, 2) assert.Contains(t, documentsArray, "content1") assert.Contains(t, documentsArray, "content2") // Verify metadata for the field exists (should be from the first file) metadata, exists := form.GetFileMetadata("documents") assert.True(t, exists) assert.Equal(t, "text/plain", metadata.ContentType) assert.Equal(t, "file1.txt", metadata.Filename) // Should be from first file, not last } func TestMultiPartForm_SetFileMetadataWithNilMap(t *testing.T) { form := &MultiPartForm{} // SetFileMetadata should handle nil filesMetadata metadata := FileMetadata{ ContentType: "application/pdf", Filename: "document.pdf", } form.SetFileMetadata("doc", metadata) // Should be able to retrieve the metadata retrievedMetadata, exists := form.GetFileMetadata("doc") assert.True(t, exists) assert.Equal(t, metadata, retrievedMetadata) } func TestMultiPartForm_GetFileMetadataWithNilMap(t *testing.T) { form := &MultiPartForm{} // GetFileMetadata should handle nil filesMetadata gracefully _, exists := form.GetFileMetadata("anything") assert.False(t, exists) } ================================================ FILE: pkg/fuzz/dataformat/raw.go ================================================ package dataformat type Raw struct{} var ( _ DataFormat = &Raw{} ) // NewRaw returns a new Raw encoder func NewRaw() *Raw { return &Raw{} } // IsType returns true if the data is Raw encoded func (r *Raw) IsType(data string) bool { return false } // Encode encodes the data into Raw format func (r *Raw) Encode(data KV) (string, error) { return data.Get("value").(string), nil } // Decode decodes the data from Raw format func (r *Raw) Decode(data string) (KV, error) { return KVMap(map[string]interface{}{ "value": data, }), nil } // Name returns the name of the encoder func (r *Raw) Name() string { return RawDataFormat } ================================================ FILE: pkg/fuzz/dataformat/xml.go ================================================ package dataformat import ( "fmt" "regexp" "strings" "github.com/clbanning/mxj/v2" ) // XML is an XML encoder type XML struct{} // NewXML returns a new XML encoder func NewXML() *XML { return &XML{} } // IsType returns true if the data is XML encoded func (x *XML) IsType(data string) bool { return strings.HasPrefix(data, "<") && strings.HasSuffix(data, ">") } // Encode encodes the data into XML format func (x *XML) Encode(data KV) (string, error) { var header string if value := data.Get("#_xml_header"); value != nil { header = value.(string) data.Delete("#_xml_header") } marshalled, err := mxj.Map(data.Map).Xml() if err != nil { return "", err } if header != "" { return fmt.Sprintf("%s", header, string(marshalled)), nil } return string(marshalled), err } var xmlHeader = regexp.MustCompile(`\<\?(.*)\?\>`) // Decode decodes the data from XML format func (x *XML) Decode(data string) (KV, error) { var prefixStr string prefix := xmlHeader.FindAllStringSubmatch(data, -1) if len(prefix) > 0 { prefixStr = prefix[0][1] } decoded, err := mxj.NewMapXml([]byte(data)) if err != nil { return KV{}, err } decoded["#_xml_header"] = prefixStr return KVMap(decoded), nil } // Name returns the name of the encoder func (x *XML) Name() string { return XMLDataFormat } ================================================ FILE: pkg/fuzz/doc.go ================================================ // Package fuzz contains the fuzzing functionality for dynamic // fuzzing of HTTP requests and its respective implementation. package fuzz ================================================ FILE: pkg/fuzz/execute.go ================================================ package fuzz import ( "fmt" "io" "maps" "regexp" "strings" "github.com/pkg/errors" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/component" fuzzStats "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/stats" "github.com/projectdiscovery/nuclei/v3/pkg/protocols" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/marker" "github.com/projectdiscovery/nuclei/v3/pkg/utils/json" "github.com/projectdiscovery/retryablehttp-go" "github.com/projectdiscovery/utils/errkit" mapsutil "github.com/projectdiscovery/utils/maps" sliceutil "github.com/projectdiscovery/utils/slice" urlutil "github.com/projectdiscovery/utils/url" ) var ( ErrRuleNotApplicable = errkit.New("rule not applicable") ) // IsErrRuleNotApplicable checks if an error is due to rule not applicable func IsErrRuleNotApplicable(err error) bool { if err == nil { return false } if strings.Contains(err.Error(), "rule not applicable") { return true } return false } // ExecuteRuleInput is the input for rule Execute function type ExecuteRuleInput struct { // Input is the context args input Input *contextargs.Context // Callback is the callback for generated rule requests Callback func(GeneratedRequest) bool // InteractURLs contains interact urls for execute call InteractURLs []string // Values contains dynamic values for the rule Values map[string]interface{} // BaseRequest is the base http request for fuzzing rule BaseRequest *retryablehttp.Request // DisplayFuzzPoints is a flag to display fuzz points DisplayFuzzPoints bool // ApplyPayloadInitialTransformation is an optional function // to transform the payload initially based on analyzer rules ApplyPayloadInitialTransformation func(string, map[string]interface{}) string AnalyzerParams map[string]interface{} } // GeneratedRequest is a single generated request for rule type GeneratedRequest struct { // Request is the http request for rule Request *retryablehttp.Request // InteractURLs is the list of interactsh urls InteractURLs []string // DynamicValues contains dynamic values map DynamicValues map[string]interface{} // Component is the component for the request Component component.Component // Parameter being fuzzed Parameter string // Key is the key for the request Key string // Value is the value for the request Value string // OriginalValue is the original value for the request OriginalValue string // OriginalPayload is the original payload for the request OriginalPayload string } // Execute executes a fuzzing rule accepting a callback on which // generated requests are returned. // // Input is not thread safe and should not be shared between concurrent // goroutines. func (rule *Rule) Execute(input *ExecuteRuleInput) (err error) { if !rule.isInputURLValid(input.Input) { return errkit.Newf("rule not applicable: invalid input url: %v", input.Input.MetaInput.Input) } if input.BaseRequest == nil && input.Input.MetaInput.ReqResp == nil { return errkit.Newf("rule not applicable: both base request and reqresp are nil for %v", input.Input.MetaInput.Input) } var finalComponentList []component.Component // match rule part with component name displayDebugFuzzPoints := make(map[string]map[string]string) for _, componentName := range component.Components { if rule.Part != componentName && !sliceutil.Contains(rule.Parts, componentName) && rule.partType != requestPartType { continue } component := component.New(componentName) discovered, err := component.Parse(input.BaseRequest) if err != nil { gologger.Verbose().Msgf("Could not parse component %s: %s\n", componentName, err) continue } if !discovered { continue } // check rule applicable on this component if !rule.checkRuleApplicableOnComponent(component) { continue } // Debugging display for fuzz points if input.DisplayFuzzPoints { displayDebugFuzzPoints[componentName] = make(map[string]string) _ = component.Iterate(func(key string, value interface{}) error { displayDebugFuzzPoints[componentName][key] = fmt.Sprintf("%v", value) return nil }) } if rule.options.FuzzStatsDB != nil { _ = component.Iterate(func(key string, value interface{}) error { rule.options.FuzzStatsDB.RecordComponentEvent(fuzzStats.ComponentEvent{ URL: input.Input.MetaInput.Target(), ComponentType: componentName, ComponentName: fmt.Sprintf("%v", value), }) return nil }) } finalComponentList = append(finalComponentList, component) } if len(displayDebugFuzzPoints) > 0 { marshalled, _ := json.MarshalIndent(displayDebugFuzzPoints, "", " ") gologger.Info().Msgf("[%s] Fuzz points for %s [%s]\n%s\n", rule.options.TemplateID, input.Input.MetaInput.Input, input.BaseRequest.Method, string(marshalled)) } if len(finalComponentList) == 0 { return errkit.Newf("rule not applicable: no component matched on this rule") } baseValues := input.Values if rule.generator == nil { for _, component := range finalComponentList { // get vars from variables while replacing interactsh urls evaluatedValues, interactURLs := rule.options.Variables.EvaluateWithInteractsh(baseValues, rule.options.Interactsh) input.Values = generators.MergeMaps(evaluatedValues, baseValues, rule.options.Options.Vars.AsMap(), rule.options.Constants) // evaluate all vars with interactsh input.Values, interactURLs = rule.evaluateVarsWithInteractsh(input.Values, interactURLs) input.InteractURLs = interactURLs err := rule.executeRuleValues(input, component) if err != nil { return err } } return nil } mainLoop: for _, component := range finalComponentList { iterator := rule.generator.NewIterator() for { values, next := iterator.Value() if !next { continue mainLoop } // get vars from variables while replacing interactsh urls evaluatedValues, interactURLs := rule.options.Variables.EvaluateWithInteractsh(generators.MergeMaps(values, baseValues), rule.options.Interactsh) input.Values = generators.MergeMaps(values, evaluatedValues, baseValues, rule.options.Options.Vars.AsMap(), rule.options.Constants) // evaluate all vars with interactsh input.Values, interactURLs = rule.evaluateVarsWithInteractsh(input.Values, interactURLs) input.InteractURLs = interactURLs if err := rule.executeRuleValues(input, component); err != nil { if err == io.EOF { return nil } gologger.Warning().Msgf("[%s] Could not execute rule: %s\n", rule.options.TemplateID, err) return err } } } return nil } // evaluateVars evaluates variables in a string using available executor options func (rule *Rule) evaluateVars(input string) (string, error) { if rule.options == nil { return input, nil } data := generators.MergeMaps( rule.options.Variables.GetAll(), rule.options.Constants, rule.options.Options.Vars.AsMap(), ) exprs := expressions.FindExpressions(input, marker.ParenthesisOpen, marker.ParenthesisClose, data) err := expressions.ContainsUnresolvedVariables(exprs...) if err != nil { return input, err } eval, err := expressions.Evaluate(input, data) if err != nil { return input, err } return eval, nil } // evaluateVarsWithInteractsh evaluates the variables with Interactsh URLs and updates them accordingly. func (rule *Rule) evaluateVarsWithInteractsh(data map[string]interface{}, interactshUrls []string) (map[string]interface{}, []string) { // Check if Interactsh options are configured if rule.options.Interactsh != nil { data = maps.Clone(data) interactshUrlsMap := make(map[string]struct{}) for _, url := range interactshUrls { interactshUrlsMap[url] = struct{}{} } interactshUrls = mapsutil.GetKeys(interactshUrlsMap) // Iterate through the data to replace and evaluate variables with Interactsh URLs for k, v := range data { value := fmt.Sprint(v) // Replace variables with Interactsh URLs and collect new URLs got, oastUrls := rule.options.Interactsh.Replace(value, interactshUrls) if got != value { data[k] = got } // Append new OAST URLs if any if len(oastUrls) > 0 { for _, url := range oastUrls { if _, ok := interactshUrlsMap[url]; !ok { interactshUrlsMap[url] = struct{}{} interactshUrls = append(interactshUrls, url) } } } // Evaluate the replaced data evaluatedData, err := expressions.Evaluate(got, data) if err == nil { // Update the data if there is a change after evaluation if evaluatedData != got { data[k] = evaluatedData } } } } // Return the updated data and Interactsh URLs without any error return data, interactshUrls } // isInputURLValid returns true if url is valid after parsing it func (rule *Rule) isInputURLValid(input *contextargs.Context) bool { if input == nil || input.MetaInput == nil || input.MetaInput.Input == "" { return false } _, err := urlutil.Parse(input.MetaInput.Input) return err == nil } // executeRuleValues executes a rule with a set of values func (rule *Rule) executeRuleValues(input *ExecuteRuleInput, ruleComponent component.Component) error { // if we are only fuzzing values if len(rule.Fuzz.Value) > 0 { for _, value := range rule.Fuzz.Value { originalPayload := value if err := rule.executePartRule(input, ValueOrKeyValue{Value: value, OriginalPayload: originalPayload}, ruleComponent); err != nil { if component.IsErrSetValue(err) { // this are errors due to format restrictions // ex: fuzzing string value in a json int field continue } return err } } return nil } // if we are fuzzing both keys and values if rule.Fuzz.KV != nil { var gotErr error rule.Fuzz.KV.Iterate(func(key, value string) bool { if err := rule.executePartRule(input, ValueOrKeyValue{Key: key, Value: value}, ruleComponent); err != nil { if component.IsErrSetValue(err) { // this are errors due to format restrictions // ex: fuzzing string value in a json int field return true } gotErr = err return false } return true }) // if mode is multiple now build and execute it if rule.modeType == multipleModeType { rule.Fuzz.KV.Iterate(func(key, value string) bool { var evaluated string evaluated, input.InteractURLs = rule.executeEvaluate(input, key, "", value, input.InteractURLs) if err := ruleComponent.SetValue(key, evaluated); err != nil { return true } return true }) req, err := ruleComponent.Rebuild() if err != nil { return err } if gotErr := rule.execWithInput(input, req, input.InteractURLs, ruleComponent, "", "", "", "", "", ""); gotErr != nil { return gotErr } } return gotErr } // something else is wrong return fmt.Errorf("no fuzz values specified") } // Compile compiles a fuzzing rule and initializes it for operation func (rule *Rule) Compile(generator *generators.PayloadGenerator, options *protocols.ExecutorOptions) error { // If a payload generator is specified from base request, use it // for payload values. if generator != nil { rule.generator = generator } rule.options = options // Resolve the default enums if rule.Mode != "" { if valueType, ok := stringToModeType[rule.Mode]; !ok { return errors.Errorf("invalid mode value specified: %s", rule.Mode) } else { rule.modeType = valueType } } else { rule.modeType = multipleModeType } if rule.Part != "" { if valueType, ok := stringToPartType[rule.Part]; !ok { return errors.Errorf("invalid part value specified: %s", rule.Part) } else { rule.partType = valueType } } if rule.Part == "" && len(rule.Parts) == 0 { return errors.Errorf("no part specified for rule") } if rule.Type != "" { if valueType, ok := stringToRuleType[rule.Type]; !ok { return errors.Errorf("invalid type value specified: %s", rule.Type) } else { rule.ruleType = valueType } } else { rule.ruleType = replaceRuleType } // Initialize other required regexes and maps if len(rule.Keys) > 0 { rule.keysMap = make(map[string]struct{}) } // eval vars in "keys" for _, key := range rule.Keys { evaluatedKey, err := rule.evaluateVars(key) if err != nil { return errors.Wrap(err, "could not evaluate key") } rule.keysMap[strings.ToLower(evaluatedKey)] = struct{}{} } // eval vars in "values" for _, value := range rule.ValuesRegex { evaluatedValue, err := rule.evaluateVars(value) if err != nil { return errors.Wrap(err, "could not evaluate value regex") } compiled, err := regexp.Compile(evaluatedValue) if err != nil { return errors.Wrap(err, "could not compile value regex") } rule.valuesRegex = append(rule.valuesRegex, compiled) } // eval vars in "keys-regex" for _, value := range rule.KeysRegex { evaluatedValue, err := rule.evaluateVars(value) if err != nil { return errors.Wrap(err, "could not evaluate key regex") } compiled, err := regexp.Compile(evaluatedValue) if err != nil { return errors.Wrap(err, "could not compile key regex") } rule.keysRegex = append(rule.keysRegex, compiled) } if rule.ruleType != replaceRegexRuleType { if rule.ReplaceRegex != "" { return errors.Errorf("replace-regex is only applicable for replace and replace-regex rule types") } } else { if rule.ReplaceRegex == "" { return errors.Errorf("replace-regex is required for replace-regex rule type") } evalReplaceRegex, err := rule.evaluateVars(rule.ReplaceRegex) if err != nil { return errors.Wrap(err, "could not evaluate replace regex") } compiled, err := regexp.Compile(evalReplaceRegex) if err != nil { return errors.Wrap(err, "could not compile replace regex") } rule.replaceRegex = compiled } return nil } ================================================ FILE: pkg/fuzz/execute_race_test.go ================================================ package fuzz import ( "sync" "testing" "github.com/projectdiscovery/nuclei/v3/pkg/protocols" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh" ) func TestEvaluateVarsWithInteractsh_RaceCondition(t *testing.T) { rule := &Rule{} rule.options = &protocols.ExecutorOptions{ Interactsh: &interactsh.Client{}, } sharedData := map[string]interface{}{ "var1": "value1", "var2": "{{var1}}_suffix", "var3": "prefix_{{var1}}", } var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() rule.evaluateVarsWithInteractsh(sharedData, nil) }() } wg.Wait() } ================================================ FILE: pkg/fuzz/frequency/tracker.go ================================================ package frequency import ( "net" "net/url" "os" "strings" "sync" "sync/atomic" "github.com/bluele/gcache" "github.com/projectdiscovery/gologger" ) // Tracker implements a frequency tracker for a given input // which is used to determine uninteresting input parameters // which are not that interesting from fuzzing perspective for a template // and target combination. // // This is used to reduce the number of requests made during fuzzing // for parameters that are less likely to give results for a rule. type Tracker struct { frequencies gcache.Cache paramOccurrenceThreshold int isDebug bool } const ( DefaultMaxTrackCount = 10000 DefaultParamOccurrenceThreshold = 10 ) type cacheItem struct { errors atomic.Int32 sync.Once } // New creates a new frequency tracker with a given maximum // number of params to track in LRU fashion with a max error threshold func New(maxTrackCount, paramOccurrenceThreshold int) *Tracker { gc := gcache.New(maxTrackCount).ARC().Build() var isDebug bool if os.Getenv("FREQ_DEBUG") != "" { isDebug = true } return &Tracker{ isDebug: isDebug, frequencies: gc, paramOccurrenceThreshold: paramOccurrenceThreshold, } } func (t *Tracker) Close() { t.frequencies.Purge() } // MarkParameter marks a parameter as frequently occurring once. // // The logic requires a parameter to be marked as frequently occurring // multiple times before it's considered as frequently occurring. func (t *Tracker) MarkParameter(parameter, target, template string) { normalizedTarget := normalizeTarget(target) key := getFrequencyKey(parameter, normalizedTarget, template) if t.isDebug { gologger.Verbose().Msgf("[%s] Marking %s as found uninteresting", template, key) } existingCacheItem, err := t.frequencies.GetIFPresent(key) if err != nil || existingCacheItem == nil { newItem := &cacheItem{errors: atomic.Int32{}} newItem.errors.Store(1) _ = t.frequencies.Set(key, newItem) return } existingCacheItemValue := existingCacheItem.(*cacheItem) existingCacheItemValue.errors.Add(1) _ = t.frequencies.Set(key, existingCacheItemValue) } // IsParameterFrequent checks if a parameter is frequently occurring // in the input with no much results. func (t *Tracker) IsParameterFrequent(parameter, target, template string) bool { normalizedTarget := normalizeTarget(target) key := getFrequencyKey(parameter, normalizedTarget, template) if t.isDebug { gologger.Verbose().Msgf("[%s] Checking if %s is frequently found uninteresting", template, key) } existingCacheItem, err := t.frequencies.GetIFPresent(key) if err != nil { return false } existingCacheItemValue := existingCacheItem.(*cacheItem) if existingCacheItemValue.errors.Load() >= int32(t.paramOccurrenceThreshold) { existingCacheItemValue.Do(func() { gologger.Verbose().Msgf("[%s] Skipped %s from parameter for %s as found uninteresting %d times", template, parameter, target, existingCacheItemValue.errors.Load()) }) return true } return false } // UnmarkParameter unmarks a parameter as frequently occurring. This carries // more weight and resets the frequency counter for the parameter causing // it to be checked again. This is done when results are found. func (t *Tracker) UnmarkParameter(parameter, target, template string) { normalizedTarget := normalizeTarget(target) key := getFrequencyKey(parameter, normalizedTarget, template) if t.isDebug { gologger.Verbose().Msgf("[%s] Unmarking %s as frequently found uninteresting", template, key) } _ = t.frequencies.Remove(key) } func getFrequencyKey(parameter, target, template string) string { var sb strings.Builder sb.WriteString(target) sb.WriteString(":") sb.WriteString(template) sb.WriteString(":") sb.WriteString(parameter) str := sb.String() return str } func normalizeTarget(value string) string { finalValue := value if strings.HasPrefix(value, "http") { if parsed, err := url.Parse(value); err == nil { hostname := parsed.Host finalPort := parsed.Port() if finalPort == "" { if parsed.Scheme == "https" { finalPort = "443" } else { finalPort = "80" } hostname = net.JoinHostPort(parsed.Host, finalPort) } finalValue = hostname } } return finalValue } ================================================ FILE: pkg/fuzz/fuzz.go ================================================ package fuzz import ( "regexp" "strings" "github.com/projectdiscovery/nuclei/v3/pkg/protocols" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators" ) // Rule is a single rule which describes how to fuzz the request type Rule struct { // description: | // 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. // values: // - "replace" // - "prefix" // - "postfix" // - "infix" Type string `yaml:"type,omitempty" json:"type,omitempty" jsonschema:"title=type of rule,description=Type of fuzzing rule to perform,enum=replace,enum=prefix,enum=postfix,enum=infix,enum=replace-regex"` ruleType ruleType // description: | // Part is the part of request to fuzz. // values: // - "query" // - "header" // - "path" // - "body" // - "cookie" // - "request" Part string `yaml:"part,omitempty" json:"part,omitempty" jsonschema:"title=part of rule,description=Part of request rule to fuzz,enum=query,enum=header,enum=path,enum=body,enum=cookie,enum=request"` partType partType // description: | // 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. // values: // - "query" // - "header" // - "path" // - "body" // - "cookie" // - "request" Parts []string `yaml:"parts,omitempty" json:"parts,omitempty" jsonschema:"title=parts of rule,description=Part of request rule to fuzz,enum=query,enum=header,enum=path,enum=body,enum=cookie,enum=request"` // description: | // Mode is the mode of fuzzing to perform. // // single fuzzes one value at a time. multiple fuzzes all values at same time. // values: // - "single" // - "multiple" Mode string `yaml:"mode,omitempty" json:"mode,omitempty" jsonschema:"title=mode of rule,description=Mode of request rule to fuzz,enum=single,enum=multiple"` modeType modeType // description: | // Keys is the optional list of key named parameters to fuzz. // examples: // - name: Examples of keys // value: > // []string{"url", "file", "host"} Keys []string `yaml:"keys,omitempty" json:"keys,omitempty" jsonschema:"title=keys of parameters to fuzz,description=Keys of parameters to fuzz"` keysMap map[string]struct{} // description: | // KeysRegex is the optional list of regex key parameters to fuzz. // examples: // - name: Examples of key regex // value: > // []string{"url.*"} KeysRegex []string `yaml:"keys-regex,omitempty" json:"keys-regex,omitempty" jsonschema:"title=keys regex to fuzz,description=Regex of parameter keys to fuzz"` keysRegex []*regexp.Regexp // description: | // Values is the optional list of regex value parameters to fuzz. // examples: // - name: Examples of value regex // value: > // []string{"https?://.*"} ValuesRegex []string `yaml:"values,omitempty" json:"values,omitempty" jsonschema:"title=values regex to fuzz,description=Regex of parameter values to fuzz"` valuesRegex []*regexp.Regexp // 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 Fuzz SliceOrMapSlice `yaml:"fuzz,omitempty" json:"fuzz,omitempty" jsonschema:"title=payloads of fuzz rule,description=Payloads to perform fuzzing substitutions with"` // description: | // replace-regex is regex for regex-replace rule type // it is only required for replace-regex rule type // examples: // - type: replace-regex // replace-regex: "https?://.*" ReplaceRegex string `yaml:"replace-regex,omitempty" json:"replace-regex,omitempty" jsonschema:"title=replace regex of rule,description=Regex for regex-replace rule type"` replaceRegex *regexp.Regexp `yaml:"-" json:"-"` options *protocols.ExecutorOptions generator *generators.PayloadGenerator } // ruleType is the type of rule enum declaration type ruleType int const ( replaceRuleType ruleType = iota + 1 prefixRuleType postfixRuleType infixRuleType replaceRegexRuleType ) var stringToRuleType = map[string]ruleType{ "replace": replaceRuleType, "prefix": prefixRuleType, "postfix": postfixRuleType, "infix": infixRuleType, "replace-regex": replaceRegexRuleType, } // partType is the part of rule enum declaration type partType int const ( queryPartType partType = iota + 1 headersPartType pathPartType bodyPartType cookiePartType requestPartType ) var stringToPartType = map[string]partType{ "query": queryPartType, "header": headersPartType, "path": pathPartType, "body": bodyPartType, "cookie": cookiePartType, "request": requestPartType, // request means all request parts } // modeType is the mode of rule enum declaration type modeType int const ( singleModeType modeType = iota + 1 multipleModeType ) var stringToModeType = map[string]modeType{ "single": singleModeType, "multiple": multipleModeType, } // matchKeyOrValue matches key value parameters with rule parameters func (rule *Rule) matchKeyOrValue(key, value string) bool { if len(rule.keysMap) == 0 && len(rule.valuesRegex) == 0 && len(rule.keysRegex) == 0 { return true } if value != "" { for _, regex := range rule.valuesRegex { if regex.MatchString(value) { return true } } } if (len(rule.keysMap) > 0 || len(rule.keysRegex) > 0) && key != "" { if _, ok := rule.keysMap[strings.ToLower(key)]; ok { return true } for _, regex := range rule.keysRegex { if regex.MatchString(key) { return true } } } return false } ================================================ FILE: pkg/fuzz/fuzz_test.go ================================================ package fuzz import ( "testing" "github.com/projectdiscovery/goflags" "github.com/projectdiscovery/nuclei/v3/pkg/protocols" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/variables" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/nuclei/v3/pkg/utils" "github.com/stretchr/testify/require" ) func TestRuleMatchKeyOrValue(t *testing.T) { rule := &Rule{ Part: "query", } err := rule.Compile(nil, nil) require.NoError(t, err, "could not compile rule") result := rule.matchKeyOrValue("url", "") require.True(t, result, "could not get correct result") t.Run("key", func(t *testing.T) { rule := &Rule{Keys: []string{"url"}, Part: "query"} err := rule.Compile(nil, nil) require.NoError(t, err, "could not compile rule") result := rule.matchKeyOrValue("url", "") require.True(t, result, "could not get correct result") result = rule.matchKeyOrValue("test", "") require.False(t, result, "could not get correct result") }) t.Run("value", func(t *testing.T) { rule := &Rule{ValuesRegex: []string{`https?:\/\/?([-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b)*(\/[\/\d\w\.-]*)*(?:[\?])*(.+)*`}, Part: "query"} err := rule.Compile(nil, nil) require.NoError(t, err, "could not compile rule") result := rule.matchKeyOrValue("", "http://localhost:80") require.True(t, result, "could not get correct result") result = rule.matchKeyOrValue("test", "random") require.False(t, result, "could not get correct result") }) } func TestEvaluateVariables(t *testing.T) { t.Run("keys", func(t *testing.T) { rule := &Rule{ Keys: []string{"{{foo_var}}"}, Part: "query", } // mock templateVars := variables.Variable{ InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1), } templateVars.Set("foo_var", "foo_var_value") constants := map[string]interface{}{ "const_key": "const_value", } options := &types.Options{} // runtime vars (to simulate CLI) runtimeVars := goflags.RuntimeMap{} _ = runtimeVars.Set("runtime_key=runtime_value") options.Vars = runtimeVars executorOpts := &protocols.ExecutorOptions{ Variables: templateVars, Constants: constants, Options: options, } err := rule.Compile(nil, executorOpts) require.NoError(t, err, "could not compile rule") result := rule.matchKeyOrValue("foo_var_value", "test_value") require.True(t, result, "should match evaluated variable key") result = rule.matchKeyOrValue("{{foo_var}}", "test_value") require.False(t, result, "should not match unevaluated variable key") }) t.Run("keys-regex", func(t *testing.T) { rule := &Rule{ KeysRegex: []string{"^{{foo_var}}"}, Part: "query", } templateVars := variables.Variable{ InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1), } templateVars.Set("foo_var", "foo_var_value") executorOpts := &protocols.ExecutorOptions{ Variables: templateVars, Constants: map[string]interface{}{}, Options: &types.Options{}, } err := rule.Compile(nil, executorOpts) require.NoError(t, err, "could not compile rule") result := rule.matchKeyOrValue("foo_var_value", "test_value") require.True(t, result, "should match evaluated variable in regex") result = rule.matchKeyOrValue("other_key", "test_value") require.False(t, result, "should not match non-matching key") }) t.Run("values-regex", func(t *testing.T) { rule := &Rule{ ValuesRegex: []string{"{{foo_var}}"}, Part: "query", } templateVars := variables.Variable{ InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1), } templateVars.Set("foo_var", "test_pattern") executorOpts := &protocols.ExecutorOptions{ Variables: templateVars, Constants: map[string]interface{}{}, Options: &types.Options{}, } err := rule.Compile(nil, executorOpts) require.NoError(t, err, "could not compile rule") result := rule.matchKeyOrValue("test_key", "test_pattern") require.True(t, result, "should match evaluated variable in values regex") result = rule.matchKeyOrValue("test_key", "other_value") require.False(t, result, "should not match non-matching value") }) // complex vars w/ consts and runtime vars t.Run("complex-variables", func(t *testing.T) { rule := &Rule{ Keys: []string{"{{template_var}}", "{{const_key}}", "{{runtime_key}}"}, Part: "query", } templateVars := variables.Variable{ InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1), } templateVars.Set("template_var", "template_value") constants := map[string]interface{}{ "const_key": "const_value", } options := &types.Options{} runtimeVars := goflags.RuntimeMap{} _ = runtimeVars.Set("runtime_key=runtime_value") options.Vars = runtimeVars executorOpts := &protocols.ExecutorOptions{ Variables: templateVars, Constants: constants, Options: options, } err := rule.Compile(nil, executorOpts) require.NoError(t, err, "could not compile rule") result := rule.matchKeyOrValue("template_value", "test") require.True(t, result, "should match template variable") result = rule.matchKeyOrValue("const_value", "test") require.True(t, result, "should match constant") result = rule.matchKeyOrValue("runtime_value", "test") require.True(t, result, "should match runtime variable") result = rule.matchKeyOrValue("{{template_var}}", "test") require.False(t, result, "should not match unevaluated template variable") }) t.Run("invalid-variables", func(t *testing.T) { rule := &Rule{ Keys: []string{"{{nonexistent_var}}"}, Part: "query", } executorOpts := &protocols.ExecutorOptions{ Variables: variables.Variable{ InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(0), }, Constants: map[string]interface{}{}, Options: &types.Options{}, } err := rule.Compile(nil, executorOpts) if err != nil { require.Contains(t, err.Error(), "unresolved", "error should mention unresolved variables") } else { result := rule.matchKeyOrValue("some_key", "some_value") require.False(t, result, "should not match when variables are unresolved") } }) t.Run("evaluateVars-function", func(t *testing.T) { rule := &Rule{} templateVars := variables.Variable{ InsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1), } templateVars.Set("test_var", "test_value") constants := map[string]interface{}{ "const_var": "const_value", } options := &types.Options{} runtimeVars := goflags.RuntimeMap{} _ = runtimeVars.Set("runtime_var=runtime_value") options.Vars = runtimeVars executorOpts := &protocols.ExecutorOptions{ Variables: templateVars, Constants: constants, Options: options, } rule.options = executorOpts // Test simple var substitution result, err := rule.evaluateVars("{{test_var}}") require.NoError(t, err, "should evaluate template variable") require.Equal(t, "test_value", result, "should return evaluated value") // Test constant substitution result, err = rule.evaluateVars("{{const_var}}") require.NoError(t, err, "should evaluate constant") require.Equal(t, "const_value", result, "should return constant value") // Test runtime var substitution result, err = rule.evaluateVars("{{runtime_var}}") require.NoError(t, err, "should evaluate runtime variable") require.Equal(t, "runtime_value", result, "should return runtime value") // Test mixed content result, err = rule.evaluateVars("prefix-{{test_var}}-suffix") require.NoError(t, err, "should evaluate mixed content") require.Equal(t, "prefix-test_value-suffix", result, "should return mixed evaluated content") // Test unresolved var - should either fail during evaluation or return original string result2, err := rule.evaluateVars("{{nonexistent}}") if err != nil { require.Contains(t, err.Error(), "unresolved", "should fail for unresolved variable") } else { // If no error, it should return the original unresolved variable require.Equal(t, "{{nonexistent}}", result2, "should return original string for unresolved variable") } }) } ================================================ FILE: pkg/fuzz/parts.go ================================================ package fuzz import ( "io" "strconv" "strings" "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/component" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/retryablehttp-go" sliceutil "github.com/projectdiscovery/utils/slice" ) // executePartRule executes part rules based on type func (rule *Rule) executePartRule(input *ExecuteRuleInput, payload ValueOrKeyValue, component component.Component) error { return rule.executePartComponent(input, payload, component) } // checkRuleApplicableOnComponent checks if a rule is applicable on given component func (rule *Rule) checkRuleApplicableOnComponent(component component.Component) bool { if rule.Part != component.Name() && !sliceutil.Contains(rule.Parts, component.Name()) && rule.partType != requestPartType { return false } foundAny := false _ = component.Iterate(func(key string, value interface{}) error { if rule.matchKeyOrValue(key, types.ToString(value)) { foundAny = true return io.EOF } return nil }) return foundAny } // executePartComponent executes this rule on a given component and payload func (rule *Rule) executePartComponent(input *ExecuteRuleInput, payload ValueOrKeyValue, ruleComponent component.Component) error { // Note: component needs to be cloned because they contain values copied by reference if payload.IsKV() { // for kv fuzzing return rule.executePartComponentOnKV(input, payload, ruleComponent.Clone()) } else { // for value only fuzzing return rule.executePartComponentOnValues(input, payload.Value, payload.OriginalPayload, ruleComponent.Clone()) } } // executePartComponentOnValues executes this rule on a given component and payload // this supports both single and multiple [ruleType] modes // i.e if component has multiple values, they can be replaced once or all depending on mode func (rule *Rule) executePartComponentOnValues(input *ExecuteRuleInput, payloadStr, originalPayload string, ruleComponent component.Component) error { finalErr := ruleComponent.Iterate(func(key string, value interface{}) error { valueStr := types.ToString(value) if !rule.matchKeyOrValue(key, valueStr) { // ignore non-matching keys return nil } var evaluated, originalEvaluated string evaluated, input.InteractURLs = rule.executeEvaluate(input, key, valueStr, payloadStr, input.InteractURLs) if input.ApplyPayloadInitialTransformation != nil { evaluated = input.ApplyPayloadInitialTransformation(evaluated, input.AnalyzerParams) originalEvaluated, _ = rule.executeEvaluate(input, key, valueStr, originalPayload, input.InteractURLs) } if err := ruleComponent.SetValue(key, evaluated); err != nil { // gologger.Warning().Msgf("could not set value due to format restriction original(%s, %s[%T]) , new(%s,%s[%T])", key, valueStr, value, key, evaluated, evaluated) return nil } if rule.modeType == singleModeType { req, err := ruleComponent.Rebuild() if err != nil { return err } if qerr := rule.execWithInput(input, req, input.InteractURLs, ruleComponent, key, valueStr, originalEvaluated, valueStr, key, evaluated); qerr != nil { return qerr } // fmt.Printf("executed with value: %s\n", evaluated) err = ruleComponent.SetValue(key, valueStr) // change back to previous value for temp if err != nil { return err } } return nil }) if finalErr != nil { return finalErr } // We do not support analyzers with // multiple payload mode. if rule.modeType == multipleModeType { req, err := ruleComponent.Rebuild() if err != nil { return err } if qerr := rule.execWithInput(input, req, input.InteractURLs, ruleComponent, "", "", "", "", "", ""); qerr != nil { err = qerr return err } } return nil } // executePartComponentOnKV executes this rule on a given component and payload // currently only supports single mode func (rule *Rule) executePartComponentOnKV(input *ExecuteRuleInput, payload ValueOrKeyValue, ruleComponent component.Component) error { var origKey string var origValue interface{} // when we have a key-value pair, iterate over only 1 value of the component // multiple values (aka multiple mode) not supported for this yet _ = ruleComponent.Iterate(func(key string, value interface{}) error { if key == payload.Key { origKey = key origValue = value } return nil }) // iterate over given kv instead of component ones return func(key, value string) error { var evaluated string evaluated, input.InteractURLs = rule.executeEvaluate(input, key, "", value, input.InteractURLs) if err := ruleComponent.SetValue(key, evaluated); err != nil { return err } if rule.modeType == singleModeType { req, err := ruleComponent.Rebuild() if err != nil { return err } if qerr := rule.execWithInput(input, req, input.InteractURLs, ruleComponent, key, value, "", "", "", ""); qerr != nil { return qerr } // after building change back to original value to avoid repeating it in further requests if origKey != "" { err = ruleComponent.SetValue(origKey, types.ToString(origValue)) // change back to previous value for temp if err != nil { return err } } else { _ = ruleComponent.Delete(key) // change back to previous value for temp } } return nil }(payload.Key, payload.Value) } // execWithInput executes a rule with input via callback func (rule *Rule) execWithInput(input *ExecuteRuleInput, httpReq *retryablehttp.Request, interactURLs []string, component component.Component, parameter, parameterValue, originalPayload, originalValue, key, value string) error { // If the parameter is a number, replace it with the parameter value // or if the parameter is empty and the parameter value is not empty // replace it with the parameter value actualParameter := parameter if _, err := strconv.Atoi(parameter); err == nil || (parameter == "" && parameterValue != "") { actualParameter = parameterValue } // If the parameter is frequent, skip it if the option is enabled if rule.options.FuzzParamsFrequency != nil { if rule.options.FuzzParamsFrequency.IsParameterFrequent( actualParameter, httpReq.String(), rule.options.TemplateID, ) { return nil } } request := GeneratedRequest{ Request: httpReq, InteractURLs: interactURLs, DynamicValues: input.Values, Component: component, Parameter: actualParameter, Key: key, Value: value, OriginalValue: originalValue, OriginalPayload: originalPayload, } if !input.Callback(request) { return types.ErrNoMoreRequests } return nil } // executeEvaluate executes evaluation of payload on a key and value and // returns completed values to be replaced and processed // for fuzzing. func (rule *Rule) executeEvaluate(input *ExecuteRuleInput, _, value, payload string, interactshURLs []string) (string, []string) { // TODO: Handle errors values := generators.MergeMaps(rule.options.Variables.GetAll(), map[string]interface{}{ "value": value, }, rule.options.Options.Vars.AsMap(), input.Values) firstpass, _ := expressions.Evaluate(payload, values) interactData, interactshURLs := rule.options.Interactsh.Replace(firstpass, interactshURLs) evaluated, _ := expressions.Evaluate(interactData, values) replaced := rule.executeRuleTypes(input, value, evaluated) return replaced, interactshURLs } // executeRuleTypes executes replacement for a key and value // ex: prefix, postfix, infix, replace , replace-regex func (rule *Rule) executeRuleTypes(_ *ExecuteRuleInput, value, replacement string) string { var builder strings.Builder if rule.ruleType == prefixRuleType || rule.ruleType == postfixRuleType { builder.Grow(len(value) + len(replacement)) } var returnValue string switch rule.ruleType { case prefixRuleType: builder.WriteString(replacement) builder.WriteString(value) returnValue = builder.String() case postfixRuleType: builder.WriteString(value) builder.WriteString(replacement) returnValue = builder.String() case infixRuleType: if len(value) <= 1 { builder.WriteString(value) builder.WriteString(replacement) returnValue = builder.String() } else { middleIndex := len(value) / 2 builder.WriteString(value[:middleIndex]) builder.WriteString(replacement) builder.WriteString(value[middleIndex:]) returnValue = builder.String() } case replaceRuleType: returnValue = replacement case replaceRegexRuleType: returnValue = rule.replaceRegex.ReplaceAllString(value, replacement) } return returnValue } ================================================ FILE: pkg/fuzz/parts_frequency_test.go ================================================ package fuzz import ( "net/http" "testing" "github.com/projectdiscovery/nuclei/v3/pkg/fuzz/frequency" "github.com/projectdiscovery/nuclei/v3/pkg/protocols" retryablehttp "github.com/projectdiscovery/retryablehttp-go" "github.com/stretchr/testify/require" ) // TestExecWithInputDoesNotUseNumericParameterIndexForFrequency verifies frequency // checks do not key on numeric path segment indexes. func TestExecWithInputDoesNotUseNumericParameterIndexForFrequency(t *testing.T) { tracker := frequency.New(64, 1) defer tracker.Close() const target = "https://example.com/users/55" const templateID = "tmpl-frequency-check" req, err := retryablehttp.NewRequest(http.MethodGet, target, nil) require.NoError(t, err) tracker.MarkParameter("2", req.String(), templateID) called := false rule := &Rule{ options: &protocols.ExecutorOptions{ TemplateID: templateID, FuzzParamsFrequency: tracker, }, } input := &ExecuteRuleInput{ Callback: func(GeneratedRequest) bool { called = true return true }, } err = rule.execWithInput(input, req, nil, nil, "2", "55", "", "", "", "") require.NoError(t, err) require.True(t, called, "numeric path index should not be used as frequency key") } // TestExecWithInputSkipsWhenActualParameterIsFrequent verifies requests are // skipped when the normalized parameter value is marked frequent. func TestExecWithInputSkipsWhenActualParameterIsFrequent(t *testing.T) { tracker := frequency.New(64, 1) defer tracker.Close() const target = "https://example.com/users/55" const templateID = "tmpl-frequency-check" req, err := retryablehttp.NewRequest(http.MethodGet, target, nil) require.NoError(t, err) tracker.MarkParameter("55", req.String(), templateID) called := false rule := &Rule{ options: &protocols.ExecutorOptions{ TemplateID: templateID, FuzzParamsFrequency: tracker, }, } input := &ExecuteRuleInput{ Callback: func(GeneratedRequest) bool { called = true return true }, } err = rule.execWithInput(input, req, nil, nil, "2", "55", "", "", "", "") require.NoError(t, err) require.False(t, called, "frequent actual parameter should be skipped") } ================================================ FILE: pkg/fuzz/parts_test.go ================================================ // TODO: Write tests package fuzz ================================================ FILE: pkg/fuzz/stats/db.go ================================================ package stats import ( _ "embed" _ "github.com/mattn/go-sqlite3" ) type StatsDatabase interface { Close() InsertComponent(event ComponentEvent) error InsertMatchedRecord(event FuzzingEvent) error InsertError(event ErrorEvent) error } ================================================ FILE: pkg/fuzz/stats/db_test.go ================================================ package stats import ( "testing" "github.com/stretchr/testify/require" ) func Test_NewStatsDatabase(t *testing.T) { db, err := NewSimpleStats() require.NoError(t, err) err = db.InsertMatchedRecord(FuzzingEvent{ URL: "http://localhost:8080/login", TemplateID: "apache-struts2-001", ComponentType: "path", ComponentName: "/login", PayloadSent: "/login'\"><", StatusCode: 401, }) require.NoError(t, err) //os.Remove("test.stats.db") } ================================================ FILE: pkg/fuzz/stats/simple.go ================================================ package stats import ( "fmt" "net/http" "net/url" "strings" "sync" "sync/atomic" ) type simpleStats struct { totalComponentsTested atomic.Int64 totalEndpointsTested atomic.Int64 totalFuzzedRequests atomic.Int64 totalMatchedResults atomic.Int64 totalTemplatesTested atomic.Int64 totalErroredRequests atomic.Int64 statusCodes sync.Map severityCounts sync.Map componentsUniqueMap sync.Map endpointsUniqueMap sync.Map templatesUniqueMap sync.Map errorGroupedStats sync.Map } func NewSimpleStats() (*simpleStats, error) { return &simpleStats{ totalComponentsTested: atomic.Int64{}, totalEndpointsTested: atomic.Int64{}, totalMatchedResults: atomic.Int64{}, totalFuzzedRequests: atomic.Int64{}, totalTemplatesTested: atomic.Int64{}, totalErroredRequests: atomic.Int64{}, statusCodes: sync.Map{}, severityCounts: sync.Map{}, componentsUniqueMap: sync.Map{}, endpointsUniqueMap: sync.Map{}, templatesUniqueMap: sync.Map{}, errorGroupedStats: sync.Map{}, }, nil } func (s *simpleStats) Close() {} func (s *simpleStats) InsertComponent(event ComponentEvent) error { componentKey := fmt.Sprintf("%s_%s", event.ComponentName, event.ComponentType) if _, ok := s.componentsUniqueMap.Load(componentKey); !ok { s.componentsUniqueMap.Store(componentKey, true) s.totalComponentsTested.Add(1) } parsedURL, err := url.Parse(event.URL) if err != nil { return err } endpointsKey := fmt.Sprintf("%s_%s", event.siteName, parsedURL.Path) if _, ok := s.endpointsUniqueMap.Load(endpointsKey); !ok { s.endpointsUniqueMap.Store(endpointsKey, true) s.totalEndpointsTested.Add(1) } return nil } func (s *simpleStats) InsertMatchedRecord(event FuzzingEvent) error { s.totalFuzzedRequests.Add(1) s.incrementStatusCode(event.StatusCode) if event.Matched { s.totalMatchedResults.Add(1) s.incrementSeverityCount(event.Severity) } if _, ok := s.templatesUniqueMap.Load(event.TemplateID); !ok { s.templatesUniqueMap.Store(event.TemplateID, true) s.totalTemplatesTested.Add(1) } return nil } func (s *simpleStats) InsertError(event ErrorEvent) error { s.totalErroredRequests.Add(1) value, _ := s.errorGroupedStats.LoadOrStore(event.Error, &atomic.Int64{}) if counter, ok := value.(*atomic.Int64); ok { counter.Add(1) } return nil } type SimpleStatsResponse struct { TotalMatchedResults int64 TotalComponentsTested int64 TotalEndpointsTested int64 TotalFuzzedRequests int64 TotalTemplatesTested int64 TotalErroredRequests int64 StatusCodes map[string]int64 SeverityCounts map[string]int64 ErrorGroupedStats map[string]int64 } func (s *simpleStats) GetStatistics() SimpleStatsResponse { statusStats := make(map[string]int64) s.statusCodes.Range(func(key, value interface{}) bool { if count, ok := value.(*atomic.Int64); ok { statusStats[formatStatusCode(key.(int))] = count.Load() } return true }) severityStats := make(map[string]int64) s.severityCounts.Range(func(key, value interface{}) bool { if count, ok := value.(*atomic.Int64); ok { severityStats[key.(string)] = count.Load() } return true }) errorStats := make(map[string]int64) s.errorGroupedStats.Range(func(key, value interface{}) bool { if count, ok := value.(*atomic.Int64); ok { errorStats[key.(string)] = count.Load() } return true }) return SimpleStatsResponse{ TotalMatchedResults: s.totalMatchedResults.Load(), StatusCodes: statusStats, SeverityCounts: severityStats, TotalComponentsTested: s.totalComponentsTested.Load(), TotalEndpointsTested: s.totalEndpointsTested.Load(), TotalFuzzedRequests: s.totalFuzzedRequests.Load(), TotalTemplatesTested: s.totalTemplatesTested.Load(), TotalErroredRequests: s.totalErroredRequests.Load(), ErrorGroupedStats: errorStats, } } func (s *simpleStats) incrementStatusCode(statusCode int) { value, _ := s.statusCodes.LoadOrStore(statusCode, &atomic.Int64{}) if counter, ok := value.(*atomic.Int64); ok { counter.Add(1) } } func (s *simpleStats) incrementSeverityCount(severity string) { value, _ := s.severityCounts.LoadOrStore(severity, &atomic.Int64{}) if counter, ok := value.(*atomic.Int64); ok { counter.Add(1) } } func formatStatusCode(code int) string { escapedText := strings.ToTitle(strings.ReplaceAll(http.StatusText(code), " ", "_")) formatted := fmt.Sprintf("%d_%s", code, escapedText) return formatted } ================================================ FILE: pkg/fuzz/stats/stats.go ================================================ // Package stats implements a statistics recording module for // nuclei fuzzing. package stats import ( "fmt" "log" "net/url" "github.com/pkg/errors" ) // Tracker is a stats tracker module for fuzzing server type Tracker struct { database *simpleStats } // NewTracker creates a new tracker instance func NewTracker() (*Tracker, error) { db, err := NewSimpleStats() if err != nil { return nil, errors.Wrap(err, "could not create new tracker") } tracker := &Tracker{ database: db, } return tracker, nil } func (t *Tracker) GetStats() SimpleStatsResponse { return t.database.GetStatistics() } // Close closes the tracker func (t *Tracker) Close() { t.database.Close() } // FuzzingEvent is a fuzzing event type FuzzingEvent struct { URL string ComponentType string ComponentName string TemplateID string PayloadSent string StatusCode int Matched bool RawRequest string RawResponse string Severity string siteName string } func (t *Tracker) RecordResultEvent(event FuzzingEvent) { event.siteName = getCorrectSiteName(event.URL) if err := t.database.InsertMatchedRecord(event); err != nil { log.Printf("could not insert matched record: %s", err) } } type ComponentEvent struct { URL string ComponentType string ComponentName string siteName string } func (t *Tracker) RecordComponentEvent(event ComponentEvent) { event.siteName = getCorrectSiteName(event.URL) if err := t.database.InsertComponent(event); err != nil { log.Printf("could not insert component record: %s", err) } } type ErrorEvent struct { TemplateID string URL string Error string } func (t *Tracker) RecordErrorEvent(event ErrorEvent) { if err := t.database.InsertError(event); err != nil { log.Printf("could not insert error record: %s", err) } } func getCorrectSiteName(originalURL string) string { parsed, err := url.Parse(originalURL) if err != nil { return "" } // Site is the host:port combo siteName := parsed.Host if parsed.Port() == "" { switch parsed.Scheme { case "https": siteName = fmt.Sprintf("%s:443", siteName) case "http": siteName = fmt.Sprintf("%s:80", siteName) } } return siteName } ================================================ FILE: pkg/fuzz/type.go ================================================ package fuzz import ( "fmt" "github.com/invopop/jsonschema" "github.com/projectdiscovery/nuclei/v3/pkg/utils/json" mapsutil "github.com/projectdiscovery/utils/maps" "gopkg.in/yaml.v2" ) var ( _ json.JSONCodec = &SliceOrMapSlice{} _ yaml.Marshaler = &SliceOrMapSlice{} _ yaml.Unmarshaler = &SliceOrMapSlice{} ) type ValueOrKeyValue struct { Key string Value string OriginalPayload string } func (v *ValueOrKeyValue) IsKV() bool { return v.Key != "" } type SliceOrMapSlice struct { Value []string KV *mapsutil.OrderedMap[string, string] } func (v SliceOrMapSlice) JSONSchemaExtend(schema *jsonschema.Schema) *jsonschema.Schema { schema = &jsonschema.Schema{ Title: schema.Title, Description: schema.Description, Type: "array", Items: &jsonschema.Schema{ OneOf: []*jsonschema.Schema{ { Type: "string", }, { Type: "object", }, }, }, } return schema } func (v SliceOrMapSlice) JSONSchema() *jsonschema.Schema { gotType := &jsonschema.Schema{ Title: "Payloads of Fuzz Rule", Description: "Payloads to perform fuzzing substitutions with.", Type: "array", Items: &jsonschema.Schema{ OneOf: []*jsonschema.Schema{ { Type: "string", }, { Type: "object", }, }, }, } return gotType } // UnmarshalJSON implements json.Unmarshaler interface. func (v *SliceOrMapSlice) UnmarshalJSON(data []byte) error { // try to unmashal as a string and fallback to map if err := json.Unmarshal(data, &v.Value); err == nil { return nil } err := json.Unmarshal(data, &v.KV) if err != nil { return fmt.Errorf("object can be a key:value or a string") } return nil } // MarshalJSON implements json.Marshaler interface. func (v SliceOrMapSlice) MarshalJSON() ([]byte, error) { if v.KV != nil { return json.Marshal(v.KV) } return json.Marshal(v.Value) } // UnmarshalYAML implements yaml.Unmarshaler interface. func (v *SliceOrMapSlice) UnmarshalYAML(callback func(interface{}) error) error { // try to unmarshal it as a string and fallback to map if err := callback(&v.Value); err == nil { return nil } // try with a mapslice var node yaml.MapSlice if err := callback(&node); err == nil { tmpx := mapsutil.NewOrderedMap[string, string]() // preserve order for _, v := range node { tmpx.Set(v.Key.(string), v.Value.(string)) } v.KV = &tmpx return nil } return fmt.Errorf("object can be a key:value or a string") } // MarshalYAML implements yaml.Marshaler interface. func (v SliceOrMapSlice) MarshalYAML() (any, error) { if v.KV != nil { return v.KV, nil } return v.Value, nil } ================================================ FILE: pkg/input/README.md ================================================ ## input input package contains and provides loading, parsing , validating and normalizing of input data ## [transform](./transform.go) Transform package transforms or normalizes the input data before it is sent to protocol executer this step mainly involves changes like adding default ports (if missing) , validating if input is file or directory or url and adjusting the input accordingly etc. ## Provider Provider package contains the interface that every input format should implement for providing that input format to nuclei. Currently Nuclei Supports three input providers: 1. SimpleInputProvider = A No-Op provider that takes a list of urls and implements the provider interface. 2. HttpInputProvider = A provider that supports loading and parsing input formats that contain complete Http Data like Entire Request, Response etc. Supported formats include Burp,openapi,swagger,postman,proxify etc. 3. ListInputProvider = Legacy/Default Provider that handles all list type inputs like urls,domains,ips,cidrs,files etc. ```go func NewInputProvider(opts InputOptions) (InputProvider, error) ``` This function returns a InputProvider based by appropriately selecting input provider based on the input format (i.e. either list or http) and returns the provider that can handle that input format. ================================================ FILE: pkg/input/formats/README.md ================================================ # formats Formats implements support for passing a number of request source as input providers to nuclei to be tested for fuzzing related issues. Currently the following formats are implemented - - Burp Suite XML Request/Response file - Proxify JSONL output file - OpenAPI Specification file - Postman Collection file - Swagger Specification file Each implementation implements either the entire or a subset of the features of the specifications. These can be increased further to add support as new things or requirements are identified. Refer to the specific code for each implementation to understand supported features of the specs. ## OpenAPI Specification File It is designed to generate HTTP requests based on an OpenAPI 3.0 Schema. Here is how these schema components are processed: ### Servers The module supports multiple server URLs defined in the `Servers` section of the OpenAPI document. It will send requests to all the server URLs defined in the schema. ### Paths and Operations The module supports all HTTP methods defined under each path in the `Paths` section. For each operation on a path, HTTP requests are generated and sent to the defined server URL. If the operation cannot generate a valid request, a warning will be logged. ### Parameters The module recognizes parameters defined in the `query`, `header`, `path`, and `cookie` categories. When generating requests, if the `requiredOnly` flag is true, only the required parameters are included. Otherwise, all parameters, regardless of their required status, are used. The `generateExampleFromSchema` function is used to generate suitable example data for each parameter from their respective schema definitions. ### RequestBody The module also comprehends request bodies and supports various media types defined in the `Content` field. Currently, the following content-types are supported: - `application/json`: The module creates application-specific JSON from the defined example schema. - `application/xml`: The example schema is converted into xml format using `mxj` library. - `application/x-www-form-urlencoded`: The example schema is converted into URL-encoded form data. - `multipart/form-data`: The module supports multipart form-data and differentiates between fields and files using the `binary` format under the property schema. - `text/plain`: Converts the example schema into string format and send as plain text. For unsupported media types, no appropriate content type is found for the body. After setting up the body of the request, the module dumps the request and sends it to the defined server URL. ### Example Request Generation This module converts each operation into one or more example HTTP requests. Each request is dumped into a string format, accompanied by its method, URL, headers, and body. These are send as a callback for further processing. _Please note: This document does not cover other features of OpenAPI specification like responses, security schemes, links, callbacks, etc. as these are not currently handled by the module._ ## Postman Collection file This module parser Postman Collection JSON files. ### 1. Request Parsing: Able to parse requests detailed in the Postman package. The parser is capable of interpreting the HTTP method, URL, and Body of each request present in the collection. ### 2. Header Parsing: All HTTP headers set in the collection's request are parsed and set in the request. ### 3. Auth Type Parsing: Able to parse and set the `Authentication` options provided in the postman collection in the request headers. Supported types of authentication: 1. **API Key**: In header 2. **Basic**: Setting basic auth through username, password. 3. **Bearer Token**: Involves setting bearer auth using tokens. 4. **No Auth**: No authentication is set. Note: Not all parts of the Postman Collection specification are supported. This parser does not currently support Postman variables or collection level variables and items. It also does not support more authentication types than detailed above. ### Limitations: * Does not support Postman variables * Does not support Collection level variables and items * Limited Authentication types supported ## Swagger Specification file Swagger specification file is converted from OpenAPI 2.0 format to OpenAPI 3.0 format. After this, the OpenAPI parser from above is used. ## Burp XML / Proxify JSONL These modules are generic and parse raw requests from these respective tools. ================================================ FILE: pkg/input/formats/burp/burp.go ================================================ package burp import ( "encoding/base64" "io" "strings" "github.com/pkg/errors" "github.com/projectdiscovery/nuclei/v3/pkg/input/formats" "github.com/projectdiscovery/nuclei/v3/pkg/input/types" "github.com/projectdiscovery/utils/conversion" burpxml "github.com/projectdiscovery/utils/parsers/burp/xml" ) // BurpFormat is a Burp XML File parser type BurpFormat struct { opts formats.InputFormatOptions } // New creates a new Burp XML File parser func New() *BurpFormat { return &BurpFormat{} } var _ formats.Format = &BurpFormat{} // Name returns the name of the format func (j *BurpFormat) Name() string { return "burp" } func (j *BurpFormat) SetOptions(options formats.InputFormatOptions) { j.opts = options } // Parse parses the input and calls the provided callback // function for each RawRequest it discovers. func (j *BurpFormat) Parse(input io.Reader, resultsCb formats.ParseReqRespCallback, filePath string) error { items, err := burpxml.ParseXML(input, burpxml.XMLParseOptions{DecodeBase64: true}) if err != nil { return errors.Wrap(err, "could not decode burp xml schema") } for _, item := range items.Items { binx, err := base64.StdEncoding.DecodeString(item.Request.Raw) if err != nil { return errors.Wrap(err, "could not decode base64") } if strings.TrimSpace(conversion.String(binx)) == "" { continue } rawRequest, err := types.ParseRawRequestWithURL(conversion.String(binx), item.URL) if err != nil { return errors.Wrap(err, "could not parse raw request") } resultsCb(rawRequest) } return nil } ================================================ FILE: pkg/input/formats/burp/burp_test.go ================================================ package burp import ( "os" "testing" "github.com/projectdiscovery/nuclei/v3/pkg/input/types" "github.com/stretchr/testify/require" ) func TestBurpParse(t *testing.T) { format := New() proxifyInputFile := "../testdata/burp.xml" var gotMethodsToURLs []string file, err := os.Open(proxifyInputFile) require.Nilf(t, err, "error opening proxify input file: %v", err) defer func() { _ = file.Close() }() err = format.Parse(file, func(request *types.RequestResponse) bool { gotMethodsToURLs = append(gotMethodsToURLs, request.URL.String()) return false }, proxifyInputFile) if err != nil { t.Fatal(err) } if len(gotMethodsToURLs) != 2 { t.Fatalf("invalid number of methods: %d", len(gotMethodsToURLs)) } var expectedURLs = []string{ "http://localhost:8087/scans", "http://google.com/", } require.ElementsMatch(t, expectedURLs, gotMethodsToURLs, "could not get burp urls") } ================================================ FILE: pkg/input/formats/formats.go ================================================ package formats import ( "errors" "io" "os" "strings" "github.com/projectdiscovery/nuclei/v3/pkg/input/types" "github.com/projectdiscovery/retryablehttp-go" fileutil "github.com/projectdiscovery/utils/file" "gopkg.in/yaml.v3" ) // ParseReqRespCallback is a callback function for discovered raw requests type ParseReqRespCallback func(rr *types.RequestResponse) bool // InputFormatOptions contains options for the input // this can be variables that can be passed or // overrides or some other options type InputFormatOptions struct { // Variables is list of variables that can be used // while generating requests in given format Variables map[string]interface{} // SkipFormatValidation is used to skip format validation // while debugging or testing if format is invalid then // requests are skipped instead of creating invalid requests SkipFormatValidation bool // RequiredOnly only uses required fields when generating requests // instead of all fields RequiredOnly bool // VarsTextTemplating uses Variables and inject it into the input // this is used for text templating of variables based on carvel ytt // Only available for Yaml formats VarsTextTemplating bool // VarsFilePaths is the path to the file containing variables VarsFilePaths []string } // Format is an interface implemented by all input formats type Format interface { // Name returns the name of the format Name() string // Parse parses the input and calls the provided callback // function for each RawRequest it discovers. Parse(input io.Reader, resultsCb ParseReqRespCallback, filePath string) error // SetOptions sets the options for the input format SetOptions(options InputFormatOptions) } // SpecDownloader is an interface for downloading API specifications from URLs type SpecDownloader interface { // Download downloads the spec from the given URL and saves it to tmpDir // Returns the path to the downloaded file // httpClient is a retryablehttp.Client instance (can be nil for fallback) Download(url, tmpDir string, httpClient *retryablehttp.Client) (string, error) // SupportedExtensions returns the list of supported file extensions SupportedExtensions() []string } var ( DefaultVarDumpFileName = "required_openapi_params.yaml" ErrNoVarsDumpFile = errors.New("no required params file found") ) // == OpenAPIParamsCfgFile == // this file is meant to be used in CLI mode // to be more interactive and user-friendly when // running nuclei with openapi format // OpenAPIParamsCfgFile is the structure of the required vars dump file type OpenAPIParamsCfgFile struct { Var []string `yaml:"var"` OptionalVars []string `yaml:"-"` // this will be written to the file as comments } // ReadOpenAPIVarDumpFile reads the required vars dump file func ReadOpenAPIVarDumpFile() (*OpenAPIParamsCfgFile, error) { var vars OpenAPIParamsCfgFile if !fileutil.FileExists(DefaultVarDumpFileName) { return nil, ErrNoVarsDumpFile } bin, err := os.ReadFile(DefaultVarDumpFileName) if err != nil { return nil, err } err = yaml.Unmarshal(bin, &vars) if err != nil { return nil, err } filtered := []string{} for _, v := range vars.Var { v = strings.TrimSpace(v) if !strings.HasSuffix(v, "=") { filtered = append(filtered, v) } } vars.Var = filtered return &vars, nil } // WriteOpenAPIVarDumpFile writes the required vars dump file func WriteOpenAPIVarDumpFile(vars *OpenAPIParamsCfgFile) error { f, err := os.OpenFile(DefaultVarDumpFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { return err } defer func() { _ = f.Close() }() bin, err := yaml.Marshal(vars) if err != nil { return err } _, _ = f.Write(bin) if len(vars.OptionalVars) > 0 { _, _ = f.WriteString("\n # Optional parameters\n") for _, v := range vars.OptionalVars { _, _ = f.WriteString(" # - " + v + "=\n") } } return f.Sync() } ================================================ FILE: pkg/input/formats/json/json.go ================================================ package json import ( "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" "github.com/projectdiscovery/nuclei/v3/pkg/utils/json" ) // JSONFormat is a JSON format parser for nuclei // input HTTP requests type JSONFormat struct { opts formats.InputFormatOptions } // New creates a new JSON format parser func New() *JSONFormat { return &JSONFormat{} } var _ formats.Format = &JSONFormat{} // 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"` Endpoint string `json:"endpoint"` } `json:"request"` } // Name returns the name of the format func (j *JSONFormat) Name() string { return "jsonl" } func (j *JSONFormat) SetOptions(options formats.InputFormatOptions) { j.opts = options } // Parse parses the input and calls the provided callback // function for each RawRequest it discovers. func (j *JSONFormat) Parse(input io.Reader, resultsCb formats.ParseReqRespCallback, filePath string) error { decoder := json.NewDecoder(input) for { var request proxifyRequest err := decoder.Decode(&request) if err == io.EOF { break } if err != nil { return errors.Wrap(err, "could not decode json file") } if request.URL == "" && request.Request.Endpoint != "" { request.URL = request.Request.Endpoint } rawRequest, err := types.ParseRawRequestWithURL(request.Request.Raw, request.URL) if err != nil { gologger.Warning().Msgf("jsonl: Could not parse raw request %s: %s\n", request.URL, err) continue } resultsCb(rawRequest) } return nil } ================================================ FILE: pkg/input/formats/json/json_test.go ================================================ package json import ( "os" "testing" "github.com/projectdiscovery/nuclei/v3/pkg/input/types" "github.com/stretchr/testify/require" ) var expectedURLs = []string{ "https://ginandjuice.shop/", "https://ginandjuice.shop/catalog/product?productId=1", "https://ginandjuice.shop/resources/js/stockCheck.js", "https://ginandjuice.shop/resources/js/xmlStockCheckPayload.js", "https://ginandjuice.shop/resources/js/xmlStockCheckPayload.js", "https://ginandjuice.shop/resources/js/stockCheck.js", "https://ginandjuice.shop/catalog/product/stock", "https://ginandjuice.shop/catalog/cart", "https://ginandjuice.shop/catalog/product?productId=1", "https://ginandjuice.shop/catalog/subscribe", "https://ginandjuice.shop/blog", "https://ginandjuice.shop/blog/?search=dadad&back=%2Fblog%2F", "https://ginandjuice.shop/logger", "https://ginandjuice.shop/blog/", "https://ginandjuice.shop/blog/post?postId=3", "https://ginandjuice.shop/about", "https://ginandjuice.shop/my-account", "https://ginandjuice.shop/login", "https://ginandjuice.shop/login", "https://ginandjuice.shop/login", "https://ginandjuice.shop/my-account", "https://ginandjuice.shop/catalog/cart", "https://ginandjuice.shop/my-account", "https://ginandjuice.shop/logout", "https://ginandjuice.shop/", "https://ginandjuice.shop/catalog", } func TestJSONFormatterParse(t *testing.T) { format := New() proxifyInputFile := "../testdata/ginandjuice.proxify.json" 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) if err != nil { t.Fatal(err) } if len(urls) != len(expectedURLs) { t.Fatalf("invalid number of urls: %d", len(urls)) } require.ElementsMatch(t, urls, expectedURLs) } ================================================ FILE: pkg/input/formats/openapi/downloader.go ================================================ package openapi import ( "encoding/json" "fmt" "io" "net/http" "net/url" "os" "path/filepath" "strings" "time" "github.com/pkg/errors" "github.com/projectdiscovery/nuclei/v3/pkg/input/formats" "github.com/projectdiscovery/retryablehttp-go" ) // OpenAPIDownloader implements the SpecDownloader interface for OpenAPI 3.0 specs type OpenAPIDownloader struct{} // NewDownloader creates a new OpenAPI downloader func NewDownloader() formats.SpecDownloader { return &OpenAPIDownloader{} } // This function downloads an OpenAPI 3.0 spec from the given URL and saves it to tmpDir func (d *OpenAPIDownloader) Download(urlStr, tmpDir string, httpClient *retryablehttp.Client) (string, error) { // Validate URL format, OpenAPI 3.0 specs are typically JSON if !strings.HasSuffix(urlStr, ".json") { return "", fmt.Errorf("URL does not appear to be an OpenAPI JSON spec") } const maxSpecSizeBytes = 10 * 1024 * 1024 // 10MB // Use provided httpClient or create a fallback var client *http.Client if httpClient != nil { client = httpClient.HTTPClient } else { // Fallback to simple client if no httpClient provided client = &http.Client{Timeout: 30 * time.Second} } resp, err := client.Get(urlStr) if err != nil { return "", errors.Wrap(err, "failed to download OpenAPI spec") } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("HTTP %d when downloading OpenAPI spec", resp.StatusCode) } bodyBytes, err := io.ReadAll(io.LimitReader(resp.Body, maxSpecSizeBytes)) if err != nil { return "", errors.Wrap(err, "failed to read response body") } // Validate it's a valid JSON and has OpenAPI structure var spec map[string]interface{} if err := json.Unmarshal(bodyBytes, &spec); err != nil { return "", fmt.Errorf("downloaded content is not valid JSON: %w", err) } // Check if it's an OpenAPI 3.0 spec if openapi, exists := spec["openapi"]; exists { if openapiStr, ok := openapi.(string); ok && strings.HasPrefix(openapiStr, "3.") { // Valid OpenAPI 3.0 spec } else { return "", fmt.Errorf("not a valid OpenAPI 3.0 spec (found version: %v)", openapi) } } else { return "", fmt.Errorf("not an OpenAPI spec (missing 'openapi' field)") } // Extract host from URL for server configuration parsedURL, err := url.Parse(urlStr) if err != nil { return "", errors.Wrap(err, "failed to parse URL") } host := parsedURL.Host scheme := parsedURL.Scheme if scheme == "" { scheme = "https" } // Add servers section if missing or empty servers, exists := spec["servers"] if !exists || servers == nil { spec["servers"] = []map[string]interface{}{{"url": scheme + "://" + host}} } else if serverList, ok := servers.([]interface{}); ok && len(serverList) == 0 { spec["servers"] = []map[string]interface{}{{"url": scheme + "://" + host}} } // Marshal back to JSON modifiedJSON, err := json.Marshal(spec) if err != nil { return "", errors.Wrap(err, "failed to marshal modified spec") } // Create output directory openapiDir := filepath.Join(tmpDir, "openapi") if err := os.MkdirAll(openapiDir, 0755); err != nil { return "", errors.Wrap(err, "failed to create openapi directory") } // Generate filename filename := fmt.Sprintf("openapi-spec-%d.json", time.Now().Unix()) filePath := filepath.Join(openapiDir, filename) // Write file file, err := os.Create(filePath) if err != nil { return "", fmt.Errorf("failed to create file: %w", err) } defer func() { _ = file.Close() }() if _, writeErr := file.Write(modifiedJSON); writeErr != nil { _ = os.Remove(filePath) return "", errors.Wrap(writeErr, "failed to write OpenAPI spec to file") } return filePath, nil } // SupportedExtensions returns the list of supported file extensions for OpenAPI func (d *OpenAPIDownloader) SupportedExtensions() []string { return []string{".json"} } ================================================ FILE: pkg/input/formats/openapi/downloader_test.go ================================================ package openapi import ( "encoding/json" "net/http" "net/http/httptest" "os" "strings" "testing" "time" ) func TestOpenAPIDownloader_SupportedExtensions(t *testing.T) { downloader := &OpenAPIDownloader{} extensions := downloader.SupportedExtensions() expected := []string{".json"} if len(extensions) != len(expected) { t.Errorf("Expected %d extensions, got %d", len(expected), len(extensions)) } for i, ext := range extensions { if ext != expected[i] { t.Errorf("Expected extension %s, got %s", expected[i], ext) } } } func TestOpenAPIDownloader_Download_Success(t *testing.T) { // Create a mock OpenAPI spec mockSpec := map[string]interface{}{ "openapi": "3.0.0", "info": map[string]interface{}{ "title": "Test API", "version": "1.0.0", }, "paths": map[string]interface{}{ "/test": map[string]interface{}{ "get": map[string]interface{}{ "summary": "Test endpoint", }, }, }, } // Create mock server server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(mockSpec); err != nil { http.Error(w, "failed to encode response", http.StatusInternalServerError) } })) defer server.Close() // Create temp directory tmpDir, err := os.MkdirTemp("", "openapi_test") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } defer func() { if err := os.RemoveAll(tmpDir); err != nil { t.Fatalf("Failed to remove temp dir: %v", err) } }() // Test download downloader := &OpenAPIDownloader{} filePath, err := downloader.Download(server.URL+"/openapi.json", tmpDir, nil) if err != nil { t.Fatalf("Download failed: %v", err) } // Verify file exists if !fileExists(filePath) { t.Errorf("Downloaded file does not exist: %s", filePath) } // Verify file content content, err := os.ReadFile(filePath) if err != nil { t.Fatalf("Failed to read downloaded file: %v", err) } var downloadedSpec map[string]interface{} if err := json.Unmarshal(content, &downloadedSpec); err != nil { t.Fatalf("Failed to parse downloaded JSON: %v", err) } // Verify servers field was added servers, exists := downloadedSpec["servers"] if !exists { t.Error("Servers field was not added to the spec") } if serversList, ok := servers.([]interface{}); ok { if len(serversList) == 0 { t.Error("Servers list is empty") } } else { t.Error("Servers field is not a list") } } func TestOpenAPIDownloader_Download_NonJSONURL(t *testing.T) { tmpDir, err := os.MkdirTemp("", "openapi_test") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } defer func() { if err := os.RemoveAll(tmpDir); err != nil { t.Fatalf("Failed to remove temp dir: %v", err) } }() downloader := &OpenAPIDownloader{} _, err = downloader.Download("http://example.com/spec.yaml", tmpDir, nil) if err == nil { t.Error("Expected error for non-JSON URL, but got none") } if !strings.Contains(err.Error(), "URL does not appear to be an OpenAPI JSON spec") { t.Errorf("Unexpected error message: %v", err) } } func TestOpenAPIDownloader_Download_HTTPError(t *testing.T) { // Create mock server that returns 404 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) })) defer server.Close() tmpDir, err := os.MkdirTemp("", "openapi_test") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } defer func() { if err := os.RemoveAll(tmpDir); err != nil { t.Fatalf("Failed to remove temp dir: %v", err) } }() downloader := &OpenAPIDownloader{} _, err = downloader.Download(server.URL+"/openapi.json", tmpDir, nil) if err == nil { t.Error("Expected error for HTTP 404, but got none") } } func TestOpenAPIDownloader_Download_InvalidJSON(t *testing.T) { // Create mock server that returns invalid JSON server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if _, err := w.Write([]byte("invalid json")); err != nil { http.Error(w, "failed to write response", http.StatusInternalServerError) } })) defer server.Close() tmpDir, err := os.MkdirTemp("", "openapi_test") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } defer func() { if err := os.RemoveAll(tmpDir); err != nil { t.Fatalf("Failed to remove temp dir: %v", err) } }() downloader := &OpenAPIDownloader{} _, err = downloader.Download(server.URL+"/openapi.json", tmpDir, nil) if err == nil { t.Error("Expected error for invalid JSON, but got none") } } func TestOpenAPIDownloader_Download_Timeout(t *testing.T) { // Create mock server with delay server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { time.Sleep(35 * time.Second) // Longer than 30 second timeout if err := json.NewEncoder(w).Encode(map[string]interface{}{"test": "data"}); err != nil { http.Error(w, "failed to encode response", http.StatusInternalServerError) } })) defer server.Close() tmpDir, err := os.MkdirTemp("", "openapi_test") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } defer func() { if err := os.RemoveAll(tmpDir); err != nil { t.Fatalf("Failed to remove temp dir: %v", err) } }() downloader := &OpenAPIDownloader{} _, err = downloader.Download(server.URL+"/openapi.json", tmpDir, nil) if err == nil { t.Error("Expected timeout error, but got none") } } func TestOpenAPIDownloader_Download_WithExistingServers(t *testing.T) { // Create a mock OpenAPI spec with existing servers mockSpec := map[string]interface{}{ "openapi": "3.0.0", "info": map[string]interface{}{ "title": "Test API", "version": "1.0.0", }, "servers": []interface{}{ map[string]interface{}{ "url": "https://existing-server.com", }, }, "paths": map[string]interface{}{}, } // Create mock server server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(mockSpec); err != nil { http.Error(w, "failed to encode response", http.StatusInternalServerError) } })) defer server.Close() tmpDir, err := os.MkdirTemp("", "openapi_test") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } defer func() { if err := os.RemoveAll(tmpDir); err != nil { t.Fatalf("Failed to remove temp dir: %v", err) } }() downloader := &OpenAPIDownloader{} filePath, err := downloader.Download(server.URL+"/openapi.json", tmpDir, nil) if err != nil { t.Fatalf("Download failed: %v", err) } // Verify existing servers are preserved content, err := os.ReadFile(filePath) if err != nil { t.Fatalf("Failed to read downloaded file: %v", err) } var downloadedSpec map[string]interface{} if err := json.Unmarshal(content, &downloadedSpec); err != nil { t.Fatalf("Failed to parse downloaded JSON: %v", err) } servers, exists := downloadedSpec["servers"] if !exists { t.Error("Servers field was removed from the spec") } if serversList, ok := servers.([]interface{}); ok { if len(serversList) != 1 { t.Errorf("Expected 1 server, got %d", len(serversList)) } } } // Helper function to check if file exists func fileExists(filename string) bool { _, err := os.Stat(filename) return !os.IsNotExist(err) } ================================================ FILE: pkg/input/formats/openapi/examples.go ================================================ package openapi import ( "fmt" "maps" "slices" "github.com/getkin/kin-openapi/openapi3" "github.com/pkg/errors" ) // From: https://github.com/danielgtaylor/apisprout/blob/master/example.go func getSchemaExample(schema *openapi3.Schema) (interface{}, bool) { if schema.Example != nil { return schema.Example, true } if schema.Default != nil { return schema.Default, true } if len(schema.Enum) > 0 { return schema.Enum[0], true } return nil, false } // stringFormatExample returns an example string based on the given format. // http://json-schema.org/latest/json-schema-validation.html#rfc.section.7.3 func stringFormatExample(format string) string { switch format { case "date": // https://tools.ietf.org/html/rfc3339 return "2018-07-23" case "date-time": // This is the date/time of API Sprout's first commit! :-) return "2018-07-23T22:58:00-07:00" case "time": return "22:58:00-07:00" case "email": return "email@example.com" case "hostname": // https://tools.ietf.org/html/rfc2606#page-2 return "example.com" case "ipv4": // https://tools.ietf.org/html/rfc5737 return "198.51.100.0" case "ipv6": // https://tools.ietf.org/html/rfc3849 return "2001:0db8:85a3:0000:0000:8a2e:0370:7334" case "uri": return "https://tools.ietf.org/html/rfc3986" case "uri-template": // https://tools.ietf.org/html/rfc6570 return "http://example.com/dictionary/{term:1}/{term}" case "json-pointer": // https://tools.ietf.org/html/rfc6901 return "#/components/parameters/term" case "regex": // https://stackoverflow.com/q/3296050/164268 return "/^1?$|^(11+?)\\1+$/" case "uuid": // https://www.ietf.org/rfc/rfc4122.txt return "f81d4fae-7dec-11d0-a765-00a0c91e6bf6" case "password": return "********" case "binary": return "sagefuzzertest" } return "" } // excludeFromMode will exclude a schema if the mode is request and the schema // is read-only func excludeFromMode(schema *openapi3.Schema) bool { if schema == nil { return true } if schema.ReadOnly { return true } return false } // isRequired checks whether a key is actually required. func isRequired(schema *openapi3.Schema, key string) bool { return slices.Contains(schema.Required, key) } type cachedSchema struct { pending bool out interface{} } var ( // ErrRecursive is when a schema is impossible to represent because it infinitely recurses. ErrRecursive = errors.New("Recursive schema") // ErrNoExample is sent when no example was found for an operation. ErrNoExample = errors.New("No example found") ) func openAPIExample(schema *openapi3.Schema, cache map[*openapi3.Schema]*cachedSchema) (out interface{}, err error) { if ex, ok := getSchemaExample(schema); ok { return ex, nil } cached, ok := cache[schema] if !ok { cached = &cachedSchema{ pending: true, } cache[schema] = cached } else if cached.pending { return nil, ErrRecursive } else { return cached.out, nil } defer func() { cached.pending = false cached.out = out }() // Handle combining keywords if len(schema.OneOf) > 0 { var ex interface{} var err error for _, candidate := range schema.OneOf { ex, err = openAPIExample(candidate.Value, cache) if err == nil { break } } return ex, err } if len(schema.AnyOf) > 0 { var ex interface{} var err error for _, candidate := range schema.AnyOf { ex, err = openAPIExample(candidate.Value, cache) if err == nil { break } } return ex, err } if len(schema.AllOf) > 0 { example := map[string]interface{}{} for _, allOf := range schema.AllOf { candidate, err := openAPIExample(allOf.Value, cache) if err != nil { return nil, err } value, ok := candidate.(map[string]interface{}) if !ok { return nil, ErrNoExample } maps.Copy(example, value) } return example, nil } switch { case schema.Type.Is("boolean"): return true, nil case schema.Type.Is("number"), schema.Type.Is("integer"): value := 0.0 if schema.Min != nil && *schema.Min > value { value = *schema.Min if schema.ExclusiveMin { if schema.Max != nil { // Make the value half way. value = (*schema.Min + *schema.Max) / 2.0 } else { value++ } } } if schema.Max != nil && *schema.Max < value { value = *schema.Max if schema.ExclusiveMax { if schema.Min != nil { // Make the value half way. value = (*schema.Min + *schema.Max) / 2.0 } else { value-- } } } if schema.MultipleOf != nil && int(value)%int(*schema.MultipleOf) != 0 { value += float64(int(*schema.MultipleOf) - (int(value) % int(*schema.MultipleOf))) } if schema.Type.Is("integer") { return int(value), nil } return value, nil case schema.Type.Is("string"): if ex := stringFormatExample(schema.Format); ex != "" { return ex, nil } example := "string" for schema.MinLength > uint64(len(example)) { example += example } if schema.MaxLength != nil && *schema.MaxLength < uint64(len(example)) { example = example[:*schema.MaxLength] } return example, nil case schema.Type.Is("array"), schema.Items != nil: example := []interface{}{} if schema.Items != nil && schema.Items.Value != nil { ex, err := openAPIExample(schema.Items.Value, cache) if err != nil { return nil, fmt.Errorf("can't get example for array item: %+v", err) } example = append(example, ex) for uint64(len(example)) < schema.MinItems { example = append(example, ex) } } return example, nil case schema.Type.Is("object"), len(schema.Properties) > 0: example := map[string]interface{}{} for k, v := range schema.Properties { if excludeFromMode(v.Value) { continue } ex, err := openAPIExample(v.Value, cache) if err == ErrRecursive { if isRequired(schema, k) { return nil, fmt.Errorf("can't get example for '%s': %+v", k, err) } } else if err != nil { return nil, fmt.Errorf("can't get example for '%s': %+v", k, err) } else { example[k] = ex } } if schema.AdditionalProperties.Has != nil && schema.AdditionalProperties.Schema != nil { addl := schema.AdditionalProperties.Schema.Value if !excludeFromMode(addl) { ex, err := openAPIExample(addl, cache) if err == ErrRecursive { // We just won't add this if it's recursive. } else if err != nil { return nil, fmt.Errorf("can't get example for additional properties: %+v", err) } else { example["additionalPropertyName"] = ex } } } return example, nil } return nil, ErrNoExample } // generateExampleFromSchema creates an example structure from an OpenAPI 3 schema // object, which is an extended subset of JSON Schema. // // https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#schemaObject func generateExampleFromSchema(schema *openapi3.Schema) (interface{}, error) { return openAPIExample(schema, make(map[*openapi3.Schema]*cachedSchema)) // TODO: Use caching } func generateEmptySchemaValue(contentType string) *openapi3.Schema { schema := &openapi3.Schema{} objectType := &openapi3.Types{"object"} stringType := &openapi3.Types{"string"} switch contentType { case "application/json": schema.Type = objectType schema.Properties = make(map[string]*openapi3.SchemaRef) case "application/xml": schema.Type = stringType schema.Format = "xml" schema.Example = "" case "text/plain": schema.Type = stringType case "application/x-www-form-urlencoded": schema.Type = objectType schema.Properties = make(map[string]*openapi3.SchemaRef) case "multipart/form-data": schema.Type = objectType schema.Properties = make(map[string]*openapi3.SchemaRef) case "application/octet-stream": default: schema.Type = stringType schema.Format = "binary" } return schema } ================================================ FILE: pkg/input/formats/openapi/generator.go ================================================ package openapi import ( "bytes" "fmt" "io" "mime/multipart" "net/http" "net/http/httputil" "net/url" "os" "strings" "github.com/clbanning/mxj/v2" "github.com/getkin/kin-openapi/openapi3" "github.com/pkg/errors" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v3/pkg/input/formats" httpTypes "github.com/projectdiscovery/nuclei/v3/pkg/input/types" "github.com/projectdiscovery/nuclei/v3/pkg/types" "github.com/projectdiscovery/nuclei/v3/pkg/utils/json" "github.com/projectdiscovery/utils/errkit" "github.com/projectdiscovery/utils/generic" mapsutil "github.com/projectdiscovery/utils/maps" "github.com/valyala/fasttemplate" ) const ( globalAuth = "globalAuth" DEFAULT_HTTP_SCHEME_HEADER = "Authorization" ) // GenerateRequestsFromSchema generates http requests from an OpenAPI 3.0 document object func GenerateRequestsFromSchema(schema *openapi3.T, opts formats.InputFormatOptions, callback formats.ParseReqRespCallback) error { if len(schema.Servers) == 0 { return errors.New("no servers found in openapi schema") } // new set of globalParams obtained from security schemes globalParams := openapi3.NewParameters() if len(schema.Security) > 0 { params, err := GetGlobalParamsForSecurityRequirement(schema, &schema.Security) if err != nil { return err } globalParams = append(globalParams, params...) } // validate global param requirements for _, param := range globalParams { if val, ok := opts.Variables[param.Value.Name]; ok { param.Value.Example = val } else { // if missing check for validation if opts.SkipFormatValidation { gologger.Verbose().Msgf("openapi: skipping all requests due to missing global auth parameter: %s\n", param.Value.Name) return nil } else { // fatal error gologger.Fatal().Msgf("openapi: missing global auth parameter: %s\n", param.Value.Name) } } } missingVarMap := make(map[string]struct{}) optionalVarMap := make(map[string]struct{}) missingParamValueCallback := func(param *openapi3.Parameter, opts *generateReqOptions) { if !param.Required { optionalVarMap[param.Name] = struct{}{} return } missingVarMap[param.Name] = struct{}{} } for _, serverURL := range schema.Servers { pathURL := serverURL.URL // Split the server URL into baseURL and serverPath u, err := url.Parse(pathURL) if err != nil { return errors.Wrap(err, "could not parse server url") } baseURL := fmt.Sprintf("%s://%s", u.Scheme, u.Host) serverPath := u.Path for path, v := range schema.Paths.Map() { // a path item can have parameters ops := v.Operations() requestPath := path if serverPath != "" { requestPath = serverPath + path } for method, ov := range ops { if err := generateRequestsFromOp(&generateReqOptions{ requiredOnly: opts.RequiredOnly, method: method, pathURL: baseURL, requestPath: requestPath, op: ov, schema: schema, globalParams: globalParams, reqParams: v.Parameters, opts: opts, callback: callback, missingParamValueCallback: missingParamValueCallback, }); err != nil { gologger.Warning().Msgf("Could not generate requests from op: %s\n", err) } } } } if len(missingVarMap) > 0 && !opts.SkipFormatValidation { gologger.Error().Msgf("openapi: Found %d missing parameters, use -skip-format-validation flag to skip requests or update missing parameters generated in %s file,you can also specify these vars using -var flag in (key=value) format\n", len(missingVarMap), formats.DefaultVarDumpFileName) gologger.Verbose().Msgf("openapi: missing params: %+v", mapsutil.GetSortedKeys(missingVarMap)) if config.CurrentAppMode == config.AppModeCLI { // generate var dump file vars := &formats.OpenAPIParamsCfgFile{} for k := range missingVarMap { vars.Var = append(vars.Var, k+"=") } vars.OptionalVars = mapsutil.GetSortedKeys(optionalVarMap) if err := formats.WriteOpenAPIVarDumpFile(vars); err != nil { gologger.Error().Msgf("openapi: could not write params file: %s\n", err) } // exit with status code 1 os.Exit(1) } } return nil } type generateReqOptions struct { // requiredOnly specifies whether to generate only required fields requiredOnly bool // method is the http method to use method string // pathURL is the base url to use pathURL string // requestPath is the path to use requestPath string // schema is the openapi schema to use schema *openapi3.T // op is the operation to use op *openapi3.Operation // post request generation callback callback formats.ParseReqRespCallback // global parameters globalParams openapi3.Parameters // requestparams map reqParams openapi3.Parameters // global var map opts formats.InputFormatOptions // missingVar Callback missingParamValueCallback func(param *openapi3.Parameter, opts *generateReqOptions) } // generateRequestsFromOp generates requests from an operation and some other data // about an OpenAPI Schema Path and Method object. // // It also accepts an optional requiredOnly flag which if specified, only returns the fields // of the structure that are required. If false, all fields are returned. func generateRequestsFromOp(opts *generateReqOptions) error { req, err := http.NewRequest(opts.method, opts.pathURL+opts.requestPath, nil) if err != nil { return errors.Wrap(err, "could not make request") } reqParams := opts.reqParams if reqParams == nil { reqParams = openapi3.NewParameters() } // add existing req params reqParams = append(reqParams, opts.op.Parameters...) // check for endpoint specific auth if opts.op.Security != nil { params, err := GetGlobalParamsForSecurityRequirement(opts.schema, opts.op.Security) if err != nil { return err } reqParams = append(reqParams, params...) } else { reqParams = append(reqParams, opts.globalParams...) } query := url.Values{} for _, parameter := range reqParams { value := parameter.Value if value.Schema == nil || value.Schema.Value == nil { continue } // paramValue or default value to use var paramValue interface{} // accept override from global variables if val, ok := opts.opts.Variables[value.Name]; ok { paramValue = val } else if value.Schema.Value.Default != nil { paramValue = value.Schema.Value.Default } else if value.Schema.Value.Example != nil { paramValue = value.Schema.Value.Example } else if len(value.Schema.Value.Enum) > 0 { paramValue = value.Schema.Value.Enum[0] } else { if !opts.opts.SkipFormatValidation { if opts.missingParamValueCallback != nil { opts.missingParamValueCallback(value, opts) } // skip request if param in path else skip this param only if value.Required { // gologger.Verbose().Msgf("skipping request [%s] %s due to missing value (%v)\n", opts.method, opts.requestPath, value.Name) return nil } else { // if it is in path then remove it from path opts.requestPath = strings.ReplaceAll(opts.requestPath, fmt.Sprintf("{%s}", value.Name), "") if !opts.opts.RequiredOnly { gologger.Verbose().Msgf("openapi: skipping optional param (%s) in (%v) in request [%s] %s due to missing value (%v)\n", value.Name, value.In, opts.method, opts.requestPath, value.Name) } continue } } exampleX, err := generateExampleFromSchema(value.Schema.Value) if err != nil { // when failed to generate example // skip request if param in path else skip this param only if value.Required { gologger.Verbose().Msgf("openapi: skipping request [%s] %s due to missing value (%v)\n", opts.method, opts.requestPath, value.Name) return nil } else { // if it is in path then remove it from path opts.requestPath = strings.ReplaceAll(opts.requestPath, fmt.Sprintf("{%s}", value.Name), "") if !opts.opts.RequiredOnly { gologger.Verbose().Msgf("openapi: skipping optional param (%s) in (%v) in request [%s] %s due to missing value (%v)\n", value.Name, value.In, opts.method, opts.requestPath, value.Name) } continue } } paramValue = exampleX } if opts.requiredOnly && !value.Required { // remove them from path if any opts.requestPath = strings.ReplaceAll(opts.requestPath, fmt.Sprintf("{%s}", value.Name), "") continue // Skip this parameter if it is not required and we want only required ones } switch value.In { case "query": query.Set(value.Name, types.ToString(paramValue)) case "header": req.Header.Set(value.Name, types.ToString(paramValue)) case "path": opts.requestPath = fasttemplate.ExecuteStringStd(opts.requestPath, "{", "}", map[string]interface{}{ value.Name: types.ToString(paramValue), }) case "cookie": req.AddCookie(&http.Cookie{Name: value.Name, Value: types.ToString(paramValue)}) } } req.URL.RawQuery = query.Encode() req.URL.Path = opts.requestPath if opts.op.RequestBody != nil { for content, value := range opts.op.RequestBody.Value.Content { cloned := req.Clone(req.Context()) var val interface{} if value.Schema == nil || value.Schema.Value == nil { val = generateEmptySchemaValue(content) } else { var err error val, err = generateExampleFromSchema(value.Schema.Value) if err != nil { continue } } // var body string switch content { case "application/json": if marshalled, err := json.Marshal(val); err == nil { // body = string(marshalled) cloned.Body = io.NopCloser(bytes.NewReader(marshalled)) cloned.ContentLength = int64(len(marshalled)) cloned.Header.Set("Content-Type", "application/json") } case "application/xml": values := mxj.Map(val.(map[string]interface{})) if marshalled, err := values.Xml(); err == nil { // body = string(marshalled) cloned.Body = io.NopCloser(bytes.NewReader(marshalled)) cloned.ContentLength = int64(len(marshalled)) cloned.Header.Set("Content-Type", "application/xml") } else { gologger.Warning().Msgf("openapi: could not encode xml") } case "application/x-www-form-urlencoded": if values, ok := val.(map[string]interface{}); ok { cloned.Form = url.Values{} for k, v := range values { cloned.Form.Set(k, types.ToString(v)) } encoded := cloned.Form.Encode() cloned.ContentLength = int64(len(encoded)) // body = encoded cloned.Body = io.NopCloser(strings.NewReader(encoded)) cloned.Header.Set("Content-Type", "application/x-www-form-urlencoded") } case "multipart/form-data": if values, ok := val.(map[string]interface{}); ok { buffer := &bytes.Buffer{} multipartWriter := multipart.NewWriter(buffer) for k, v := range values { // This is a file if format is binary, otherwise field if property, ok := value.Schema.Value.Properties[k]; ok && property.Value.Format == "binary" { if writer, err := multipartWriter.CreateFormFile(k, k); err == nil { _, _ = writer.Write([]byte(types.ToString(v))) } } else { _ = multipartWriter.WriteField(k, types.ToString(v)) } } _ = multipartWriter.Close() // body = buffer.String() cloned.Body = io.NopCloser(buffer) cloned.ContentLength = int64(len(buffer.Bytes())) cloned.Header.Set("Content-Type", multipartWriter.FormDataContentType()) } case "text/plain": str := types.ToString(val) // body = str cloned.Body = io.NopCloser(strings.NewReader(str)) cloned.ContentLength = int64(len(str)) cloned.Header.Set("Content-Type", "text/plain") case "application/octet-stream": str := types.ToString(val) if str == "" { // use two strings str = "string1\nstring2" } if value.Schema != nil && generic.EqualsAny(value.Schema.Value.Format, "bindary", "byte") { cloned.Body = io.NopCloser(bytes.NewReader([]byte(str))) cloned.ContentLength = int64(len(str)) cloned.Header.Set("Content-Type", "application/octet-stream") } else { // use string placeholder cloned.Body = io.NopCloser(strings.NewReader(str)) cloned.ContentLength = int64(len(str)) cloned.Header.Set("Content-Type", "text/plain") } default: gologger.Verbose().Msgf("openapi: no correct content type found for body: %s\n", content) // LOG: return errors.New("no correct content type found for body") continue } dumped, err := httputil.DumpRequestOut(cloned, true) if err != nil { return errors.Wrap(err, "could not dump request") } rr, err := httpTypes.ParseRawRequestWithURL(string(dumped), cloned.URL.String()) if err != nil { return errors.Wrap(err, "could not parse raw request") } opts.callback(rr) continue } } if opts.op.RequestBody != nil { return nil } dumped, err := httputil.DumpRequestOut(req, true) if err != nil { return errors.Wrap(err, "could not dump request") } rr, err := httpTypes.ParseRawRequestWithURL(string(dumped), req.URL.String()) if err != nil { return errors.Wrap(err, "could not parse raw request") } opts.callback(rr) return nil } // GetGlobalParamsForSecurityRequirement returns the global parameters for a security requirement func GetGlobalParamsForSecurityRequirement(schema *openapi3.T, requirement *openapi3.SecurityRequirements) ([]*openapi3.ParameterRef, error) { globalParams := openapi3.NewParameters() if len(schema.Components.SecuritySchemes) == 0 { return nil, errkit.Newf("security requirements (%+v) without any security schemes found in openapi file", schema.Security) } found := false // this api is protected for each security scheme pull its corresponding scheme schemaLabel: for _, security := range *requirement { for name := range security { if scheme, ok := schema.Components.SecuritySchemes[name]; ok { found = true param, err := GenerateParameterFromSecurityScheme(scheme) if err != nil { return nil, err } globalParams = append(globalParams, &openapi3.ParameterRef{Value: param}) continue schemaLabel } } if !found && len(security) > 1 { // if this is case then both security schemes are required return nil, errkit.Newf("security requirement (%+v) not found in openapi file", security) } } if !found { return nil, errkit.Newf("security requirement (%+v) not found in openapi file", requirement) } return globalParams, nil } // GenerateParameterFromSecurityScheme generates an example from a schema object func GenerateParameterFromSecurityScheme(scheme *openapi3.SecuritySchemeRef) (*openapi3.Parameter, error) { if !generic.EqualsAny(scheme.Value.Type, "http", "apiKey") { return nil, errkit.Newf("unsupported security scheme type (%s) found in openapi file", scheme.Value.Type) } if scheme.Value.Type == "http" { // check scheme if !generic.EqualsAny(scheme.Value.Scheme, "basic", "bearer") { return nil, errkit.Newf("unsupported security scheme (%s) found in openapi file", scheme.Value.Scheme) } // HTTP authentication schemes basic or bearer use the Authorization header headerName := scheme.Value.Name if headerName == "" { headerName = DEFAULT_HTTP_SCHEME_HEADER } // create parameters using the scheme switch scheme.Value.Scheme { case "basic": h := openapi3.NewHeaderParameter(headerName) h.Required = true h.Description = globalAuth // differentiator for normal variables and global auth return h, nil case "bearer": h := openapi3.NewHeaderParameter(headerName) h.Required = true h.Description = globalAuth // differentiator for normal variables and global auth return h, nil } } if scheme.Value.Type == "apiKey" { // validate name and in if scheme.Value.Name == "" { return nil, errkit.Newf("security scheme (%s) name is empty", scheme.Value.Type) } if !generic.EqualsAny(scheme.Value.In, "query", "header", "cookie") { return nil, errkit.Newf("unsupported security scheme (%s) in (%s) found in openapi file", scheme.Value.Type, scheme.Value.In) } // create parameters using the scheme switch scheme.Value.In { case "query": q := openapi3.NewQueryParameter(scheme.Value.Name) q.Required = true q.Description = globalAuth // differentiator for normal variables and global auth return q, nil case "header": h := openapi3.NewHeaderParameter(scheme.Value.Name) h.Required = true h.Description = globalAuth // differentiator for normal variables and global auth return h, nil case "cookie": c := openapi3.NewCookieParameter(scheme.Value.Name) c.Required = true c.Description = globalAuth // differentiator for normal variables and global auth return c, nil } } return nil, errkit.Newf("unsupported security scheme type (%s) found in openapi file", scheme.Value.Type) } ================================================ FILE: pkg/input/formats/openapi/openapi.go ================================================ package openapi import ( "io" "github.com/getkin/kin-openapi/openapi3" "github.com/pkg/errors" "github.com/projectdiscovery/nuclei/v3/pkg/input/formats" ) // OpenAPIFormat is a OpenAPI Schema File parser type OpenAPIFormat struct { opts formats.InputFormatOptions } // New creates a new OpenAPI format parser func New() *OpenAPIFormat { return &OpenAPIFormat{} } var _ formats.Format = &OpenAPIFormat{} // Name returns the name of the format func (j *OpenAPIFormat) Name() string { return "openapi" } func (j *OpenAPIFormat) SetOptions(options formats.InputFormatOptions) { j.opts = options } // Parse parses the input and calls the provided callback // function for each RawRequest it discovers. func (j *OpenAPIFormat) Parse(input io.Reader, resultsCb formats.ParseReqRespCallback, filePath string) error { loader := openapi3.NewLoader() schema, err := loader.LoadFromIoReader(input) if err != nil { return errors.Wrap(err, "could not decode openapi 3.0 schema") } return GenerateRequestsFromSchema(schema, j.opts, resultsCb) } ================================================ FILE: pkg/input/formats/openapi/openapi_test.go ================================================ package openapi import ( "os" "strings" "testing" "github.com/projectdiscovery/nuclei/v3/pkg/input/types" "github.com/stretchr/testify/require" ) const baseURL = "http://hackthebox:5000" var methodToURLs = map[string][]string{ "GET": { "{{baseUrl}}/createdb", "{{baseUrl}}/", "{{baseUrl}}/users/v1/John.Doe", "{{baseUrl}}/users/v1", "{{baseUrl}}/users/v1/_debug", "{{baseUrl}}/books/v1", "{{baseUrl}}/books/v1/bookTitle77", }, "POST": { "{{baseUrl}}/users/v1/register", "{{baseUrl}}/users/v1/login", "{{baseUrl}}/books/v1", }, "PUT": { "{{baseUrl}}/users/v1/name1/email", "{{baseUrl}}/users/v1/name1/password", }, "DELETE": { "{{baseUrl}}/users/v1/name1", }, } func TestOpenAPIParser(t *testing.T) { format := New() proxifyInputFile := "../testdata/openapi.yaml" gotMethodsToURLs := make(map[string][]string) file, err := os.Open(proxifyInputFile) require.Nilf(t, err, "error opening proxify input file: %v", err) defer func() { _ = file.Close() }() err = format.Parse(file, func(rr *types.RequestResponse) bool { gotMethodsToURLs[rr.Request.Method] = append(gotMethodsToURLs[rr.Request.Method], strings.Replace(rr.URL.String(), baseURL, "{{baseUrl}}", 1)) return false }, proxifyInputFile) if err != nil { t.Fatal(err) } if len(gotMethodsToURLs) != len(methodToURLs) { t.Fatalf("invalid number of methods: %d", len(gotMethodsToURLs)) } for method, urls := range gotMethodsToURLs { if len(urls) != len(methodToURLs[method]) { t.Fatalf("invalid number of urls for method %s: %d", method, len(urls)) } require.ElementsMatch(t, urls, methodToURLs[method], "invalid urls for method %s", method) } } ================================================ FILE: pkg/input/formats/swagger/downloader.go ================================================ package swagger import ( "encoding/json" "fmt" "io" "net/http" "net/url" "os" "path/filepath" "strings" "time" "github.com/pkg/errors" "github.com/projectdiscovery/nuclei/v3/pkg/input/formats" "github.com/projectdiscovery/retryablehttp-go" "gopkg.in/yaml.v3" ) // SwaggerDownloader implements the SpecDownloader interface for Swagger 2.0 specs type SwaggerDownloader struct{} // NewDownloader creates a new Swagger downloader func NewDownloader() formats.SpecDownloader { return &SwaggerDownloader{} } // This function downloads a Swagger 2.0 spec from the given URL and saves it to tmpDir func (d *SwaggerDownloader) Download(urlStr, tmpDir string, httpClient *retryablehttp.Client) (string, error) { // Swagger can be JSON or YAML supportedExts := d.SupportedExtensions() isSupported := false for _, ext := range supportedExts { if strings.HasSuffix(urlStr, ext) { isSupported = true break } } if !isSupported { return "", fmt.Errorf("URL does not appear to be a Swagger spec (supported: %v)", supportedExts) } const maxSpecSizeBytes = 10 * 1024 * 1024 // 10MB // Use provided httpClient or create a fallback var client *http.Client if httpClient != nil { client = httpClient.HTTPClient } else { // Fallback to simple client if no httpClient provided client = &http.Client{Timeout: 30 * time.Second} } resp, err := client.Get(urlStr) if err != nil { return "", errors.Wrap(err, "failed to download Swagger spec") } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("HTTP %d when downloading Swagger spec", resp.StatusCode) } bodyBytes, err := io.ReadAll(io.LimitReader(resp.Body, maxSpecSizeBytes)) if err != nil { return "", errors.Wrap(err, "failed to read response body") } // Determine format and parse var spec map[string]interface{} var isYAML bool // Try JSON first if err := json.Unmarshal(bodyBytes, &spec); err != nil { // Then try YAML if err := yaml.Unmarshal(bodyBytes, &spec); err != nil { return "", fmt.Errorf("downloaded content is neither valid JSON nor YAML: %w", err) } isYAML = true } // Validate it's a Swagger 2.0 spec if swagger, exists := spec["swagger"]; exists { if swaggerStr, ok := swagger.(string); ok && strings.HasPrefix(swaggerStr, "2.") { // Valid Swagger 2.0 spec } else { return "", fmt.Errorf("not a valid Swagger 2.0 spec (found version: %v)", swagger) } } else { return "", fmt.Errorf("not a Swagger spec (missing 'swagger' field)") } // Extract host from URL for host configuration parsedURL, err := url.Parse(urlStr) if err != nil { return "", errors.Wrap(err, "failed to parse URL") } host := parsedURL.Host scheme := parsedURL.Scheme if scheme == "" { scheme = "https" } // Add host if missing if _, exists := spec["host"]; !exists { spec["host"] = host } // Add schemes if missing if _, exists := spec["schemes"]; !exists { spec["schemes"] = []string{scheme} } // Create output directory swaggerDir := filepath.Join(tmpDir, "swagger") if err := os.MkdirAll(swaggerDir, 0755); err != nil { return "", errors.Wrap(err, "failed to create swagger directory") } // Generate filename and content based on original format var filename string var content []byte if isYAML { filename = fmt.Sprintf("swagger-spec-%d.yaml", time.Now().Unix()) content, err = yaml.Marshal(spec) if err != nil { return "", errors.Wrap(err, "failed to marshal modified YAML spec") } } else { filename = fmt.Sprintf("swagger-spec-%d.json", time.Now().Unix()) content, err = json.Marshal(spec) if err != nil { return "", errors.Wrap(err, "failed to marshal modified JSON spec") } } filePath := filepath.Join(swaggerDir, filename) // Write file file, err := os.Create(filePath) if err != nil { return "", errors.Wrap(err, "failed to create file") } defer func() { _ = file.Close() }() if _, writeErr := file.Write(content); writeErr != nil { _ = os.Remove(filePath) return "", errors.Wrap(writeErr, "failed to write file") } return filePath, nil } // SupportedExtensions returns the list of supported file extensions for Swagger func (d *SwaggerDownloader) SupportedExtensions() []string { return []string{".json", ".yaml", ".yml"} } ================================================ FILE: pkg/input/formats/swagger/downloader_test.go ================================================ package swagger import ( "encoding/json" "net/http" "net/http/httptest" "os" "strings" "testing" "time" "gopkg.in/yaml.v3" ) func TestSwaggerDownloader_SupportedExtensions(t *testing.T) { downloader := &SwaggerDownloader{} extensions := downloader.SupportedExtensions() expected := []string{".json", ".yaml", ".yml"} if len(extensions) != len(expected) { t.Errorf("Expected %d extensions, got %d", len(expected), len(extensions)) } for i, ext := range extensions { if ext != expected[i] { t.Errorf("Expected extension %s, got %s", expected[i], ext) } } } func TestSwaggerDownloader_Download_JSON_Success(t *testing.T) { // Create a mock Swagger spec (JSON) mockSpec := map[string]interface{}{ "swagger": "2.0", "info": map[string]interface{}{ "title": "Test API", "version": "1.0.0", }, "paths": map[string]interface{}{ "/test": map[string]interface{}{ "get": map[string]interface{}{ "summary": "Test endpoint", }, }, }, } // Create mock server server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(mockSpec); err != nil { http.Error(w, "failed to encode response", http.StatusInternalServerError) } })) defer server.Close() // Create temp directory tmpDir, err := os.MkdirTemp("", "swagger_test") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } defer func() { if err := os.RemoveAll(tmpDir); err != nil { t.Fatalf("Failed to remove temp dir: %v", err) } }() // Test download downloader := &SwaggerDownloader{} filePath, err := downloader.Download(server.URL+"/swagger.json", tmpDir, nil) if err != nil { t.Fatalf("Download failed: %v", err) } // Verify file exists if !fileExists(filePath) { t.Errorf("Downloaded file does not exist: %s", filePath) } // Verify file content content, err := os.ReadFile(filePath) if err != nil { t.Fatalf("Failed to read downloaded file: %v", err) } var downloadedSpec map[string]interface{} if err := json.Unmarshal(content, &downloadedSpec); err != nil { t.Fatalf("Failed to parse downloaded JSON: %v", err) } // Verify host field was added _, exists := downloadedSpec["host"] if !exists { t.Error("Host field was not added to the spec") } } func TestSwaggerDownloader_Download_YAML_Success(t *testing.T) { // Create a mock Swagger spec (YAML) mockSpecYAML := ` swagger: "2.0" info: title: "Test API" version: "1.0.0" paths: /test: get: summary: "Test endpoint" ` // Create mock server server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/yaml") if _, err := w.Write([]byte(mockSpecYAML)); err != nil { http.Error(w, "failed to write response", http.StatusInternalServerError) } })) defer server.Close() // Create temp directory tmpDir, err := os.MkdirTemp("", "swagger_test") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } defer func() { if err := os.RemoveAll(tmpDir); err != nil { t.Fatalf("Failed to remove temp dir: %v", err) } }() // Test download downloader := &SwaggerDownloader{} filePath, err := downloader.Download(server.URL+"/swagger.yaml", tmpDir, nil) if err != nil { t.Fatalf("Download failed: %v", err) } // Verify file exists if !fileExists(filePath) { t.Errorf("Downloaded file does not exist: %s", filePath) } // Verify file content content, err := os.ReadFile(filePath) if err != nil { t.Fatalf("Failed to read downloaded file: %v", err) } var downloadedSpec map[string]interface{} if err := yaml.Unmarshal(content, &downloadedSpec); err != nil { t.Fatalf("Failed to parse downloaded YAML: %v", err) } // Verify host field was added _, exists := downloadedSpec["host"] if !exists { t.Error("Host field was not added to the spec") } } func TestSwaggerDownloader_Download_UnsupportedExtension(t *testing.T) { tmpDir, err := os.MkdirTemp("", "swagger_test") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } defer func() { if err := os.RemoveAll(tmpDir); err != nil { t.Fatalf("Failed to remove temp dir: %v", err) } }() downloader := &SwaggerDownloader{} _, err = downloader.Download("http://example.com/spec.xml", tmpDir, nil) if err == nil { t.Error("Expected error for unsupported extension, but got none") } if !strings.Contains(err.Error(), "URL does not appear to be a Swagger spec") { t.Errorf("Unexpected error message: %v", err) } } func TestSwaggerDownloader_Download_HTTPError(t *testing.T) { // Create mock server that returns 404 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) })) defer server.Close() tmpDir, err := os.MkdirTemp("", "swagger_test") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } defer func() { if err := os.RemoveAll(tmpDir); err != nil { t.Fatalf("Failed to remove temp dir: %v", err) } }() downloader := &SwaggerDownloader{} _, err = downloader.Download(server.URL+"/swagger.json", tmpDir, nil) if err == nil { t.Error("Expected error for HTTP 404, but got none") } } func TestSwaggerDownloader_Download_InvalidJSON(t *testing.T) { // Create mock server that returns invalid JSON server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if _, err := w.Write([]byte("invalid json")); err != nil { http.Error(w, "failed to write response", http.StatusInternalServerError) } })) defer server.Close() tmpDir, err := os.MkdirTemp("", "swagger_test") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } defer func() { if err := os.RemoveAll(tmpDir); err != nil { t.Fatalf("Failed to remove temp dir: %v", err) } }() downloader := &SwaggerDownloader{} _, err = downloader.Download(server.URL+"/swagger.json", tmpDir, nil) if err == nil { t.Error("Expected error for invalid JSON, but got none") } } func TestSwaggerDownloader_Download_InvalidYAML(t *testing.T) { // Create mock server that returns invalid YAML server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/yaml") if _, err := w.Write([]byte("invalid: yaml: content: [")); err != nil { http.Error(w, "failed to write response", http.StatusInternalServerError) } })) defer server.Close() tmpDir, err := os.MkdirTemp("", "swagger_test") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } defer func() { if err := os.RemoveAll(tmpDir); err != nil { t.Fatalf("Failed to remove temp dir: %v", err) } }() downloader := &SwaggerDownloader{} _, err = downloader.Download(server.URL+"/swagger.yaml", tmpDir, nil) if err == nil { t.Error("Expected error for invalid YAML, but got none") } } func TestSwaggerDownloader_Download_Timeout(t *testing.T) { // Create mock server with delay server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { time.Sleep(35 * time.Second) // Longer than 30 second timeout if err := json.NewEncoder(w).Encode(map[string]interface{}{"test": "data"}); err != nil { http.Error(w, "failed to encode response", http.StatusInternalServerError) } })) defer server.Close() tmpDir, err := os.MkdirTemp("", "swagger_test") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } defer func() { if err := os.RemoveAll(tmpDir); err != nil { t.Fatalf("Failed to remove temp dir: %v", err) } }() downloader := &SwaggerDownloader{} _, err = downloader.Download(server.URL+"/swagger.json", tmpDir, nil) if err == nil { t.Error("Expected timeout error, but got none") } } func TestSwaggerDownloader_Download_WithExistingHost(t *testing.T) { // Create a mock Swagger spec with existing host mockSpec := map[string]interface{}{ "swagger": "2.0", "info": map[string]interface{}{ "title": "Test API", "version": "1.0.0", }, "host": "existing-host.com", "paths": map[string]interface{}{}, } // Create mock server server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(mockSpec); err != nil { http.Error(w, "failed to encode response", http.StatusInternalServerError) } })) defer server.Close() tmpDir, err := os.MkdirTemp("", "swagger_test") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } defer func() { if err := os.RemoveAll(tmpDir); err != nil { t.Fatalf("Failed to remove temp dir: %v", err) } }() downloader := &SwaggerDownloader{} filePath, err := downloader.Download(server.URL+"/swagger.json", tmpDir, nil) if err != nil { t.Fatalf("Download failed: %v", err) } // Verify existing host is preserved content, err := os.ReadFile(filePath) if err != nil { t.Fatalf("Failed to read downloaded file: %v", err) } var downloadedSpec map[string]interface{} if err := json.Unmarshal(content, &downloadedSpec); err != nil { t.Fatalf("Failed to parse downloaded JSON: %v", err) } host, exists := downloadedSpec["host"] if !exists { t.Error("Host field was removed from the spec") } if hostStr, ok := host.(string); !ok || hostStr != "existing-host.com" { t.Errorf("Expected host 'existing-host.com', got '%v'", host) } } // Helper function to check if file exists func fileExists(filename string) bool { _, err := os.Stat(filename) return !os.IsNotExist(err) } ================================================ FILE: pkg/input/formats/swagger/swagger.go ================================================ package swagger import ( "io" "path" "github.com/getkin/kin-openapi/openapi2" "github.com/getkin/kin-openapi/openapi2conv" "github.com/getkin/kin-openapi/openapi3" "github.com/invopop/yaml" "github.com/pkg/errors" "github.com/projectdiscovery/nuclei/v3/pkg/input/formats" "github.com/projectdiscovery/nuclei/v3/pkg/input/formats/openapi" "github.com/projectdiscovery/nuclei/v3/pkg/utils/json" ) // SwaggerFormat is a Swagger Schema File parser type SwaggerFormat struct { opts formats.InputFormatOptions } // New creates a new Swagger format parser func New() *SwaggerFormat { return &SwaggerFormat{} } var _ formats.Format = &SwaggerFormat{} // Name returns the name of the format func (j *SwaggerFormat) Name() string { return "swagger" } func (j *SwaggerFormat) SetOptions(options formats.InputFormatOptions) { j.opts = options } // Parse parses the input and calls the provided callback // function for each RawRequest it discovers. func (j *SwaggerFormat) Parse(input io.Reader, resultsCb formats.ParseReqRespCallback, filePath string) error { schemav2 := &openapi2.T{} ext := path.Ext(filePath) var err error if ext == ".yaml" || ext == ".yml" { var data []byte data, err = io.ReadAll(input) if err != nil { return errors.Wrap(err, "could not read data file") } err = yaml.Unmarshal(data, schemav2) } else { err = json.NewDecoder(input).Decode(schemav2) } if err != nil { return errors.Wrap(err, "could not decode openapi 2.0 schema") } schema, err := openapi2conv.ToV3(schemav2) if err != nil { return errors.Wrap(err, "could not convert openapi 2.0 schema to 3.0") } loader := openapi3.NewLoader() err = loader.ResolveRefsIn(schema, nil) if err != nil { return errors.Wrap(err, "could not resolve openapi schema references") } return openapi.GenerateRequestsFromSchema(schema, j.opts, resultsCb) } ================================================ FILE: pkg/input/formats/swagger/swagger_test.go ================================================ package swagger import ( "os" "testing" "github.com/projectdiscovery/nuclei/v3/pkg/input/types" "github.com/stretchr/testify/require" ) func TestSwaggerAPIParser(t *testing.T) { format := New() proxifyInputFile := "../testdata/swagger.yaml" var gotMethodsToURLs []string file, err := os.Open(proxifyInputFile) require.Nilf(t, err, "error opening proxify input file: %v", err) defer func() { _ = file.Close() }() err = format.Parse(file, func(request *types.RequestResponse) bool { gotMethodsToURLs = append(gotMethodsToURLs, request.URL.String()) return false }, proxifyInputFile) if err != nil { t.Fatal(err) } if len(gotMethodsToURLs) != 2 { t.Fatalf("invalid number of methods: %d", len(gotMethodsToURLs)) } expectedURLs := []string{ "https://localhost/v1/users", "https://localhost/v1/users/1?test=asc", } require.ElementsMatch(t, gotMethodsToURLs, expectedURLs, "could not get swagger urls") } ================================================ FILE: pkg/input/formats/testdata/burp.xml ================================================ ]> localhost 8087 http null 200 152 JSON google.com 80 http null 301 792 HTML ================================================ FILE: pkg/input/formats/testdata/ginandjuice.proxify.json ================================================ {"timestamp":"2023-09-07T21:03:38+05:30","url":"https://ginandjuice.shop/","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Cache-Control":"max-age=0","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=K3apVBuA9VPgjvRhmuEIK81dRquSyRPC8gXbOq4toRUpky4GpcmmPzP2j1c7KVfskcjCGih6K1kxXVYeKlClX5Rx60P+G6gHWE6hNhos6T/CuvhaP5uLb0BZgaZ7; AWSALBCORS=K3apVBuA9VPgjvRhmuEIK81dRquSyRPC8gXbOq4toRUpky4GpcmmPzP2j1c7KVfskcjCGih6K1kxXVYeKlClX5Rx60P+G6gHWE6hNhos6T/CuvhaP5uLb0BZgaZ7","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"none","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/","scheme":"https"},"raw":"GET / HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nCache-Control: max-age=0\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=K3apVBuA9VPgjvRhmuEIK81dRquSyRPC8gXbOq4toRUpky4GpcmmPzP2j1c7KVfskcjCGih6K1kxXVYeKlClX5Rx60P+G6gHWE6hNhos6T/CuvhaP5uLb0BZgaZ7; AWSALBCORS=K3apVBuA9VPgjvRhmuEIK81dRquSyRPC8gXbOq4toRUpky4GpcmmPzP2j1c7KVfskcjCGih6K1kxXVYeKlClX5Rx60P+G6gHWE6hNhos6T/CuvhaP5uLb0BZgaZ7\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: none\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"2127","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:33:38 GMT","Set-Cookie":"AWSALB=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi; Expires=Thu, 14 Sep 2023 15:33:38 GMT; Path=/, AWSALBCORS=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi; Expires=Thu, 14 Sep 2023 15:33:38 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffd\u001a\ufffdr\ufffd8\ufffd\ufffd=\ufffd\ufffdwGa\u0006\ufffdMR\ufffdtHrs\ufffd@\ufffd\n-\u0003\u0003s\ufffdad[\ufffdUd\ufffdXr\ufffd\u001f\ufffd^\ufffd\ufffd\ufffdV\ufffd\ufffd:\ufffd\u001d;\ufffd\ufffdp3t:m$\ufffd\ufffdV\ufffd\ufffdRF?\ufffd]\ufffd\ufffd\ufffd\ufffd\ufffd\t\ufffdU\ufffd\u0026?\ufffd\ufffd?\u0004?\ufffd\ufffd\ufffd\ufffd~4CF\ufffd'\u0014gd:\ufffd2\"E\ufffd\u0005Dz\u000c\ufffd\u001a\ufffdd^ \ufffd'\u0003\ufffd_\ufffds3у\t\ufffd\u00116\ufffd\ufffd`DƄ\ufffd6b\ufffd\u0004\u0010\ufffdo\ufffdLB\ufffd\u0013H\ufffdˆㄌ\ufffd\u0019%\ufffdTd\ufffdA\ufffd\ufffd\ufffdp5v\ufffd4T\ufffd8$3\u001a\u0010\ufffd\u000c\ufffd\ufffd\\\ufffd\ufffd\u0005\u000ea\u0007F\ufffd\\8\u0015j2\ufffdh\ufffd\ufffd̂\ufffdSa\ufffdZ\ufffd\u0000\u0007\ufffd\u0007\ufffd\u0008\u0013i\u0002\ufffd{\ufffdҙ\ufffd\u003c\ufffdѝ\ufffd\u001b\ufffdd\u00072\ufffdH\ufffdd\ufffd|Q\ufffd5\ufffda;\ufffd\ufffdQ\ufffd\u003c\ufffd\u0019\ufffd\u003e\ufffd\ufffdc\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdɹH\u0008r\ufffd3\ufffd\ufffd\u001d\ufffd\ufffd\ufffdЋ\u001c\ufffd\ufffd\ufffd\ufffd\"\u001dy\u0016\ufffdj޻Q\ufffd\ufffd\u0017a\ufffdx\ufffd\ufffd4\ufffd\u0010\u000c\ufffd\u000c\ufffdp\ufffdT5^\u0011\ufffd=\n\t\u0014\u0015\u001c\u0005\u000cK9v\ufffd\ufffd\ufffd!\ufffd\ufffd\ufffd\ufffd\u001a\ufffdAJ7\ufffd\ufffd\ufffdۘJ\u0004\ufffd\u0018\ufffd\ufffdQ\ufffddX\u0011V\ufffdY\ufffd8|\u0006\ufffd\ufffd9\ufffd\u0011\ufffd\ufffdh\ufffdͮ\ufffd\u000f\ufffd8\t\ufffdTdH\u0011\ufffd(\ufffd4\ufffd\u001d\ufffd\ufffd\ufffd\ufffd\u0002\ufffd2\ufffd\n;\ufffd\ufffd\u0002s\ufffdW\ufffd\ufffdH\ufffdxy\u0014m\ufffd\u000e\u0012\u003c\u0011`T\ufffdL\ufffdi\ufffd㡘\ufffd\u003e~\ufffd\ufffdGc\ufffd_.\ufffd\ufffd\ufffd\ufffd`e5\ufffd\ufffd\ufffd\ufffdЮ.\ufffda\ufffdW\ufffd\ufffdV\ufffd\ufffdt\ufffd\ufffd)\ufffd[\ufffd\ufffd\ufffd\u001dT\ufffdke8\\\u001d\u001e\ufffdCL\ufffd\ufffd\u001a#\ufffd\ufffd\ufffd\ufffdb\ufffd\ufffd\ufffd?{\ufffd\ufffd\ufffd)\ufffd¦\ufffd3Ƃ\ufffdx|po\ufffd+#X\ufffd\u000cz \ufffdT\ufffd\ufffd\u001f\ufffd\ufffd\ufffdA\ufffdܰf\ufffdș\\\ufffd\n\u0015`֨\ufffd\u0003R\u0002\ufffd\ufffd\u0018\ufffd\ufffd\u000c\u0026\r\ufffd\ufffd1\u0017o\ufffd^\u0000\ufffdZ\ufffdV[\u003c\ufffdf\ufffd\ufffdltPCV\ufffd\ufffd\u0005\ufffd!\u0005S\u0000\ufffd\ufffd\ufffd\u0011\ufffdQ\ufffd\ufffd\ufffd!SCN\ufffd\ufffd\ufffd2\u0011\t\ufffdS\ufffdo \ufffdᆅ\u001bR\ufffda\ufffd\u0016\ufffd\u000c\u000e\ufffd\r\ufffd,!r\ufffd0m\ufffd\ufffd\ufffd\ufffd\t\ufffdtXm\ufffd\u0012\ufffd\u0019MpV\ufffdQ\ufffd\ufffdn\ufffdd\ufffd\u001d\ufffd\u0000\ufffd\ufffd\ufffd~\ufffd\u0014\u0004\u0005+P\ufffd\u0003\ufffd\ufffd '\ufffd\ufffdL\ufffdy\ufffdd\ufffdPV\ufffdy]6\ufffd=w\ufffda\ufffd1\ufffd\ufffd\ufffd\ufffd¾ȕ3\ufffd\ufffd~\ufffdDV|#\ufffdF^\ufffdnmB\ufffd\ufffd\ufffd\ufffd{6\"\u001c\u0004\"\ufffdʥAE*I\ufffd\ufffd\ufffd\ufffd\u001d\ufffd,\ufffd\ufffd \ufffd-\u0016o\ufffd)Xx\ufffd\ufffd\u0001\ufffd\u0002\ufffd\u0007п\ufffd\ufffd\u0004\ufffd\u0005d\ufffdd5\ufffd.\ufffd-\ufffd\ufffd\rp\ufffdco\u001e\ufffd\u001c\ufffd\u0007\ufffd\u0010\u0011\ufffd\ufffd3džn'\ufffd\ufffd/\ufffd/\u000bT~\ufffd\u0013\ufffd\ufffd\ufffdݝ\ufffdW\u0018l\ufffd\ufffd5k-\u0003\ufffd\ufffdW\ufffd\ufffd\ufffd\ufffdwXf\ufffd\ufffd֫Q\ufffd\ufffd\ufffdP\ufffd\ufffdn;\u001f\ufffd\ufffd\ufffd\ufffd\u0012Q\ufffd\u0008\ufffd\ufffdWf3\u001aE\ufffdB\ufffd\ufffdY\u001e\ufffdq\u0000\ufffd\ufffd6*\ufffd\ufffdƼאyk\ufffdk\ufffd\ufffd5(\ufffd\ufffd\ufffdPѝH\u000c\u0005vK\u0015\ufffd@\ufffd^\ufffd\ufffd\ufffd\u0002\ufffd\u0026\ufffd;\ufffd\ufffd\u000c\ufffd\r\ufffdKA\u000bE\ufffde\ufffd[F\ufffd\u0026D\ufffd.7\ufffd\ufffd\ufffdl\n\r[+m\ufffd\ufffd7\n\ufffd\ufffd\ufffd\"g?n\ufffd\ufffd\ufffd\u0000\u0006aƠ\ufffdhI\ufffd\r\u0015\ufffd\u0006\ufffd\ufffd\u001bY\u0010\ufffdʈfb\u001b\ufffd\u00151/\ufffdζv\u0026+\ufffd\ufffd\ufffd\u0014\ufffd:\u0005\ufffd\u0005\ufffdR\ufffd\ufffd\ufffd\u0000\ufffd\ufffd\ufffdV\u0013h\u0012\ufffd(4\u001b\ufffdAe)\ufffd{\ufffd\ufffd\u0010\ufffdX\"h\u003c\u0019\ufffd;\u001a\n\ufffdc(\"\ufffd\ufffd\u0012\ufffd)\ufffd(w\ufffd۶\ufffdҒ\ufffdvG\ufffd\u0004\ufffd\u001fyǺ\u001a\ufffd\ufffdY9-\ufffd\ufffd\u0000\ufffd\u0014\ufffd?ڒ=\ufffd%\u001d:F; 3\ufffdr\ufffdu\u0019\ufffd\u0010'$\ufffd\ufffd\ufffd\u0016eո\u001b[\ufffd\u0026+-m\ufffd\u0002\ufffd\ufffd\ufffdJ\ufffd\ufffdx\u0019N\ufffd\ufffd\ufffd\ufffd\u0005\u001b%\ufffd\ufffd0\ufffd\ufffd\ufffdJV\ufffd(\ufffd?\u000f\ufffd\ufffd\u000et\u000c-\ufffdD\ufffd%\ufffdIW\ufffd\ufffd\ufffd\u0016\u0014\ufffd6\ufffd^\n\ufffdꦞ|$!\ufffdg\ufffd\ufffd\u003c\ufffd\ufffdW\u003c\ufffd\\-\ufffd\ufffd\u0013\ufffd\ufffdNE\ufffd\t\ufffd\ufffd \u0004\u000ew\ufffdy#\ufffdfX_+\u000cw\ufffd\ufffd\ufffd\ufffd\ufffdT\u0005\ufffd0~\u001d\u001e\ufffd\u001e\u001c6\ufffd\ufffd\ufffdT\ufffd\ufffd\ufffd\ufffdԐ\ufffd\ufffdɮĺ\u0015\u0002\u001d,`\ufffd'\u000b\ufffd\ufffdwT\ufffd\r\ufffd\ufffdo\u001d\u001d/\ufffd\ufffdQ\ufffd\ufffd\ufffd*\ufffd\ufffdQ\ufffd\ufffd\ufffd\ufffd\ufffd‡\ufffdR\ufffd\ufffd\ufffd\n\ufffd\ufffdT\ufffdK\ufffdg\u000c\u0017\ufffd\ufffd\ufffd\ufffd\ufffdA\ufffd\ufffd\ufffd\ufffdTsKYeA\ufffd'\ufffdu#\ufffdWU\ufffd\ufffdZv\ufffd\ufffd\u000c\ufffd\ufffd\ufffdK\ufffd\\CsSю\ufffd\ufffd\ufffd@\ufffd\ufffd\ufffdC\ufffd\ufffd^\ufffdd\ufffdKX\ufffd=\ufffd]\ufffd\ufffd\ufffdޅ\ufffd\ufffd\ufffd\ufffdOt\ufffdiV\ufffd3\\\ufffdm\u000fZ9[1\u001e\ufffd!]\ufffd\ufffdF\ufffdm\u001c\u001b\ufffd\ufffda\u0007\ufffd\ufffdj\u0012\u0006\ufffd\ufffd\n\ufffd\ufffd\ufffd@\ufffd;\u0008\ufffd-\u0014\ufffd\ufffd\"I\u0019\ufffdBj/\"=\ufffd\ufffd\"\ufffd\ufffd\ufffd\ufffd\ufffd\u0007\ufffd\ufffda\ufffd\ufffd|\ufffd['l\ufffd+\ufffd\ufffd\n\u0001=\ufffd;Ϡ\ufffd\ufffdon\ufffd*R\ufffd\ufffdԤnRfd\ufffdoD\ufffdܛo\ufffd\u0004\ufffd\u0011\ufffd\u00168\ufffd\u0002^\u0011H\u001f(\ufffdҾ0a\ufffd\\$s_\ufffd\ufffd\u003eA\\̷\ufffd\t\ufffd\ufffd/\u0004\ufffdo\ufffd\ufffd\ufffd\ufffd\ufffdoNC\ufffd\u0002\ufffd$9\ufffdn\ufffd\ufffd\ufffd\u0010\ufffd!\ufffd6\ufffd\ufffd\u0014s\ufffd0\ufffd\ufffdo^\ufffdȗ\ufffd\ufffd\ufffd\ufffd`'2\ufffd\ufffd\ufffd\u0012bX?}\u0018n\u0000\u000eV\ufffdc(\ufffd\ufffd\ufffd(\u0010\ufffda_d\ufffd߀\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdt\u001b\ufffd7ʃ\ufffdW\ufffd\ufffda_A\ufffds-\u000fj\u001e\u0011kem\ufffd\u0000\u0017\ufffd΍\ufffd\ufffd\ufffdA!\ufffd\u00057!*\u0016\ufffd5\ufffdQ\ufffdNac\u0003\ufffd\ufffd\ufffd\ufffd@\ufffd\ufffdP\ufffd\ufffd\n\ufffd\ufffd9\ufffd\u0019\ufffd\ufffd摔$\ufffd\u0017\ufffdK\ufffd\ufffd\u0008\ufffd\u001d\ufffdX\ufffdP7\ufffdO\ufffd\u0014\u000eC8m[\ufffdSKމi\u0018\u0012\ufffd\ufffdó̦\u000eҭ\u001e\u000c\ufffdtv\ufffd\ufffd\ufffdɻ\ufffd\ufffd\ufffd_\u001f\u001e\ufffdǟs\ufffd䃺\u000e/_\ufffd\ufffd\ufffd\ufffd\ufffde\ufffd~e@Y\ufffd\u00035ۂH\u0012\ufffd\u0026o\u0016\ufffd\u0019yv\ufffd6\ufffd\ufffd\ufffd\ufffdQX \ufffdTp\ufffd\ufffd\ufffd\ufffd\u001fK\ufffdf\ufffdn9a\ufffd\ufffd$\ufffd✂\u0007\ufffd\u0006\ufffd\ufffd\ufffdSC\ufffd̰t\u0017\ufffd\ufffd\ufffd{\ufffd\ufffd\ufffdN\ufffd\ufffdMʧ\ufffds\u0019ٟ\u000c\u000e\u0003?\ufffd\"\u001d\t\n\u0015C\ufffd\u0007\u003e\ufffd\ufffd\ufffd_\ufffd{\ufffd\ufffd\ufffd\ufffdU\u0016\ufffd\ufffdn\ufffdB\ufffdg\ufffd\ufffd\ufffd3\ufffd\ufffd\ufffd\u0018\ufffd V\ufffd\u003c\ufffd^\ufffd\u001a\ufffd\ufffd\ufffdU\ufffd.\ufffd.\ufffd\ufffdd\u001a\ufffd6\u0015\u0003\ufffdv\ufffd\ufffd\ufffdȊl*V\u0007D)\t]\ufffd~\ufffd\ufffd5\ufffdU\ufffdP\ufffdvۂ\ufffd\ufffdN;\ufffd\ufffd\ufffd?Ӕ\ufffd;;*\ufffd\ufffd^_\ufffd\ufffd;5}\ufffd\ufffd\ufffdo\u000b\ufffd\ufffdL!\ufffd@4#\u0026\ufffd\ufffd9\u0003!2\ufffd\u0005\ufffd\ufffd\ufffdv\u0007\ufffd:T\u0000\ufffd+\ufffd\ufffd\ufffd\ufffd\ufffd\u0007\ufffd\ufffdh\u0014\ufffdR\ufffd\ufffd\ufffd\ufffdȹ\ufffd\ufffd7\u000fԝ\ufffd\ufffd\tL\ufffd\ufffdG_\ufffd\u000e\ufffd\u0015Pxc)\ufffd\u000b\u0015\ufffd\ufffd\ufffd\ufffd\ufffd-Ǘ'm\ufffd\ufffdo\ufffdsj\u000b\u00128\ufffd|\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdc{\"E\u0000\u001a\ufffdhy\u0004m\u0013ޏW\ufffdo\ufffdݏW\ufffd\ufffd\ufffd\ufffd\ufffdg\ufffd-\ufffdڃ\ufffdڰ\ufffd(\ufffd\ufffdejS\ufffdu\ufffdͪ\u0014\ufffd\ufffd\u0008\ufffd\ufffdO\ufffd\u0012ͷ\u0016\ufffd\u0003*%\u0016\ufffd\ufffd(\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2127\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:33:38 GMT\r\nSet-Cookie: AWSALB=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi; Expires=Thu, 14 Sep 2023 15:33:38 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi; Expires=Thu, 14 Sep 2023 15:33:38 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:03:43+05:30","url":"https://ginandjuice.shop/catalog/product?productId=1","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi; AWSALBCORS=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi","Referer":"https://ginandjuice.shop/","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/catalog/product","scheme":"https"},"raw":"GET /catalog/product?productId=1 HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi; AWSALBCORS=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi\r\nReferer: https://ginandjuice.shop/\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"2461","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:33:43 GMT","Set-Cookie":"AWSALB=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; Expires=Thu, 14 Sep 2023 15:33:43 GMT; Path=/, AWSALBCORS=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; Expires=Thu, 14 Sep 2023 15:33:43 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdZ\ufffdr\ufffd6\u0012\ufffdާ@y\ufffd(\ufffd1ES\ufffd\u001b\ufffd\"\ufffd\ufffd8Nz\ufffd\u0013k\ufffd\\\ufffdޗ\u000cDB$b\ufffd`\u0008P\ufffd2\ufffdB\ufffd\u001a\ufffdd\ufffd\u0000H\ufffd\ufffdH\ufffd\ufffd\ufffd\ufffd~\ufffd\ufffdc\ufffd\ufffd\ufffd\ufffd\u0002\ufffd\ufffdbw\ufffdѷ/o.\ufffd\ufffd1\ufffdB\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd|!\ufffd\ufffd\"\ufffd\u0003\ufffdS?2\ufffdܡ(#\ufffd\ufffd\ufffd\u0011\ufffd\ufffd\ufffd'\ufffdax\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdp\ufffd\ufffd\ufffd7\ufffd']Ї\u0002\ufffd\u00116\u0016rɈ\ufffd\u0008\ufffdm`\n\u0002\u0000\ufffd-\ufffd\ufffdd?@L$F\t\ufffd\ufffdؚS\ufffdHy\u0026-\ufffd\ufffdD\ufffdD\ufffd\ufffd\u0005\rd4\u000eȜ\ufffd\ufffd\ufffd\u000f'(\u0017$\ufffdAB\u0018\ufffd\ufffdq­\n\ufffd\ufffd3\ufffdJ$2lU\u0004\ufffd(\ufffd\u0001\ufffd\ufffd\u000fH\ufffd\ufffd4\u0006\ufffd\ufffdGay#\ufffd\ufffd\ufffd\u000ea\u0007\u003c\u003e\u0000F.S\ufffd\ufffd$\ufffd\ufffd\ufffd\ufffd\ufffdؔZu\ufffd8\ts\ufffd\ufffd\u000f\ufffd\ufffd\ufffd~ڄ*\ufffdděЄ\ufffd4e\u0004]\u0005TR\ufffd\ufffdK\ufffd\ufffdIL\u0019\ufffd\ufffd$\ufffdA\ufffdK\ufffd\ufffd\ufffd\u0026\ufffd\u0011\ufffd\ufffdg\ufffd\ufffd\u001c\ufffd\u000f\ufffdF\u003c\u001d9\u0006\ufffd0\ufffdYSc4\ufffd\ufffd\u0012%\ufffd\r\ufffd\ufffd\u0001\u0003:G4\u0018[UFT\u0016\ufffdL\ufffd\ufffdZ\u0008\ufffda!Ɩ\ufffd\ufffd\u001d\u0010#=\ufffdluН\ufffd\ufffd2\ufffdy\u0017Q\ufffd\ufffd\u000f\ufffd\ufffd0:%\u0019\ufffd\ufffd-\ufffd\u003cg\t\ufffd\ufffd\rG\u000b2Ej\ufffd\ufffd\ufffdzT\u0018\ufffd\ufffd\t\tЌgH\u0012!i\u0012\ufffdF\ufffd\ufffd\ufffdH\ufffd\ufffd\u001d)\ufffdri\ufffd\ufffdT\ufffdL\ufffdk\u0005\u0018\ufffd\u0014\ufffd\ufffd\ufffd\ufffdm!\ufffd\ufffd\u001cH\u0007\ufffdM2E\ufffd$\ufffd\ufffd\ufffd\ufffd\u000f\ufffd.\u001a#wU\ufffd盵\ufffd\ufffd\ufffd\u0018\ufffd\ufffd\ufffdCS[VCiR\ufffd\u003e\ufffd蜧c\ufffd\ufffd\ufffd\ufffd\ufffdЏ\u001e\ufffd\ufffdX\u001b\ufffd\ufffd\ufffddz'\ufffd\ufffdb\ufffd\ufffd\u0008K\u003e}ܻ\u001e\ufffd۝\ufffd\u003eg~\ufffd\ufffd\ufffd\ufffd\ufffdi\ufffd\ufffd11\u001e\ufffd\ufffd\ufffdFe\u0004+\ufffda\u001f\ufffd$\u00158\ufffd\u0019\ufffd)\u001bԔ\rk\ufffd\ufffd,o\ufffdK\ufffd\u0004ڣb\u001f\ufffd\ufffdHFDoݷ@yX\ufffd\u001a\ufffd8[|\ufffd\ufffd\ufffdu{\ufffd\ufffd3\ufffd}\ufffd\u0007\ufffd\ufffd\ufffd*z\ufffd\ufffd\ufffd\ufffd`Ы\ufffd\ufffd\ufffd#l\ufffd\\ϩ\ufffd\ufffd\ufffd\u0013\ufffd\ufffdٌ\ufffd\u001c\ufffd\u0018\ufffd\u001b`\u001d\ufffdP\ufffd\ufffd\ufffd\nk\ufffdȧ\ufffd$\ufffda\ufffdU\ufffd\ufffd\ufffdԆ\ufffd4Ԝ\ufffd\u000b\ufffdUd\u0017(\ufffdh\ufffd\ufffd\ufffdy\ufffdQ\ufffd\u001dHF\ufffd\u001b醸\u001cz\ufffdK\t[\ufffd\u0018\ufffdK\u0012Xfe-\u0007(\ufffda\ufffd\ufffd\"Ƅ\ufffd\ufffd\ufffd\ufffd\ufffdu\ufffdH\ufffd\ufffdb\ufffd\ufffd\ufffdj\ufffd^\ufffd\ufffd?\ufffdXx\ufffdsiy7J\ufffd$ϖ\u000f$\ufffd\ufffd\ufffd\ufffd\ufffd\\\u0002e\ufffdI\ufffd\ufffdل}\ufffd牴\ufffd_Y\ufffdxi\u0017\ufffdp\ufffd\ufffdy\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdc\u0017\ufffd\u001e\ufffd/\ufffdZ\ufffd\ufffdC\ufffd\ufffdm`\ufffd\ufffdBgu\u001a+\ufffd.\ufffd[,O\ufffd\ufffd\u0007M{w\ufffd[t\ufffdN\ufffd\ufffd\ufffd\ufffd\u0026\ufffd%ָ\ufffdX\ufffdu\ufffd~\ufffdD\ufffd\ufffd\ufffd$z;\ufffd\ufffd\ufffd}\u0001a}\ufffdm\ufffd\ufffd0|\ufffd\ufffdQ|UG\ufffdiq\u0014vd\ufffdꪩ\ufffdF?\ufffd\ufffd\ufffd\ufffd\\Q|[\ufffd0d\u0004F\ufffd\u001b\ufffd\u0019\rC\ufffd*\ufffd٬\u0026\ufffd\ufffd\u0000\ufffd\u001dk\ufffdFN\ufffd\u0001\ufffdp\u0004\ufffd\u0014\ufffd8\u0016[\ufffd\ufffd\ufffd\u000c\u001e\ufffd\nY\ufffd\ufffd\ufffd1\ufffdݰSsf\ufffd\ufffd\ufffd1\u001c᫓\ufffd\ufffd-^\u001f\ufffd֪\u001d\ufffd\ufffdv\n[\ufffd`?G\ufffd\ufffd\ufffdV\ufffdK:+\ufffd\ufffd\ufffd\u001c7utLuC\ufffdھO!\ufffd\t\ufffd,\ufffd\ufffd\ufffdl\ufffdR\ufffd\ufffdM\ufffd\u001c\ufffd\ufffd\ufffdǤ}[7{պ\u0000\ufffd\u0000\ufffd\u0001Qs\ufffdf.moZ\ufffd\ufffdn\ufffdht\ufffdV-h\u001cn\ufffd\ufffdZ\ufffdXP?8\ufffdmt\ufffd\u0016\ufffd](\u001e\ufffd\ufffd\ufffd\ufffd\ufffd@\ufffd\u003c\ufffdi\u0012\ufffd\ufffd\ufffd(v4ܻ\ufffdP\ufffd'\ufffd\ufffd\u001e\ufffd\u001aǁS\u000b*\n\ufffd$m\ufffdW߷C'\ufffd\ufffd\ufffd\ufffd\ufffd~\ufffd\ufffd\ufffd\ufffdG9;\ufffd\ufffd\ufffdm\ufffd\n\n\ufffdo\u00263\ufffd\ufffd\u0012\u001dFۘ\ufffd\ufffd\ufffd}\ufffd+\ufffdS¼\ufffd\ufffdN\ufffd\u00189\ufffd\ufffd\ufffdo\ufffd\ufffd\ufffdAA\u0013\ufffd \ufffdLjјBD\ufffd\ufffdm\ufffd`\ufffdK\ufffdR('\u0008\u001c\u00064\ufffd\ufffd\u001d\ufffd.Q\ufffdS\ufffd*\ufffd\ufffd2\ufffdIp\ufffd\ufffd\u001fJ\ufffd\u0002\ufffd\ufffd\ufffd\ufffdC\ufffd\u0005\ufffd\ufffdy\ufffdc4\ufffdr*\ufffdG\ufffd\u00029A\ufffddVY\u0012\n\ufffdj\ufffd\u0011\u0015\ufffdf\ufffdm\u0013\ufffd.xƂ\u003ez\u001fa\ufffd\u0002NDғHb\u0001\ufffd\ufffd\ufffdH\t\u0006W\ufffd\ufffd\ufffd\ufffd\u0010\ufffd\ufffd\u0015I\u0011\u000e\u0002\u0012\ufffd\u0008C}\ufffd\ufffdD\ufffdI\ufffd1\u0004q\ufffdBE3\u001aF\u0000\ufffd0yB\u0010\ufffd\ufffd:o\ufffd\u0006\ufffdxhN\u0004\u000c\u0008rD*\ufffd\ufffd\ufffd\ufffd\ufffdH\u0007s\ufffdr\ufffdB\ufffd\u0003(\ufffdy\u0018\ufffduH\u000c\u000b88?}s\ufffd\ufffd\ufffd\ufffd\u0001\ufffd\u003c\ufffd\ufffd\u0005~]|\ufffd\ufffd\ufffd\ufffdm'd\u003e\ufffd\u0017\u0010_\ufffdw\ufffd\u001f\u0011\ufffd\ufffd\u000b/`^\ufffd\ufffd\u001d\ufffd~\ufffd\ufffd\ufffd+(\ufffd\ufffd\ufffdS\u000b_\ufffd{\nEwtC\u000b\ufffd\u0004\ufffd\u0008\ufffd\u00267\ufffd\ufffd:*ވ\u0026),eF\u003e\ufffd4\u0003\u0002\ufffd\ufffd[Da'\ufffd\ufffd2\ufffd\ufffdb\ufffdB\ufffd9\ufffd,\ufffd\u0012\ufffd+\ufffd\tU\u000b\u001c\u0015f\u0011@9\ufffd5\ufffdZ/\ufffd\ufffd\"\ufffd͓\ufffd\ufffdonj\ufffd\u0018j\u0000P\u0013\ufffdQq4\ufffd\u0010\ufffd\ufffdP\ufffd\u000f\ufffdI\ufffdKjm:\ufffd.B\ufffd=\"\ufffd\ufffd\ufffd[[!\ufffd\ufffd\ufffd\ufffd4\u0017F\ufffd)\ufffd\ufffd\ufffd*\ufffdu5훬\ufffd\ufffd\ufffd\ufffdI\ufffd\ufffdl\ufffd\ufffdS\ufffd\ufffd1\ufffd]\ufffdN\ufffd\ufffdq\u001c4\ufffdr\ufffd\u0000}-\ufffd\u0001\ufffd\u0007\ufffd\ufffd\ufffd \ufffd\ufffd\ufffd\ufffd\ufffd`\ufffd\ufffd\ufffdK諔\ufffd\ufffd\ufffd\ufffdЦTl\ufffd\ufffdG\ufffd\ufffd\ufffdj\ufffd\ufffd+\ufffd.h\ufffd\u003e\u0010.\u0014Ҭ\ufffd\ufffd\ufffdz\ufffd\ufffd_\ufffd]%.LE\ufffd,}\ufffdO9N$\ufffd\ufffd\ufffdzL=NY~\ufffd-)\ufffdh\ufffd\ufffd\u0000\ufffd\ufffd\u001b\u003c\ufffd%\ufffd\ufffdG\ufffd\ufffdY\ufffd\ufffd\ufffd \ufffd\ufffdw~4\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd O-\ufffd\ufffd\ufffd \u0017\ufffdwq4\ufffd\u000f\ufffd\ufffd\ufffd\ufffd \ufffd\ufffd幧\ufffd\ufffd\ufffd\u0001\ufffd\u001e\ufffdZ\u0017h\ufffd\u001e\ufffd[\u0017\ufffd\ufffd\u001e\ufffd\\\u0017\ufffd\ufffd\u001e\ufffd]\u0017\ufffd\ufffd\u001e\ufffd\ufffd#\u000ecs\u0016\u0017f\ufffd8t\ufffd\u0007\ufffd\ufffd\ufffd\ufffd\ufffd~؃\ufffd\ufffd#I\ufffd\u000c\ufffd\u0007\ufffdKM\ufffd\ufffd\ufffdd\ufffdo\ufffd\\̰%A\ufffd\u0026\ufffd\ufffd[\ufffd\ufffd˪\ufffd^MW[\ufffd\ufffdЌs\u0008@\ufffdE\u0006\u0001G}\u001ei+\ufffda:4\ufffd\ufffdv\ufffd\u0019\ufffdi\ufffd\ufffdyJ\ufffd}|}A\ufffd/\u001b\u0011\r\ufffd\ufffdd\u000e\ufffdSL\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u001a\ufffd\ufffd\u001c\ufffd)QA\ufffdȁV\ufffd\u0010\ufffd\ufffd3\ufffd\ufffdh\u0015\ufffd-h@\ufffdJ\ufffd\ufffd\t\u001c\ufffd\ufffd\ufffdtĤ\u0002B\u001d\ufffd\ufffd\u0019N P\ufffdʼn\u000e\ufffdȽ\ufffdrA\ufffd0\u0012Y\u0008\ufffd\ufffd4\u0010\ufffd\ufffd\ufffdQK\u0003\ufffd\ufffdF\ufffd\ufffd@\ufffd \ufffd\ufffds\u0006\ufffd,\ufffdt:M\ufffdP3\u0006\ufffd\ufffd\ufffd7\ufffd\ufffd\ufffdp\ufffd-\u001f\ufffd\ufffd\ufffd[\ufffd\u0000/\ufffd\ufffd\ufffd\ufffd[_—}\ufffd\ufffdҫ\ufffd*\u0000J\ufffdeԔr!\ufffd\ufffd\ufffd@k\rЖ\ufffd\ufffd\ufffd\ufffd ئ̸-\ufffdgʰO\"\ufffd\u0002u\ufffd}\ufffd\ufffd\ufffd\t\ufffdٶ\ufffd\ufffdt\ufffd\ufffd|\ufffd\ufffdV\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdY\ufffd\ufffdՕ{u\ufffdy\"\ufffdӋ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd~{5\ufffdo_\ufffd\ufffdWX\ufffd-\ufffdP1|\ufffdm\ufffd2햮=\ufffd\ufffd\ufffdZo\ufffd\ufffd\ufffd\ufffd'\ufffd)i\u0013r#\ufffd\ufffd\ufffd5\ufffdw\ufffd\u000c}\ufffd\u0005)m%\ufffd\ufffdgԿ+\ufffd/5\ufffdK-\ufffdc\ufffd\ufffdD\u003eQ\ufffd\ufffdپ\ufffd\ufffdI\ufffd\u0019\ufffd\u001aIG\ufffd78\ufffd\u000e\ufffdt\ufffd\ufffd%X\ufffdLG\u0008:\ufffd\ufffdq\ufffd\u001b=\ufffd\ufffd\ufffdfwE\u0018\u0016{\ufffd.\ufffd\u000b%\ufffd\ufffd\ufffd\ufffdS\ufffd\rR)\ufffd\u003c\tڳ\ufffd\u001b\ufffd\ufffd\u000e\ufffd\ufffd\u0005\ufffdj\u0013VBC\ufffd\ufffd\ufffd@\ufffd\u0019\ufffd\u000bvdcm*\ufffd\u0003PJ\u0002\u001b\ufffdߝ\ufffd\ufffdf\ufffd*T\ufffd\ufffd\u003ec\ufffd3\ufffd\u0001MS\ufffdy\ufffd\ufffd\ufffdJi\td\ufffdWN\ufffdΰ\ufffd\u0017\ufffdR\ufffd\ufffd\ufffd\n\u001bє\ufffd5#ڪ\ufffd2݂g\ufffd\r\ufffd\ufffdTVG\ufffd\ufffd4\ufffd1\ufffd\ufffdb\ufffd\ufffdN=غL%\ufffd\u000ePo\ufffd\ufffd\ufffdX\ufffd\u000b2\ufffd\ufffdH\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0017\rN\u0007C4\u0001\ufffd[\ufffd\ufffd\ufffde\ufffd\ufffd0\ufffd\ufffdn\ufffd\u001af[̴\ufffd:\ufffd\ufffdϩuH`\ufffd\ufffd\u0003ܒ\ufffd\ufffd\ufffd\u000cZ\ufffdbV\u0019K\ufffdÎ^\ufffd\ufffd|жx\ufffdV\ufffd\ufffdb\ufffd\ufffdZMm\ufffd\ufffd\u0017\ufffd\ufffd\ufffd\ufffd֍\ufffd\ufffdc\ufffdwj\ufffdL;\ufffd\ufffd\ufffd\ufffd]\ufffd\u0014N%\u001e,\ufffdo\ufffdl\ufffd\ufffd\u0017\ufffd\u001fZ\ufffdMaw,\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2461\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:33:43 GMT\r\nSet-Cookie: AWSALB=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; Expires=Thu, 14 Sep 2023 15:33:43 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; Expires=Thu, 14 Sep 2023 15:33:43 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:03:46+05:30","url":"https://ginandjuice.shop/resources/js/stockCheck.js","request":{"header":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; AWSALBCORS=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g","Referer":"https://ginandjuice.shop/catalog/product?productId=1","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"script","Sec-Fetch-Mode":"no-cors","Sec-Fetch-Site":"same-origin","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/resources/js/stockCheck.js","scheme":"https"},"raw":"GET /resources/js/stockCheck.js HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; AWSALBCORS=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g\r\nReferer: https://ginandjuice.shop/catalog/product?productId=1\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: script\r\nSec-Fetch-Mode: no-cors\r\nSec-Fetch-Site: same-origin\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Cache-Control":"public, max-age=3600","Content-Encoding":"gzip","Content-Length":"420","Content-Type":"application/javascript; charset=utf-8","Date":"Thu, 07 Sep 2023 15:33:46 GMT","Set-Cookie":"AWSALB=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/, AWSALBCORS=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdR\ufffdn\ufffd0\u000c\ufffd\ufffd+8]*\ufffd\ufffd\ufffdm7\u0017n\ufffd\ufffd\u001d6\ufffd\ufffda\ufffd\u000f(\u00163\u000bu\ufffd\ufffd\ufffd\ufffd\u0019A\ufffd}\ufffd\ufffd\u0006uS\ufffd\u0004\u000cX\ufffd\ufffd\ufffd\u0013\ufffdlh\ufffd\u001a=\ufffd\ufffd\ufffdn:\ufffd߯\ufffd\u000f\ufffdU\ufffd\ufffd\u003c.Zl\u001e\ufffd\ufffd~\ufffd\ufffd\ufffdX{\ufffd\ufffdӷ.\u0012z\ufffd\ufffd$-׎\ufffd\u000cV\ufffd7\ufffd\ufffd\ufffdX\ufffd\ufffd\u00048\u001ai\ufffd\u0017\nM\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdw\ufffdD\ufffd\ufffd\u001a\ufffd\rV\u00153x%i2\ufffd$=nA\ufffd_\u001b2\ufffd\ufffd(.2;\ufffd\ufffd\u001eE\ufffd5\ufffdL\ufffdH3\ufffd\ufffd\ufffd\ufffdI\ufffd\ufffd\ufffd\ufffd\ufffd\u0019l\u000c\ufffd3\ufffd\ufffdu\u0010\u0019|$\ufffd\ufffd\ufffd\u0001jЬ\u0000c\u0001\ufffd%\ufffd?\ufffdk\ufffd\ufffd:\ufffd+\ufffd\ufffd\ufffd\u000e\ufffd\nVHM\ufffd\u000f\ufffdD\ufffd0Av\ufffd\ufffd\ufffd5Gx\ufffd\ufffdb\u001f+\ufffd\ufffd\ufffd\"x\ufffd0\ufffd?\u000c\u001b\u003c\ufffd`\ufffd\ufffd\r۲\u0019Q\u0001aL\ufffd\u000cv\ufffdX\ufffd\ufffd\u0005cu\ufffd\ufffd\ufffdf\ufffd\ufffd4͔Ԣ׽\ufffd\ufffd\ufffd\ufffd\u0011\ufffdH\ufffd\ufffdܿ\ufffdO\ufffd\ufffdѤ\ufffd\\D\ufffd\ufffd\ufffd\u003e\ufffd\ufffdt\ufffdxg\ufffd4\u0015\ufffd'\ufffd\u000f\ufffd\u0019(H\ufffdQT\ufffd\u0011G\u0005j\u0011Rg\ufffd\u0007\u001aW\t\ufffdp\ufffd\ufffd\ufffdv\ufffd\ufffd\ufffdt\ufffd\ufffd׾\ufffd\ufffd_\u0018\ufffd#\ufffd`\ufffdٶ\ufffd\u001f~\ufffd\ufffdks\ufffd\u000b\ufffd\ufffdȃ\ufffd\ufffd\u0004\ufffd\ufffd\ufffd\u00078\ufffd\ufffdb\ufffd\\;\ufffd\ufffd\ufffdm\ufffd\u0000\ufffd\u000e\u001b\ufffd8\u0003\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 420\r\nCache-Control: public, max-age=3600\r\nContent-Encoding: gzip\r\nContent-Type: application/javascript; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:33:46 GMT\r\nSet-Cookie: AWSALB=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:03:46+05:30","url":"https://ginandjuice.shop/resources/js/xmlStockCheckPayload.js","request":{"header":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; AWSALBCORS=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g","Referer":"https://ginandjuice.shop/catalog/product?productId=1","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"script","Sec-Fetch-Mode":"no-cors","Sec-Fetch-Site":"same-origin","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/resources/js/xmlStockCheckPayload.js","scheme":"https"},"raw":"GET /resources/js/xmlStockCheckPayload.js HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; AWSALBCORS=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g\r\nReferer: https://ginandjuice.shop/catalog/product?productId=1\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: script\r\nSec-Fetch-Mode: no-cors\r\nSec-Fetch-Site: same-origin\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Cache-Control":"public, max-age=3600","Content-Encoding":"gzip","Content-Length":"230","Content-Type":"application/javascript; charset=utf-8","Date":"Thu, 07 Sep 2023 15:33:46 GMT","Set-Cookie":"AWSALB=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/, AWSALBCORS=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffdU\ufffd͎\ufffd0\u0010\ufffd\ufffd}\ufffd\t\u00170dAo\ufffd z\ufffd\ufffd'Г\ufffd0)\ufffdn\u0003\ufffdM)(1\ufffd\ufffd\u001d]\u000e\ufffd\ufffdL~\ufffd\ufffd\ufffd)ݘ[!\ufffd\ufffd\ufffd\ufffdq\ufffd\u00045\ufffdhm\ufffd$zety\ufffd\ufffdi%D;j\ufffd7X\ufffd{\ufffdM֠\ufffd\u0015\u003c\u0004\ufffd\ufffd\ufffdAбu\ufffd\ufffde\"7\u0004q\ufffdl\ufffdu\u0002\ufffd\ufffdi\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdw!\ufffd},\ufffd\ufffd4x#\ufffd\ufffd?\ufffd\u001d\ufffd7k\ufffd\ufffd8עr`Z\ufffd\ufffd\"\ufffd\ufffd\u0014\r\ufffdji^\ufffd;\ufffdC;k\ufffd\ufffdK\u0015\ufffd\t\ufffd\ufffd\u0016\ufffd\ufffd\ufffd\ufffdG\ufffd)\ufffd\ufffd\ufffd\u001c\ufffd\u001d\ufffd\u001fK\ufffd\ufffde\ufffd\u003e\ufffdO\u0011?_\ufffd\ufffd3s\ufffdG\ufffdYR\ufffd\ufffd\u000b\ufffd\ufffd\ufffd\ufffdd\u0001\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 230\r\nCache-Control: public, max-age=3600\r\nContent-Encoding: gzip\r\nContent-Type: application/javascript; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:33:46 GMT\r\nSet-Cookie: AWSALB=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:03:46+05:30","url":"https://ginandjuice.shop/resources/js/xmlStockCheckPayload.js","request":{"header":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+; AWSALBCORS=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+","Sec-Fetch-Dest":"empty","Sec-Fetch-Mode":"cors","Sec-Fetch-Site":"none","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/resources/js/xmlStockCheckPayload.js","scheme":"https"},"raw":"GET /resources/js/xmlStockCheckPayload.js HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+; AWSALBCORS=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+\r\nSec-Fetch-Dest: empty\r\nSec-Fetch-Mode: cors\r\nSec-Fetch-Site: none\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Cache-Control":"public, max-age=3600","Content-Encoding":"gzip","Content-Length":"230","Content-Type":"application/javascript; charset=utf-8","Date":"Thu, 07 Sep 2023 15:33:47 GMT","Set-Cookie":"AWSALB=Og/rMkIG6TdnxmKZZY/dHf/nbCJoVtem7Xf2gR2asoHlHQ4cRDVDhCHAELX8TKOz9FBJ38LxoNlB7BQHKwTbaHIhExVtj6B34UaUrguamGInLdvBYBiSoYTHu7x3; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/, AWSALBCORS=Og/rMkIG6TdnxmKZZY/dHf/nbCJoVtem7Xf2gR2asoHlHQ4cRDVDhCHAELX8TKOz9FBJ38LxoNlB7BQHKwTbaHIhExVtj6B34UaUrguamGInLdvBYBiSoYTHu7x3; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffdU\ufffd͎\ufffd0\u0010\ufffd\ufffd}\ufffd\t\u00170dAo\ufffd z\ufffd\ufffd'Г\ufffd0)\ufffdn\u0003\ufffdM)(1\ufffd\ufffd\u001d]\u000e\ufffd\ufffdL~\ufffd\ufffd\ufffd)ݘ[!\ufffd\ufffd\ufffd\ufffdq\ufffd\u00045\ufffdhm\ufffd$zety\ufffd\ufffdi%D;j\ufffd7X\ufffd{\ufffdM֠\ufffd\u0015\u003c\u0004\ufffd\ufffd\ufffdAбu\ufffd\ufffde\"7\u0004q\ufffdl\ufffdu\u0002\ufffd\ufffdi\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdw!\ufffd},\ufffd\ufffd4x#\ufffd\ufffd?\ufffd\u001d\ufffd7k\ufffd\ufffd8עr`Z\ufffd\ufffd\"\ufffd\ufffd\u0014\r\ufffdji^\ufffd;\ufffdC;k\ufffd\ufffdK\u0015\ufffd\t\ufffd\ufffd\u0016\ufffd\ufffd\ufffd\ufffdG\ufffd)\ufffd\ufffd\ufffd\u001c\ufffd\u001d\ufffd\u001fK\ufffd\ufffde\ufffd\u003e\ufffdO\u0011?_\ufffd\ufffd3s\ufffdG\ufffdYR\ufffd\ufffd\u000b\ufffd\ufffd\ufffd\ufffdd\u0001\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 230\r\nCache-Control: public, max-age=3600\r\nContent-Encoding: gzip\r\nContent-Type: application/javascript; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:33:47 GMT\r\nSet-Cookie: AWSALB=Og/rMkIG6TdnxmKZZY/dHf/nbCJoVtem7Xf2gR2asoHlHQ4cRDVDhCHAELX8TKOz9FBJ38LxoNlB7BQHKwTbaHIhExVtj6B34UaUrguamGInLdvBYBiSoYTHu7x3; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=Og/rMkIG6TdnxmKZZY/dHf/nbCJoVtem7Xf2gR2asoHlHQ4cRDVDhCHAELX8TKOz9FBJ38LxoNlB7BQHKwTbaHIhExVtj6B34UaUrguamGInLdvBYBiSoYTHu7x3; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:03:46+05:30","url":"https://ginandjuice.shop/resources/js/stockCheck.js","request":{"header":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg; AWSALBCORS=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg","Sec-Fetch-Dest":"empty","Sec-Fetch-Mode":"cors","Sec-Fetch-Site":"none","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/resources/js/stockCheck.js","scheme":"https"},"raw":"GET /resources/js/stockCheck.js HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg; AWSALBCORS=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg\r\nSec-Fetch-Dest: empty\r\nSec-Fetch-Mode: cors\r\nSec-Fetch-Site: none\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Cache-Control":"public, max-age=3600","Content-Encoding":"gzip","Content-Length":"420","Content-Type":"application/javascript; charset=utf-8","Date":"Thu, 07 Sep 2023 15:33:47 GMT","Set-Cookie":"AWSALB=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/, AWSALBCORS=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdR\ufffdn\ufffd0\u000c\ufffd\ufffd+8]*\ufffd\ufffd\ufffdm7\u0017n\ufffd\ufffd\u001d6\ufffd\ufffda\ufffd\u000f(\u00163\u000bu\ufffd\ufffd\ufffd\ufffd\u0019A\ufffd}\ufffd\ufffd\u0006uS\ufffd\u0004\u000cX\ufffd\ufffd\ufffd\u0013\ufffdlh\ufffd\u001a=\ufffd\ufffd\ufffdn:\ufffd߯\ufffd\u000f\ufffdU\ufffd\ufffd\u003c.Zl\u001e\ufffd\ufffd~\ufffd\ufffd\ufffdX{\ufffd\ufffdӷ.\u0012z\ufffd\ufffd$-׎\ufffd\u000cV\ufffd7\ufffd\ufffd\ufffdX\ufffd\ufffd\u00048\u001ai\ufffd\u0017\nM\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdw\ufffdD\ufffd\ufffd\u001a\ufffd\rV\u00153x%i2\ufffd$=nA\ufffd_\u001b2\ufffd\ufffd(.2;\ufffd\ufffd\u001eE\ufffd5\ufffdL\ufffdH3\ufffd\ufffd\ufffd\ufffdI\ufffd\ufffd\ufffd\ufffd\ufffd\u0019l\u000c\ufffd3\ufffd\ufffdu\u0010\u0019|$\ufffd\ufffd\ufffd\u0001jЬ\u0000c\u0001\ufffd%\ufffd?\ufffdk\ufffd\ufffd:\ufffd+\ufffd\ufffd\ufffd\u000e\ufffd\nVHM\ufffd\u000f\ufffdD\ufffd0Av\ufffd\ufffd\ufffd5Gx\ufffd\ufffdb\u001f+\ufffd\ufffd\ufffd\"x\ufffd0\ufffd?\u000c\u001b\u003c\ufffd`\ufffd\ufffd\r۲\u0019Q\u0001aL\ufffd\u000cv\ufffdX\ufffd\ufffd\u0005cu\ufffd\ufffd\ufffdf\ufffd\ufffd4͔Ԣ׽\ufffd\ufffd\ufffd\ufffd\u0011\ufffdH\ufffd\ufffdܿ\ufffdO\ufffd\ufffdѤ\ufffd\\D\ufffd\ufffd\ufffd\u003e\ufffd\ufffdt\ufffdxg\ufffd4\u0015\ufffd'\ufffd\u000f\ufffd\u0019(H\ufffdQT\ufffd\u0011G\u0005j\u0011Rg\ufffd\u0007\u001aW\t\ufffdp\ufffd\ufffd\ufffdv\ufffd\ufffd\ufffdt\ufffd\ufffd׾\ufffd\ufffd_\u0018\ufffd#\ufffd`\ufffdٶ\ufffd\u001f~\ufffd\ufffdks\ufffd\u000b\ufffd\ufffdȃ\ufffd\ufffd\u0004\ufffd\ufffd\ufffd\u00078\ufffd\ufffdb\ufffd\\;\ufffd\ufffd\ufffdm\ufffd\u0000\ufffd\u000e\u001b\ufffd8\u0003\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 420\r\nCache-Control: public, max-age=3600\r\nContent-Encoding: gzip\r\nContent-Type: application/javascript; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:33:47 GMT\r\nSet-Cookie: AWSALB=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:03:48+05:30","url":"https://ginandjuice.shop/catalog/product/stock","request":{"header":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Content-Length":"107","Content-Type":"application/xml","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn; AWSALBCORS=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn","Origin":"https://ginandjuice.shop","Referer":"https://ginandjuice.shop/catalog/product?productId=1","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"empty","Sec-Fetch-Mode":"cors","Sec-Fetch-Site":"same-origin","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"POST","path":"/catalog/product/stock","scheme":"https"},"body":"\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\u003cstockCheck\u003e\u003cproductId\u003e1\u003c/productId\u003e\u003cstoreId\u003e1\u003c/storeId\u003e\u003c/stockCheck\u003e","raw":"POST /catalog/product/stock HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nContent-Length: 107\r\nContent-Type: application/xml\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn; AWSALBCORS=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn\r\nOrigin: https://ginandjuice.shop\r\nReferer: https://ginandjuice.shop/catalog/product?productId=1\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: empty\r\nSec-Fetch-Mode: cors\r\nSec-Fetch-Site: same-origin\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"23","Content-Type":"text/plain; charset=utf-8","Date":"Thu, 07 Sep 2023 15:33:49 GMT","Set-Cookie":"AWSALB=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb; Expires=Thu, 14 Sep 2023 15:33:49 GMT; Path=/, AWSALBCORS=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb; Expires=Thu, 14 Sep 2023 15:33:49 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd32\ufffd\u0000\u0000\u0003\u0004\ufffd\u001d\u0003\u0000\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 23\r\nContent-Encoding: gzip\r\nContent-Type: text/plain; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:33:49 GMT\r\nSet-Cookie: AWSALB=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb; Expires=Thu, 14 Sep 2023 15:33:49 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb; Expires=Thu, 14 Sep 2023 15:33:49 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:03:50+05:30","url":"https://ginandjuice.shop/catalog/cart","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Cache-Control":"max-age=0","Connection":"close","Content-Length":"36","Content-Type":"application/x-www-form-urlencoded","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb; AWSALBCORS=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb","Origin":"https://ginandjuice.shop","Referer":"https://ginandjuice.shop/catalog/product?productId=1","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"POST","path":"/catalog/cart","scheme":"https"},"body":"productId=1\u0026redir=PRODUCT\u0026quantity=1","raw":"POST /catalog/cart HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nCache-Control: max-age=0\r\nConnection: close\r\nContent-Length: 36\r\nContent-Type: application/x-www-form-urlencoded\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb; AWSALBCORS=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb\r\nOrigin: https://ginandjuice.shop\r\nReferer: https://ginandjuice.shop/catalog/product?productId=1\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"0","Date":"Thu, 07 Sep 2023 15:33:50 GMT","Location":"/catalog/product?productId=1","Set-Cookie":"AWSALB=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg; Expires=Thu, 14 Sep 2023 15:33:50 GMT; Path=/, AWSALBCORS=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg; Expires=Thu, 14 Sep 2023 15:33:50 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"raw":"HTTP/1.1 302 Found\r\nConnection: close\r\nContent-Length: 0\r\nContent-Encoding: gzip\r\nDate: Thu, 07 Sep 2023 15:33:50 GMT\r\nLocation: /catalog/product?productId=1\r\nSet-Cookie: AWSALB=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg; Expires=Thu, 14 Sep 2023 15:33:50 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg; Expires=Thu, 14 Sep 2023 15:33:50 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:03:51+05:30","url":"https://ginandjuice.shop/catalog/product?productId=1","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Cache-Control":"max-age=0","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg; AWSALBCORS=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg","Referer":"https://ginandjuice.shop/catalog/product?productId=1","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/catalog/product","scheme":"https"},"raw":"GET /catalog/product?productId=1 HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nCache-Control: max-age=0\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg; AWSALBCORS=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg\r\nReferer: https://ginandjuice.shop/catalog/product?productId=1\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"2459","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:33:51 GMT","Set-Cookie":"AWSALB=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB; Expires=Thu, 14 Sep 2023 15:33:51 GMT; Path=/, AWSALBCORS=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB; Expires=Thu, 14 Sep 2023 15:33:51 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdZ\ufffdr\ufffd6\u0012\ufffdާ@y\ufffd(\ufffd1ES\ufffd\ufffd\ufffd\"\ufffd\ufffd\ufffdiz\ufffdc\ufffdu\ufffd\ufffdݗ\u000cDB\u0014b\ufffd`\u0008P\ufffd2\ufffdB\ufffd\u001a\ufffdd\ufffd\u0000H\ufffd\ufffdH\ufffd2\ufffd\ufffd~\ufffd\ufffdc\ufffd\ufffd\ufffd\ufffd\u0002\ufffd\ufffdbw\ufffdѷ\u0017\ufffd\ufffd\ufffd\ufffdu\ufffd\u001a\ufffddļoF\ufffd\u000b\ufffdg4#80?\ufffd#\ufffd\ufffd\u001d\ufffd\ufffdd:vR\"x\ufffd\ufffdD8\u000cOT3\ufffd:\ufffd\u0010\ufffd\ufffdq\ufffd\ufffd\ufffd\ufffd\u000b\ufffdP\ufffdR\ufffd\ufffdB.\u0019\u00113Bd\u0013\ufffd\ufffd\u0000@q\u000b0\u0011\ufffd\u000f\u0010\u0011\ufffdQ\ufffd#2\ufffd\ufffd\ufffd,\u0012\ufffdJ\u000b\ufffd\u003c\ufffd$\ufffdckA\u00039\u001b\u0007dN}b\ufffd\ufffd#\ufffd\t\ufffd\ufffd !\ufffd\ufffd\ufffd8\ufffdV\tM\ufffd)M$\u0012\ufffd?\ufffdJ\u0002}\u0012\ufffd\ufffd}\ufffd\u0007$\ufffdx\u0012\u0001x\ufffd\ufffd\ufffd\ufffd\ufffdcz\ufffd\ufffd\ufffd\u0003\u001e\u001d\u0000#\ufffd\t\ufffdL\ufffd{\ufffd|\ufffdslJ\ufffd*t\u001c\ufffd\u0019\ufffd\ufffdG\ufffd~n?\ufffdC\ufffdT2\ufffd\ufffdИ\ufffd$a\u0004\ufffd\u000e\ufffd\ufffd\u003cF\ufffdܿ\ufffd\ufffd2d\ufffd\ufffd\ufffd\u0007\ufffd/\ufffd\ufffd\u001b\u001a\ufffd'8J^\ufffd_2X?t;\ufffd\ufffd\ufffd1\u0010\ufffd\u0019Κ\u001a\ufffd\t\u000f\ufffd(\u000em\u0000.\r\u0018\ufffd9\ufffd\ufffd\ufffd*3\ufffd\ufffd\ufffdf\ufffd\ufffd\ufffdB\ufffd\u000c\u000b1\ufffd\u000c\ufffd\ufffd\ufffd\u0018\ufffd\ufffdf\ufffd\ufffd\ufffd\ufffd얩\ufffd\ufffd\u0019\u0015\u0008\ufffd0\n\u0008\ufffd\u0013\ufffdbI\ufffd\u0012\ufffd3\u0016\ufffdo\ufffdp\ufffd \u0013\ufffd\u0026O}\ufffdG\ufffdqh\u0018\ufffd\u0000My\ufffd$\u0011\ufffdơj\ufffd$\ufffd\ufffd\ufffdeё2*\ufffd\ufffdHM\u0005\ufffdD\ufffdR\ufffd\ufffdH\ufffdj*\ufffd\ufffd\u0016\ufffdqāt\ufffd\ufffd$U\ufffd\ufffd\u0003\ufffd\ufffd\ufffdx\ufffd\ufffd1rW\ufffd|\ufffdY;ب\ufffd\ufffd\ufffd\\;4\ufffdE5\ufffd\ufffd\ufffd꓍\ufffdY2\ufffd\ufffd\u0014=-\u000f\ufffd\ufffd\t*\ufffd\ufffd\ufffd8\ufffd|\u003cy\ufffd\u0018\ufffdWk\ufffd\ufffd䓧\ufffd\ufffd\ufffd\ufffd\ufffdɛS\ufffdG?Q\ufffd\ufffd\u003e\t\ufffd0\ufffd/\ufffd\ufffd޳ը\ufffd`%3\ufffd\u0003\ufffd\ufffd\u0004\ufffd\ufffdD;e\ufffd\ufffd\ufffdaEى\ufffd\ufffdd\u0012-\ufffd\ufffd(\ufffd\u0007$9\ufffd3\ufffd\ufffd\ufffd[\ufffd\u003c,\u0005]\ufffd-\ufffd@Cú\ufffd\\\ufffd\u0019ھ\ufffd\u0003\ufffd*`\u0015\ufffdMSer0\ufffdUZ\ufffd\ufffd\u00116V\ufffd\ufffdT\ufffdT\ufffd\tm\ufffdl\ufffdC\u000ez\u000c\ufffd5\ufffd\u000e\ufffd\ufffdXCi\ufffd\ufffd\u001b\ufffd\ufffd}b\\3̪E\ufffd\njC[\u001ajNع\ufffd*\ufffd\u000b\ufffd\ufffd4\ufffd\ufffd\ufffd\u003cU(\ufffd\u000e$\ufffd͍tC\\\u000c=ɤ\ufffd-R\u000c\ufffd%\t,\ufffd\ufffd\ufffd\u0003\u0014Ű`@\u0011c\ufffdD\ufffd\ufffdl\ufffd:m$x\ufffd\ufffd+\ufffd\u0026Z\ufffdW\ufffd\ufffdO%\u0016\ufffd\ufffdLZ޵R(\ufffd\ufffd\ufffd#\t7r2֙K\ufffd\ufffd\u003c\u000e\ufffd2\ufffd\ufffd\ufffd\ufffd,\ufffd6\ufffdK\ufffd\u0012-\ufffd\ufffd\u001c\ufffdQ1\u000f\ufffd\u0011\u0018\ufffd\ufffdr}\ufffd\u0002\ufffdC\ufffd\u0005]\ufffd\ufffd}h\ufffd\ufffd\rL\ufffd_\ufffd\ufffdNc\ufffd\ufffdfy\ufffd\ufffdi\ufffd\ufffd\ufffdi\ufffdN\ufffd\u000e\ufffd\ufffd_\ufffd\u0010Ѹ\ufffd\ufffd\u001a\ufffd\u0015k\ufffd\ufffd\ufffd\ufffd\ufffd(\ufffd\ufffd\ufffdDo\ufffdv{\ufffd\u0007\u0010\ufffd\ufffd\ufffd\u0016[s\ufffd\ufffd\ufffd\u001a\ufffdWu\u0004\ufffd\ufffdQؒ\ufffd\ufffd\ufffd\ufffd\ufffd\u001a\ufffdp޶\ufffdrI\ufffdm\ufffdÐ\u0011\u0018\ro\ufffd\ufffd4\u000c\ufffd\ufffdhf\ufffd\ufffdL\ufffd\u0003\ufffd\ufffd\ufffd*\ufffd\ufffd\ufffd\u0003\ufffd\ufffd\u0008\ufffd(\ufffdp,\ufffdZ\ufffd\ufffd\u0019\u003c\u0016\u0015\ufffd\ufffd\ufffd\ufffd\"\u0002\ufffda'\ufffd\ufffdj\ufffd\ufffd#8\ufffdW'y\ufffd[\ufffd\u003e\ufffd\ufffdU;\ufffd\ufffd\ufffd\u0004\ufffd\ufffd\ufffd~\ufffd\ufffd\ufffd]\ufffd\u003e\ufffdt\ufffd;ǹ9\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdڵ}\ufffd@\ufffd\u0013\ufffdi\u0016M\ufffd\ufffdp\ufffd\n\ufffd\ufffdd9\ufffd\ufffd3\ufffdH\ufffd\ufffdn\ufffd\ufffdt\u0001\ufffd\u0001\ufffd\u0003\ufffd\ufffd\ufffd\ufffd\\\ufffd޴\ufffd\ufffd\ufffd\ufffdQ\ufffd\ufffd\ufffdZ\ufffd(\ufffd³\ufffd\ufffd\u0016\ufffd\ufffd~p\ufffd\ufffd\ufffd\ufffd-\n\ufffd\ufffd?\n')\ufffd\ufffd\ufffd\ufffdy\ufffd\ufffd8\ufffd'W\ufffd\ufffd\ufffdR\ufffd\ufffdp\ufffd\ufffdB\ufffd\ufffdp\ufffdz\ufffdr\u001c\u0007N-\ufffd(Ē\ufffd\ufffd_u\ufffd\u0016\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdq\u000b;[\u001d\ufffd\ufffd\ufffdS\ufffd\ufffd\ufffd*ȭ\ufffd\ufffd̰\ufffdJ\ufffd\u0018mc\ufffd\ufffd#\ufffd\ufffd\ufffd\u000cO\u0008\ufffd.֝\ufffd1rLYs\ufffdĻ\ufffd\ufffd\ufffdƘA\ufffd\ufffd\u0011\ufffd\u0011\ufffd\ufffd\u0000\ufffd\ufffd\u000c\ufffd\u0008\ufffd\\\ufffdP\ufffd\u00108\u000ch\ufffd\ufffd;4Y\ufffd\ufffd'*U\u0002\ufffde\ufffd\ufffd\ufffd\u0008\ufffd?\u0014\ufffd\u00054'\u0011\ufffd\ufffd\ufffd\u000b*g\ufffd)\ufffd\ufffd4ͨD\ufffdT\n\ufffd\u0008\ufffd\ufffdYeI(\ufffd\ufffdYJT(\ufffd\u0002\ufffdM|\ufffd\ufffd)\u000b\ufffd\ufffd\ufffd\u000cK\u0014p\"\ufffd\ufffdD\u0012\u000b\ufffdg'DJ0\ufffdz\u0014%\ufffd\ufffd\u0000\r\ufffdH\ufffdp\u0010\ufffd\ufffdG\u0018\ufffd\ufffd\u0017\ufffd#\ufffd@z\ufffd!\ufffd\ufffd\u0015*\ufffd\ufffdp\u00060\n\ufffd\ufffd\u0004\ufffd\ufffd\ufffd\ufffd\u0016j@\ufffd\ufffd\ufffdD\ufffd\ufffd \ufffdL\ufffd\ufffdTT\u001c\u001a\ufffd`.R.Q\ufffdy\u0000\ufffd\u003c\u000bg}\u001d\u0012\ufffd\u0002\u000eN\ufffd\ufffd]\ufffd\ufffd w\ufffdn\ufffd\ufffd\ufffd¯\u0017ߡ\ufffdW\ufffd\ufffd̝y\u0001\ufffd\ufffdg\ufffd3\ufffdߵ\ufffd\u0005\ufffd+2\ufffd#\ufffd\ufffd\\u\ufffd\t\ufffd,\ufffd\ufffd\ufffd—\ufffd\ufffd\\\ufffd\u001d\ufffd\ufffdB\u0011\ufffd)B\ufffd\ufffd\ufffd\ufffd\ufffd-\u0015oD\ufffd\u0004\ufffd2%\ufffd3\ufffd\u0002\u0001L\ufffdmFa'\ufffd\ufffd2\ufffd\ufffd|\ufffdB\ufffd9\ufffd,\ufffd\u0012\ufffd-\ufffd\tUs\u001c\u0015f\u0011@9\ufffd5\ufffdZ/\ufffd\ufffd\"\ufffd\ufffd〃onj\u001e\u000c5\u0000\ufffd\u001b\ufffdR\ufffd\u0019i\u0008H\ufffd(\ufffd\u0007ʤ\ufffd%\ufffd6-[硿\ufffd\u001e\ufffdM@ݭ\ufffd\ufffd\ufffd\ufffdLA\ufffd\u000b#\ufffd\u0014\ufffdq]\u0015\ufffdښ\ufffdMV\ufffdFDƤ\ufffd\ufffd6\ufffd\ufffd)\ufffd\ufffd\ufffdݮpo\ufffd\ufffdq\u001c\ufffd\ufffdr\u001f\ufffd\ufffd\u0016\ufffd\u0000̃\ufffd\\G\u0010\ufffdf\ufffdWs\ufffdz\ufffd\ufffd9\ufffdUJ^\ufffd\ufffdFhS(\ufffd\ufffd\ufffd\u000ejm\ufffd\ufffd(\ufffdJ\ufffds\u001a\ufffd\ufffd\ufffd\u000b\ufffd4\ufffd1o~\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdĹ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd9ñ\ufffdr\ufffd[\ufffd\ufffd\ufffd)ʻؒ\"\ufffd\ufffdB\ufffd\ufffd\ufffd\ufffd\u001b\u003c\ufffd%\ufffd\ufffd\ufffdAN,\ufffd\ufffd3ȩ\ufffd\ufffdv\u0006\ufffd\ufffd\ufffd\ufffd\ufffd\u000c\ufffd\ufffd\ufffd\ufffdw\u0006yay/:\ufffd\ufffd`y?t\u0006q\ufffd-\ufffd=\ufffd\u000e\u0003\u0007\ufffd۝\ufffd.\ufffd\ufffd\ufffd\ufffd[\u0017\ufffd\ufffdvg\ufffd\u000b\ufffdu\ufffds\ufffd\u0005\ufffd\ufffd\u0007\ufffd\ufffd\ufffdal\ufffd\ufffd\ufffdl\ufffd\ufffd\ufffdY\u0010(\ufffdX\ufffd\ufffd\ufffd=\ufffd\u000f\u003e\ufffd\ufffdͰ}\ufffd\ufffdT\ufffdj\ufffdL\ufffd\ufffd\u0001\ufffd\ufffd\u000c\u001b\u0012dMB\ufffd\ufffd婼\ufffd\ufffd\ufffdUw\ufffdUJ\rM9\ufffd\u0000\ufffd^\ufffd\u0010pT瑶R\u0018\ufffdC]\u001eh\u0017\ufffd\ufffd\ufffdv\ufffd꧴\ufffd\ufffd\ufffd\u0017\ufffd\ufffd\ufffd\u0011\ufffd\ufffdwE\ufffd\u00109ET\ufffd+^\ufffdn\ufffd\ufffd{ʑ\ufffd\u0010\u0015č\u001chU\u000f\ufffdx\ufffdp\u0008\ufffdV\ufffdق\u0006\u0004\ufffd$\\\u0016\ufffd1\ufffd\ufffdTGL* \ufffd\u0001\u001f\ufffd\ufffd\u0018\u0002e_\u001c\ufffd0\ufffd\ufffd\ufffd,\u0013t\u000e#\ufffd\ufffd\ufffd\u0008L\u00031\ufffd\ufffd\u001e\ufffd4\ufffd\u000ej\ufffd\ufffd\n\u0004\u000b\u0002O\u003eg\u0010\ufffd\ufffdT\ufffdӄ\t5#\ufffd\ufffd\ufffd{ë\ufffd\u000f\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd^\ufffd\u0005xq\ufffd\u0016^\ufffd\ufffd\u0012\ufffd\ufffdk\ufffd\ufffd^\u0005T\u0001P\ufffd.\ufffd\ufffd\ufffd\u000b\ufffd\u0017\ufffd\u0004Zk\ufffd\ufffd\ufffdM\ufffd\u0004\ufffd6e\ufffdm1?\u0013\ufffd}2\ufffd,P\u0017߯u\u00118\ufffd0ۦ\ufffd\ufffd6њ/\ufffd\ufffd*P{\ufffdk\ufffd\ufffd%\ufffdE\ufffd\ufffdW\ufffd\ufffd\ufffd\ufffd*\ufffd;\ufffd\ufffdno\ufffdr\u001d\\}Β\ufffd\ufffdrK\ufffde\u0010J\ufffdϻ-V\ufffd\ufffd\ufffd5'\ufffd\ufffdZ\ufffd\r\ufffdy\ufffd\ufffd\ufffd6%MBn\ufffd~u\ufffdz\ufffdn\ufffd\ufffdϸ \ufffd\ufffd\u0004\ufffd\ufffd\ufffd\ufffdwy\ufffd\ufffdƾ\ufffd\"=\u0005m\ufffd\ufffd3\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd4\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0006\ufffd߁\ufffdN\ufffd\ufffd\u0004K\ufffd\ufffd\u0008A\ufffd\ufffd:\ufffdU\ufffd'پ\ufffd\ufffd\ufffd\u0008\ufffd|o\ufffd\ufffdz\ufffd\ufffd68\u0010w\ufffdo\ufffdA*%\ufffd\ufffdAsvs\u00036߁\u0002:\ufffd]m\ufffdJh\ufffd\ufffd\ufffd\u0018(5\ufffd?`G6֦\ufffd:\u0000\ufffd$\ufffd\ufffd\ufffd\ufffdY[kV\ufffdB\ufffd\ufffd\ufffd3\ufffd;#\u001d\ufffd4\ufffdΒ\ufffd-UJK 3\ufffdr*t\ufffdM\ufffd\ufffd\ufffd\ufffd\ufffduT؈\u0026\u0004\ufffd\u0019\ufffdVE\ufffd\ufffd\u0016\u003cUo\ufffd5\ufffd\ufffdZ\nդ\ufffd\ufffdQ\ufffd\u0006K\ufffdu\ufffd\ufffd֥*\ufffdw\ufffdz\ufffd\ufffd\ufffd\ufffdB_\ufffd\ufffd7DZ-\ufffdN\ufffd\ufffd\ufffd\ufffdhp\u003c\u0018\ufffd\u001b@\ufffd5\u0008\ufffdR\u0006\ufffd\u00163;\ufffdf\ufffdf\ufffd\ufffdLk\ufffd\ufffd\ufffd\ufffd\ufffdJ\ufffd\u0004f\ufffd8\ufffd-\ufffdZ\ufffdg\ufffdp\u0017\ufffd\ufffdXr\u001fv\ufffd\ufffd\ufffd僦\ufffd\ufffd뵚\ufffd\u0017\ufffd\ufffd\ufffdj*k\u000f\ufffd\ufffd\ufffd\ufffd\u0015\ufffdn\ufffd\ufffd\u001ek\ufffdS\ufffdd\ufffdI5/\ufffdp*\ufffd`\ufffd}\u0003g\ufffd~\ufffd\ufffd\ufffdf\u0001L\\w,\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2459\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:33:51 GMT\r\nSet-Cookie: AWSALB=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB; Expires=Thu, 14 Sep 2023 15:33:51 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB; Expires=Thu, 14 Sep 2023 15:33:51 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:03:58+05:30","url":"https://ginandjuice.shop/catalog/subscribe","request":{"header":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Content-Length":"69","Content-Type":"application/json;charset=UTF-8","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB; AWSALBCORS=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB","Origin":"https://ginandjuice.shop","Referer":"https://ginandjuice.shop/catalog/product?productId=1","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"empty","Sec-Fetch-Mode":"cors","Sec-Fetch-Site":"same-origin","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"POST","path":"/catalog/subscribe","scheme":"https"},"body":"{\"email\":\"eqeq@gfmail.com\",\"csrf\":\"GQrKlppDdKQale2DNpk4mASSUzOdNqup\"}","raw":"POST /catalog/subscribe HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nContent-Length: 69\r\nContent-Type: application/json;charset=UTF-8\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB; AWSALBCORS=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB\r\nOrigin: https://ginandjuice.shop\r\nReferer: https://ginandjuice.shop/catalog/product?productId=1\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: empty\r\nSec-Fetch-Mode: cors\r\nSec-Fetch-Site: same-origin\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"68","Content-Type":"application/json; charset=utf-8","Date":"Thu, 07 Sep 2023 15:33:58 GMT","Set-Cookie":"AWSALB=2SKCWqUz3j0AasFKb1dBCnRQrU3mM16R6pX5fClkcKjvp9b8dRFcFCYjXp5pHaVHN6uVcVD0B4tt7MAs81yd5Unvr/oSF0i53t8n7wjb/pw+/XRO6yEaCLu2sCW1; Expires=Thu, 14 Sep 2023 15:33:58 GMT; Path=/, AWSALBCORS=2SKCWqUz3j0AasFKb1dBCnRQrU3mM16R6pX5fClkcKjvp9b8dRFcFCYjXp5pHaVHN6uVcVD0B4tt7MAs81yd5Unvr/oSF0i53t8n7wjb/pw+/XRO6yEaCLu2sCW1; Expires=Thu, 14 Sep 2023 15:33:58 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdVJ\ufffd/-\ufffd\ufffdS\ufffdR\ufffd\ufffd\ufffdS\ufffd\n\ufffdtv5\ufffd0\u0008P\ufffdQJ\ufffdM\ufffd\ufffd\u0001\ufffd\ufffd\u0016\ufffd\u0016:\ufffd\ufffd\ufffdxz\ufffd\ufffd\ufffdJ\ufffd\u0000-/{\ufffd4\u0000\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 68\r\nContent-Encoding: gzip\r\nContent-Type: application/json; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:33:58 GMT\r\nSet-Cookie: AWSALB=2SKCWqUz3j0AasFKb1dBCnRQrU3mM16R6pX5fClkcKjvp9b8dRFcFCYjXp5pHaVHN6uVcVD0B4tt7MAs81yd5Unvr/oSF0i53t8n7wjb/pw+/XRO6yEaCLu2sCW1; Expires=Thu, 14 Sep 2023 15:33:58 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=2SKCWqUz3j0AasFKb1dBCnRQrU3mM16R6pX5fClkcKjvp9b8dRFcFCYjXp5pHaVHN6uVcVD0B4tt7MAs81yd5Unvr/oSF0i53t8n7wjb/pw+/XRO6yEaCLu2sCW1; Expires=Thu, 14 Sep 2023 15:33:58 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:04:03+05:30","url":"https://ginandjuice.shop/blog","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=2rYKe8I1LwfOtI/423ImCGauGOtHRl2cXGdbbyWcVzzI5FqsI0s8Rij7fJUDNtQn4HPmweWUufw1GsgLXkGVie58e4PGUBD4je7UWtZobziKhxVouiWJeEFiNMkR; AWSALBCORS=2rYKe8I1LwfOtI/423ImCGauGOtHRl2cXGdbbyWcVzzI5FqsI0s8Rij7fJUDNtQn4HPmweWUufw1GsgLXkGVie58e4PGUBD4je7UWtZobziKhxVouiWJeEFiNMkR","Referer":"https://ginandjuice.shop/catalog/product?productId=1","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/blog","scheme":"https"},"raw":"GET /blog HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=2rYKe8I1LwfOtI/423ImCGauGOtHRl2cXGdbbyWcVzzI5FqsI0s8Rij7fJUDNtQn4HPmweWUufw1GsgLXkGVie58e4PGUBD4je7UWtZobziKhxVouiWJeEFiNMkR; AWSALBCORS=2rYKe8I1LwfOtI/423ImCGauGOtHRl2cXGdbbyWcVzzI5FqsI0s8Rij7fJUDNtQn4HPmweWUufw1GsgLXkGVie58e4PGUBD4je7UWtZobziKhxVouiWJeEFiNMkR\r\nReferer: https://ginandjuice.shop/catalog/product?productId=1\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"2646","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:04 GMT","Set-Cookie":"AWSALB=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2; Expires=Thu, 14 Sep 2023 15:34:04 GMT; Path=/, AWSALBCORS=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2; Expires=Thu, 14 Sep 2023 15:34:04 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdZ\ufffdn\ufffd8\u0016\ufffd\ufffd\ufffd`\ufffdئ\u0005b{\ufffd\ufffd\ufffd\u0005j{п\ufffd\ufffdh\ufffd\ufffd\ufffdآ{S\ufffd\u0012m1\ufffdH\ufffdH\ufffd\ufffdݾ\ufffd\u003e¾\ufffd\u003e\ufffd\u003e\ufffd~\ufffd\ufffd\u001d\ufffd_i\u001b\ufffd\u0017S\u0014\ufffdD\u001e\u001e\u001e\ufffd|\u003c\ufffd\ufffdދw\ufffd?|z\ufffd\ufffd\ufffd.S\ufffd\ufffd\u0006\ufffd\ufffd\ufffd\ufffd \u0015\u003c\t\ufffd\ufffdUI}\ufffd\ufffdBL\ufffd\ufffdBXS\u0016\ufffd\ufffd=\ufffd\ufffdD\u0026\ufffd^lm\ufffd\ufffd\\_\ufffd\ufffd}C\u0017\r\ufffd\u0010jh]\ufffd\ufffdM\ufffdp\ufffd\ufffd\u0011\u000b0\ufffdϔ\ufffd~\ufffd\ufffdKH\ufffd\ufffd\ufffd\u000c2\ufffd8\ufffd\u003c\u0013\ufffdh\u0026\ufffd\u003c7\ufffd\ufffdXl\ufffd\u0013\ufffd\r\ufffd\ufffdL\\:L\ufffdLƢ\ufffd_\ufffdYiE\ufffd\ufffd\u00021\ufffd\u0012Cm\ufffd\u00067\u001b\u00172w\ufffd\u0016\ufffd0j\u0008te\ufffd\ufffdc\ufffd\u0005'\ufffdL\ufffd\ufffdy\ufffd\ufffdF\ufffdA/\ufffdhϢ\ufffd\ufffd\ufffd\u0016l\\\ufffdceN|q\ufffd+\u003e\ufffd\ufffd5\ufffdƝ\ufffdi\ufffdx\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd.\ufffdN:%F\ufffd\u0016\ufffda\ufffd\ufffdf\ufffdy\ufffd?a\ufffdK\ufffd\u000f\ufffdLM\u003e\ufffd\u0005\ufffd\u0000\ufffd\ufffd\rr\u0006c\ufffdTLO;\u003c\ufffd\u001b\u000c\u00139c2\u0019FM\ufffd464,E\ufffdN\u001a\ufffdbŭ\u001dF\u0001f\ufffdD\u0004\ufffdг6\ufffd\u000f\ufffd7\ufffd\ufffd߇TZ\ufffd\ufffd\ufffd%Bɱ(\ufffd\u0013\ufffdb\ufffdRi\u003cC\ufffdl.\ufffd\u000c2*\u0019s?+\ufffd\ufffdS-\u001261\u0005s\ufffd:\ufffd\ufffdDt_\ufffdm\ufffdd1P*\ufffd\ufffd\ufffdDKA\ufffd\ufffdn\u0015``s\ufffd\\\n\ufffd7bFg\u0006\ufffd\ufffd2EA\ufffdӉ\ufffdw?\ufffd\ufffdgC\ufffd_v\ufffd\ufffdj\ufffd\ufffdJo\ufffd\ufffdf\ufffdi\ufffd]t\ufffdU7\ufffd\ufffdV\u0006\ufffd\ufffd0\ufffd\u0013\ufffd\ufffd9\ufffd\ufffd\ufffd\ufffd9\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdC\ufffdL\ufffd[CƝ\u0019?8zs\ufffd\ufffd\ufffd\ufffd\ufffd#\u0015g\ufffdI\ufffd\ufffdK\ufffd\ufffdR*\ufffd\u000f\ufffdG\u000f\ufffd\ufffd*\ufffdIf\ufffdA8\ufffd`\ufffd\ufffd6\ufffdN\ufffd\ufffd\ufffdni;\ufffdF\ufffdK\ufffd*\ufffd\ufffd\ufffdz`\ufffd0\ufffd\n\ufffd\ufffd{\ufffd4\ufffd\u000b\\zkx\u0001a@\ufffd^,\u001e\u0005\ufffd\u003e\ufffd\u0013\u001dmaK\ufffd\u000e\ufffddR\ufffd\u0004\ufffdv\\\u000ex\ufffdbG\ufffd-l\ufffd\ufffd\ufffd޴up\u0010\r\ufffd)\ufffdw\ufffd\ufffd\ufffd\u001d\u001d7\ufffd\ufffd\ufffd\ufffd\u001c\ufffdϏ\ufffd|\ufffd4K\ufffdR-\ufffd\rZ9\ufffd\ufffd\ufffd\ufffd\u0007\ufffd\ufffdnY^Ȍ\u0017Ux\ufffdrp7X*y\ufffd\ufffd\u0013\ufffd\ufffd\ufffd\ufffd\ufffd9\u0018\ufffd\ufffd\ufffdQ\u000f\ufffd\ufffd\ufffd' \ufffd0I\u0019;\ufffdsSV\ufffd\ufffd\ufffdL\ufffd\ufffd\ufffd1\ufffd\ufffdD\ufffd\u0014s\ufffde$\ufffd\ufffd#ȷ\u0014\ufffd\ufffdM\ufffd\ufffd\ufffd;:P\ufffd\u0014\ufffdw\u0012n\ufffd+\ufffd7c\t\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdؔ\ufffdud\ufffdؕ\ufffd\ufffd\ufffd\ufffdp\ufffdv6\u001d\r`\ufffd\u0016\ufffd7n\u0015P\ufffd\ufffd\u000f\ufffd.軠\ufffd\ufffd\nO\ufffd_\u000c\u0026oK|\ufffdlo\ufffd=\ufffd\u0008o\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd7\ufffd\u0002\ufffdn-\ufffd\ufffd\ufffd\n\ufffdw+\ufffdE\ufffd\ufffd\ufffd;\u0012\ufffd0\ufffd\ufffd\ufffd\ufffd\n\ufffdƼXCkm\u0001{\ufffdCx%\u0017د]aK\ufffd\ufffdP\u000f\ufffd\u001b\ufffd\ufffd\ufffdm\ufffd%7\u000e~Ǚ\ufffdT\t\ufffd\ufffdWZ\u000b9\ufffdR\ufffd\u0018V\ufffd\\\ufffd\ufffd\u0017\ufffd\ufffd\ufffdVi\ufffd\ufffd\ufffd\u0000w\ufffd\ufffd-\ufffd[\u0002\ufffd5*\ufffd\ufffd\u0011\ufffdPJ\u0012\ufffd\ufffd\ufffdp8\ufffd\ufffd^:\ufffdm\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdt\u0008\ufffd;9\ufffd\ufffd\ufffdd\u000e\ufffd\ufffd]n\ufffdqrR\ufffdõ\u0005\ufffd5\ufffd\u0017\ufffdw\ufffd\ufffd\ufffd\u000e\u000e\u000f;\ufffd\ufffd\ufffd\ufffd\ufffdh5?ID\ufffd\u000b\ufffd!39jd\u0026{\u0006X\ufffd\ufffd8\ufffd\ufffd\u0002N\ufffdFm\ufffdze_\ufffd\ufffd=`@:\ufffd\ufffd\ufffdА\ufffdw\ufffd=\ufffd\\25\ufffd\ufffd\ufffd\ufffd\u000f\u0007`\u0026u^ֹ\u0019\ufffdf,W\u003c\u0016\ufffdQع\ufffdѥ\ufffd\ufffdǬĶ\ufffd\ufffd\u001e\ufffd\u0004u]\ufffd\u0026\ufffdT\u0026\ufffdЁn\ufffd\ufffdk6\ufffd\ufffd\u0014uX\ufffd;\ufffd\u0019\u0007uL\ufffd9\ufffdr\ufffdIW\ufffdB\ufffd\u0018\u0005\ufffd\u0006\ufffd\ufffdu\ufffd\t\ufffd]\ufffd\u0005\ufffd\ufffd\u0011\ufffd\ufffd{\r\ufffd$7ܺu{doB\ufffd\ufffd\ufffdf?=o\u0006[=\ufffd\ufffd\ufffd\ufffd\ufffd'\ufffdSX\u0012\ufffdM\ufffd\ufffd\ufffd[\ufffd^\ufffd\ufffdohm\ufffdQ\ufffd*\ufffdF\ufffd\ufffd\ufffd =\u0019=e\ufffdsYT\ufffd\u0005G\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdaI\ufffd$\ufffd\u001e{\ufffd\u000be,KE!\u0018\ufffd\ufffd\ufffdv\ufffd9\ufffds\ufffd|\n#\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdS\ufffdX\ufffd\u003cUPj\ufffdD\ufffd\ufffdB\u0001\ufffd\ufffdO\u0006\n\ufffd\ufffd\ufffd\ufffd.\ufffd\u003e\ufffd|,\ufffd\n\ufffd\ufffdW\u000bvr\ufffd:F9%\ufffd\ufffd\ufffd1Ȕ\ufffdr\ufffd\ufffd\ufffd;a\ufffd\ufffd:\ufffd\ufffdX\ufffdI\ufffdLX\u0004\ufffd]vi\ufffd\u000fx\ufffd\u0018\u0019t\ufffd\u001a-X̕\u0002B7\ufffd\ufffd\ufffdm_\ufffd}a|l\ufffd\ufffd\ufffd\u003eu\ufffd]\ufffd9\ufffd\ufffd\ufffd\ufffd\ufffd;\ufffd\ufffd\ufffd\ufffd\u000f!g\ufffd\u0010r\ufffd\u0012!\u001f\ufffd\ufffd\ufffd\u0026\ufffd\ufffd\ufffd\u0014\ufffd\u001c\ufffd\u0008B\u00162\ufffd\ufffd\ufffd\ufffd{d\ufffd\u0005\ufffd(\u0005\ufffdq\ufffd~\ufffd\\\ufffdj\u001d\u0019\ufffdK\ufffd\ufffd\u000c\n2\ufffd\ufffd\u0000\ufffdt\ufffd\ufffd\ufffdHk\ufffd'K\ufffdL\ufffd\ufffd\ufffd\u001d\ufffd\ufffdf\u000e8\u0019F9r\ufffd\ufffdj\u003cI\ufffdLI\ufffd\ufffd\ufffd\ufffd\ufffdz\u000e4\ufffd\ufffdע˞\ufffdj\ufffd+\u0010\ufffd\u0005\ufffd\u0000uW\ufffd\ufffd\u0003 9\ufffd\ufffd\u0017\ufffd\ufffd\u0001\ufffdㅠ\u0004\ufffd\ufffd\ufffd\ufffd*\ufffd\u0000Ew\u0000\ufffd\ufffd\u001f\u000f:\ufffd[A\ufffd\ufffd-t\n@\ufffd\ufffdq\u001b\ufffd\u0002\ufffdU\ufffd\ufffdL\u001b\ufffd\u001e*}ivn\u0015\ufffd\ufffd\ufffdD\ufffd\ufffdQ\u0018 \ufffd\u003cS\u0004C\u0003\u0013Q\u0008\ufffd:`(\ufffd\ufffd֡\ro4\ufffd\ufffd\ufffdN\ufffd\n(,\ufffd\ufffdMq\r\ufffd\u0000@\r\ufffdd\u0015\ufffd\ufffd\ufffdZ\u0018\ufffd\ufffdS\u001d\u000e\ufffdx\ufffd\u00263\ufffd\u0015\ufffd\ufffdA\u000fH\ufffd\u0014S\u003e\u0013`}\u0007\u0018y\ufffd\ufffda\ufffd\ufffd\u0015F\u001e\ufffd¼|He\ufffd\ufffd\ufffd\ufffd\u0010\ufffd\ufffd\u0013JM\u0026\ufffd\u000by\ufffd\ufffdy\ufffdpx\ufffdo^I\ufffd+\ufffdֲ\ufffd,\\J6\u0006ˆ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd%A\ufffd\ufffd}\u0012\ufffd\ufffd\u0013*\ufffdѠ2\ufffd\ufffd|\ufffdu\u0012,\u0011\ufffd\u000bo\ufffd\ufffd\ufffdJ\ufffdK\ufffdDL\ufffd\u000e\u0006\ufffd\u0011Z\ufffd\ufffd\u0002x\ufffd\ufffd\"\ufffd\ufffd\ufffd0\ufffd\ufffd\u0013ai*\u000b5)`^\u0012\ufffd'd\u0013\ufffd\u0015\ufffd\ufffd\ufffd\u0003\ufffd\ufffd\ufffdx\ufffdy\ufffd\n0\ufffd\ufffd\ufffd\ufffdc\ufffd\u000b$\u0008\u0006J\ufffd\ufffd\u0015?s\u0010?\ufffd\u0013\u001f\ufffdx+\ufffd\ufffdcz\u0001X\ufffd\ufffd\ufffdR\u0005t\ufffd\ufffd.2E\ufffd,\u0003\ufffd\ufffd\ufffdK\ufffd\u000bN\u000e\u0008:!.ށ\ufffd0\ufffd\ufffd\u0006\t;\ufffd\u0026\u000f¼\ufffd~/%\u000b1\u0016\u0008|9+|DC\ufffd\u0007\ufffdS\u0011\ufffd\u0015r`\ufffd,\ufffd9\ufffd\u0007A\u0016\u0000\ufffd\ufffd;\t\ufffd\u0000\u0019E3\ufffd\ufffd\ufffd5\ufffd\ufffd\u0019H\ufffd\u0003\ufffd\u003c\ufffd\ufffd\ufffd\ufffdo\u0005\ufffd\ufffd\ufffd\ufffd\ufffd\u0010\ufffdǕ\ufffdH\u001c\ufffdJP\u0001\ufffdJo\ufffd\ufffd\ufffdB3:\u0016В\ufffd'o\ufffd\ufffd:\ufffd\ufffd\ufffd\ufffdCX\u0003\ufffd\u0015\ufffd֋0\ufffd\ufffdB\ufffd7P\ufffd\ufffd\ufffd\ufffdC\ufffd\ufffd|aPV\ufffdR\ufffd37\ufffdJ\ufffd-\ufffd\u000b~i\"\ufffd\ufffd\ufffd\ufffd\r\ufffd\ufffd\ufffd;\ufffd\ufffd\ufffdW\ufffd\ufffdx\t\ufffd\ufffd0E\ufffd\ufffd\ufffdp\u000f\u0004\ufffd\ufffd\u00001\ufffd=\ufffdV\ufffd\ufffd\ufffdon@nb\u000cNog^Pֱ\ufffdҰ\ufffd…\u0001\ufffd*\u0005\ufffd\ufffd\ufffd\ufffdPyjϒ6\ufffd\ufffd\ufffd\ufffdl\ufffd!\u0000\u001a\ufffd\n\ufffds2\u0004\ufffd\ufffd\ufffd/\ufffd\ufffdrLՀ\ufffd\u0000\u0010\ufffd\u00071\ufffd\u001a0et\ufffd\ufffd\ufffdF%s\u0000\u0016\ufffdͲRKW\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdו\ufffd\ufffd\ufffdk\ufffd#F\u001cC\u003eN|\ufffdUi%\ufffd\ufffd\ufffd\ufffd\ufffd6b\ufffd8]Hyi@\ufffd\u001e\ufffd\ufffd\u0016\ufffd\n\ufffd\ufffdF)\ufffd\ufffd\ufffd\u0017\\\ufffdN\ufffd3 \ufffd\u0000\ufffd\ufffd\ufffdB\u0016\ufffd}k\ufffd-J!˽\ufffd7\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0017YD'\ufffdw\ufffd\ufffdG\ufffd-Qh\ufffdK\"7U\ufffd\u001b\u0006\ufffd\ufffd\"\ufffd\ufffdg)\u000b\ufffd\u0001\ufffd\u001a!\ufffd\ufffd\ufffdP\ufffd\u0008\ufffd\ufffdRI\ufffd\ufffd7\ufffd$\ufffdj\u000f\ufffd\ufffd\ufffd\ufffd\ufffdB\ufffd$\ufffd\ufffd\u0000\ufffdm1\ufffd\u0016\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd~\u001e\ufffd\ufffd\ufffd.Ӌ\ufffd\ufffd^O\ufffd\ufffd\ufffd\ufffdwo\ufffdx\ufffd\ufffd˖E\ufffd\ufffd\ufffdt\ufffd\ufffd2\ufffd\\\ufffdL\ufffd\ufffd\ufffd!\ufffd@{\ufffd\u0015F\u0001\ufffdѝ\ufffdrHȕ\ufffd\ufffd\u001f\ufffd\ufffd|\u001fXa\ufffd\u0000Bt\u0016\ufffd4:V2\ufffd\ufffd\ufffd\ufffd{\ufffd/\ufffdH\u000fp\u001a\ufffd{H'\ufffdЪ\ufffd\ufffd)\ufffdĴ\ufffdяN\ufffd\ufffd\ufffd\ufffd\ufffd\ufffds:ad\t*\ufffd[\ufffd\u0014![͞\ufffd\ufffd\\\ufffd\ufffd\u0008\ufffd\ufffdn\ufffd\ufffd\ufffd2\ufffd\u0004\u003e\ufffd(l\ufffdw\ufffd\u0013S\ufffd\u0004R\ufffdނ\ufffd£Ԭk\ufffdK%,\ufffdFצb\ufffd\u001a\ufffd\ufffd\n\ufffd\ufffd\ufffdM\u0003u`*Eҁ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdc\ufffd\ufffd\ufffdo\ufffdt\u000b\ufffd|\ufffd4\ufffdU\u0015\nUaz\ufffd\ufffd\ufffdg\u0001\ufffd\ufffdJN\ufffd\u001b!\ufffdC\ufffd8\ufffd\ufffd\ufffd\ufffd\n\ufffdy\nS\ufffdWK{m\ufffd-\ufffd:tr{ᠶ\u000b\ufffd\u0016^\u000f\ufffd\ufffd\ufffd\ufffd-\ufffd7}\ufffdd\ufffd\ufffd\ufffd\ufffd6\ufffdj\ufffd\ufffd\ufffd\ufffd\ufffd\ufffda'?\ufffd\ufffd\ufffd\ufffd\ufffdp\u00198\ufffd7.\ufffd\ufffdX\ufffd\ufffd\ufffdKv\ufffd\ufffd^\ufffd\ufffd;\ufffd\ufffdq\ufffdր\u0004\ufffd\ufffd\ufffd\",y\ufffd\ufffd|nY\ufffd\ufffd\ufffdR\ufffd7\u0007n\ufffd\u000fm\ufffd\u001f\ufffdZ|7\ufffd\ufffd\ufffd\ufffd\ufffd{\ufffdjn\ufffd\u0015֮1\ufffd^wD\ufffd\ufffd\ufffd\ufffd 5|\ufffd\ufffd\u0019\ufffd\ufffd+\ufffd\ufffd\u001a\ufffd\u0004\ufffd\ufffd\ufffdI\ufffd?\ufffd\ufffd%C\ufffd*\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2646\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:04 GMT\r\nSet-Cookie: AWSALB=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2; Expires=Thu, 14 Sep 2023 15:34:04 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2; Expires=Thu, 14 Sep 2023 15:34:04 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:04:07+05:30","url":"https://ginandjuice.shop/blog/?search=dadad\u0026back=%2Fblog%2F","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2; AWSALBCORS=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2","Referer":"https://ginandjuice.shop/blog","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/blog/","scheme":"https"},"raw":"GET /blog/?search=dadad\u0026back=%2Fblog%2F HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2; AWSALBCORS=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2\r\nReferer: https://ginandjuice.shop/blog\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"2075","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:07 GMT","Set-Cookie":"AWSALB=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s; Expires=Thu, 14 Sep 2023 15:34:07 GMT; Path=/, AWSALBCORS=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s; Expires=Thu, 14 Sep 2023 15:34:07 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdZ͒\ufffd6\u0012\ufffd\ufffd)`\ufffd\ufffd\u001cUB13\ufffd\ufffd\u000f#jk\ufffd\ufffdz+5v\ufffd;\ufffd\ufffd\ufffd\ufffd\ufffd@\ufffd\ufffd\u0007$\u0018\u0000\ufffd\ufffd\ufffd\ufffd\u0003\ufffd5\ufffddi\u0000$EI\ufffdH\ufffd\ufffdj\u000f\ufffd]%\u0012@7\u001a\ufffd\u000f\ufffd_C3{\ufffd\ufffd\ufffd'\ufffdz\ufffd\u000c\ufffdt\ufffd\ufffd_\ufffd\ufffd\u0017\ufffd\ufffdlEq\ufffd\u001e\ufffd+g\ufffd\u001dZI\ufffd\ufffdBI\ufffd($\ufffd*\ufffdxa\ufffdQ\u0019\u0012\ufffdBEp\ufffd\ufffd\ufffd\ufffd6L\ufffd\u0001I\ufffd#\ufffdKNՊR=\ufffd̨\u0000\ufffdꚋ\ufffd#\ufffdo\ufffd\ufffd\ufffd\u001eW\ufffdR\ufffdQ\ufffdS\u001aykF7\ufffd\ufffd\ufffdCDd\ufffdf:\ufffd6,֫(\ufffdkFh`_\ufffdF\ufffd\ufffd2\ufffd\u0005\ufffd\u000c\ufffdF\ufffd\ufffdZ\ufffd\u0014\ufffd,\ufffdHI\u0012y-\ufffd\ufffd+x\ufffdDOA\u0013\ufffd\"OA\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdI\ufffdW\u0011\ufffd\"=A\ufffd.sX\ufffd\ufffd\ufffd:|\ufffd\ufffdصz]\ufffdq\ufffd\u0014\u001c\ufffdw\ufffd\ufffd\ufffd\ufffdq\ufffdV\ufffd4\ufffd\ufffd\ufffd,C\u000fq\ufffd_\ufffd\ufffd\np\u000c\ufffd]\ufffd|\u0016\ufffd\u003e\ufffd\ufffdp\u000b\ufffd\ufffdB\ufffd%ʒ\u0000\ufffdyKS\ufffdֈő\ufffdFJ˓n\r\ufffdh\u00262D8V*\ufffd\u001c\ufffd\ufffd\ufffd:\ufffd\ufffdgO\ufffd\n\ufffd\ufffdm\ufffd\ufffdz\ufffd\u0014\ufffd\ufffd\u0018Ŕ\ufffd\u0005\ufffdXS^\ufffdu\ufffd3x\ufffd\ufffdD\u001b\ufffd@`#g\u0004\ufffdYa\u001e\ufffdd4FK!\ufffd\ufffdJ\ufffd,1\ufffd\u001ef\u000b\ufffd_Ղ\ufffd3]\ufffd\u0026\ufffd\u0014hS\ufffdN\u0003f*\ufffd\ufffdR\u000cl=$\ufffdT\u0000\ufffd`\u0017\ufffd4X\ufffdb\ufffd\ufffd\ufffd{w\ufffd\"t\ufffdt\ufffd\ufffdn\ufffd\ufffdNo\n\ufffd\ufffd\ufffdK\ufffd[wCk\ufffd\ufffd~\ufffd#\\\ufffd\ufffdǖ\ufffd\ufffd=\ufffdÇ\ufffd=\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\t\ufffd\ufffd\ufffdV\ufffd\ufffd\u0016\ufffd3\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdo9I\ufffd\ufffd\ufffd\ufffd\ufffdy\ufffd\ufffds\ufffd\ufffd\"\ufffd\ufffd\ufffd)66\ufffd\u003ePM[\ufffdί\ufffdA\ufffdEG\ufffdeG\ufffd#o\ufffd\ufffdШ\u0004\u003c\ufffdj\u001f\ufffd\u0016H\ufffd\ufffdݺ\u0007\ufffdep\u0007\\\ufffd=\ufffd\ufffd@\ufffd\ufffd\ufffdX\ufffd\u001dl\ufffd\ufffdD~\ufffdZ\u0003o7\ufffd\ufffd\u0012\ufffd`X7.g؅/?\ufffdPӡN٘\u0016@`\u0014p@\ufffd\ufffdGm\ufffd{:\ufffd\ufffd\ufffd\ufffd\r\u0006\ufffd\ufffd2\u0019\u0019Q\ufffd\u001a\ufffd0\ufffd%\u0016\u0013Au`\r\ufffd\u0015\ufffd%K\ufffd,\ufffd[\ufffd\ufffd=P\ufffd\ufffd\ufffd ;\u0010\ufffdS/\n\ufffd!(8\ufffdz! \u0013\ufffd\ufffd\u0000\u0019R\ufffd\u0005Ѫ\ufffd);\ufffd\ufffd1\u0013\ufffdu\ufffd\ufffd\ufffdh\u001a7f.\ufffd\ufffd\u0026\ufffd\ufffd?\ufffdט\ufffd\u0017\ufffd\ufffd\ufffd\ufffd{s\ufffd\ufffd\ufffd\ufffd'2n\u0016\u0016\ufffd\ufffd\ufffd\u0004\ufffdQd\ufffdgF\u0013\u0026D\u0014\ufffd\u000e\u0018iy%-\ufffd\ufffd\u001d\ufffd\ufffdZ'\ufffd\u0019\u0004\ufffd\ufffds\ufffdO\u0001\ufffd\ufffd\ufffd\u0002\ufffdz\ufffd\u0014F\ufffd\ufffd\u0013\ufffd/\u0008\ufffd4k\ufffd\ufffdqo\ufffd\ufffdq\u0003OZ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd^\ufffd\ufffdH\u0010\ufffdF[l\ufffd\ufffdB\ufffd\ufffd5\ufffdE\ufffd\ufffd\ufffd\ufffdd\ufffd0\ufffd\ufffd\ufffd\ufffd\u0000\ufffd\u0012,\ufffd\ufffdZE\ufffd\ufffd\ufffd\u0018\ufffd\ufffd\u0014x^\ufffd‘\ufffd5\ufffd\u0016\ufffd[\ufffd\ufffd\ufffdv\ufffd\ufffd[\u0007?\ufffd\"I8\ufffd\ufffd\ufffdN\ufffddIb\ufffd\ufffd[M\ufffd\ufffd\ufffd\u0017\ufffd\ufffdc\ufffd\ufffd,\ufffdM\ufffd=)\ufffd\ufffd\ufffd\ufffdX썲\ufffd\u0019\u0018\ufffd\ufffdE\\R8J\ufffdS\ufffd\ufffdM\ufffd\ufffdb\ufffd\ufffdL\ufffd5\ufffd\ufffd\u0002\u00079\ufffdnOȜ\ufffd0\ufffd8\\h\ufffd\ufffd\ufffdp\u0015\ufffd\ufffd\u0004C\ufffdݧ\ufffd\ufffdJp\ufffd\ufffdÎ\ufffde\ufffd\ufffd[\ufffd\ufffd4\ufffd\u0012\ufffdP\ufffd\ufffd\ufffd\ufffd䈀\ufffdX\ufffd\u0015\u0004)\ufffdɞT\ufffd\ufffd;~u\ufffdG\ufffd\u0000\ufffd@\n\ufffdĈD6y\ufffd\u0008\ufffdȕ\ufffd\ufffd\ufffd\ufffd^\u000f\ufffd\ufffdeyQ\u0015e\ufffd\u0026C9DŽ\ufffd\u0004\u0007\ufffdE\ufffd\ufffd\ufffd\ufffdrV\ufffdv:\ufffd\ufffd\ufffd2u\u0026\ufffd5\ufffd\ufffd\ufffd\ufffd\u0018\ufffd\ufffd\ufffdΖ\ufffd\u0015\ufffdc\ufffd9\ufffd\u0005\u0026w\ufffd\ufffd\ufffds O\ufffd*\ufffdb5\ufffdb\ufffd2]\ufffd\ufffdu̝\ufffd\ufffd\ufffdz=r^\ufffd\ufffd\ufffd\ufffd\ufffdɯ\ufffd\ufffdc;e\u003e\ufffd\"s\ufffd\ufffd%\ufffd\ufffdYt\ufffdKAe9A\ufffd;\ufffd\ufffdX\ufffd\ufffdV\ufffd\u001b\ufffd4=\ufffdg,M\u000e\n\ufffd*\ufffdY倞\ufffd-\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0014x\ufffdWv\ufffd\ufffd|o\ufffdO\ufffdz\ufffd\ufffd\ufffd\ufffdg\ufffd%\ufffd*\ufffd`:\ufffd\ufffd\u0006\ufffd\ufffd\ufffd\u001b\ufffd\ufffdW\u0006\ufffd\ufffd.\ufffd\ufffd\ufffdj\ufffdf\ufffdL\ufffd\t\ufffdg\ufffd{;65[\ufffd\ufffdơ\ufffdN]\ufffdI\ufffd\ufffd\u00064`kJ\ufffdL\u0004\ufffd\ufffd\ufffd\ufffdc\ufffd\ufffd\u001d\ufffdF\t\ufffd\u000b\ufffdIk0\ufffd%6\ufffd(\ufffd\ufffd\ufffd\ufffd\ufffd\\\ufffd[o5\u0026\ufffd.\ufffd\ufffdB\ufffd\ufffd\u0001z)\ufffd\ufffd\u0000-\ufffds\ufffd\u0010\ufffd.\ufffd͟_\ufffdQ\ufffdE\ufffdk\ufffd\ufffd5EE\ufffd\u0002DV\u0014\ufffd\ufffd\ufffd\u000b*\ufffd\ufffd\ufffd{\u0008s%\u0001 \u0002\u0013\ufffdi$\u0026L\ufffd*Nh\u0003\ufffd週C\ufffd[WB\ufffd0,ۄ\ufffdVЩ\ufffd\ufffd/}(\ufffd\tg\ufffd.\ufffd\ufffd\ufffd\u001c\ufffd\\\ufffd\ufffd\ufffdt\ufffda\ufffdg\ufffd\ufffd\ufffd%A\u0017\ufffd\ufffd\ufffdn\ufffd\ufffd\ufffd\ufffd\ufffd\t\ufffd\ufffdWT\ufffdHW\ufffd\ufffdڄ4(\ufffd\u0007\ufffd\ufffd\ufffd\ufffdqo\u0000\u001a\ufffd\ufffd\ufffd\ufffd\ufffd-\ufffd\u0000\ufffd\u0007\u001b\ufffd\ufffd\ufffd;\u0011\ufffd\u001d\u0005'ЗH\u000f5s\ufffd4\ufffd\ufffdȒ\u000ee\ufffd\ufffdT:v\ufffd\u0000\ufffd/a\ufffd$J\ufffdr\ufffdb\ufffd\u0003\ufffd \ufffd\ufffdc\ufffd\ufffdpN7\ufffd1;\ufffd\ufffd\ufffd\t\ufffd!s\ufffd\ufffd\u0011\ufffd\ufffd\u001b\u0016SDD\ufffd\u0016\u0019\ufffd%\u0012K\ufffdp\ufffdp\u0016\ufffd\ufffd\ufffd\ufffdp\ufffd3\ufffdh\ufffd\ufffd\ufffdWj\ufffd\ufffd\ufffdB\u0019\ufffd\u0003(\u0014\ufffd\ufffd*\ufffd\ufffd\ufffd\ufffdXk`\ufffd\ufffd\ufffd4\u0017F\ufffd\ufffd\u001b\u0011\ufffdCY*-Z\ufffd\ufffdhN\ufffd\ufffdӃ[\ufffd]\ufffd\ufffd3\ufffdz\ufffd/!\ufffd\u000e3\ufffd\ufffd\ufffd\ufffdⲖ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdb\ufffd\ufffdAE\u0008\ufffd\\(]5U\ufffdaKʷ\nF\ufffd\u0006I)\ufffd\ufffd\ufffdK\ufffd\u0014\ufffd\u001fwy\ufffd=\ufffd\ufffd\ufffd\ufffd\ufffd6\ufffd8\ufffd\ufffd\u000e\u0006\ufffd.\ufffd\ufffd#\u0012^u3N\ufffd\\z5\ufffdx\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\t}\ufffd\ufffd\ufffd\ufffd}|\ufffdCI\ufffde\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u000bzw\ufffdf$\ufffdث\ufffdZ\ufffdc~[{f\u000c\ufffd\u0018\nc\ufffd\ufffdvà\ufffdˁƺ\ufffd\u00132I%\ufffd\ufffd\u0007VH\ufffdP4\ufffd\ufffdY\u0007K\ufffd\ufffd\ufffd\ufffd~jM:\ufffdasbN\ufffdЪ\ufffd\ufffdɲ\ufffd\u0018a\ufffd\ufffd\u00062~\ufffd\ufffd\ufffd\ufffd\ufffd.\ufffd\ufffd\u0004\ufffd^Aj\ufffd\ufffd\ufffd\ufffdf\ufffdͥ\ufffd\ufffd\ufffdzuY\ufffdM^\ufffdK\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd1\u0010\ufffd\ufffd\ufffd\ufffd\ufffd\u0004\ufffd\ufffd\u000eԪ+\ufffd\ufffd\u00264FC\ufffd\ufffd\ufffd@\ufffd\ufffd\ufffd\u0003vd\ufffd7-ԁRF\ufffd\u0000\ufffdߝ\ufffd\ufffd\ufffdV\u0017\ufffd\ufffd\ufffd\u0018\ufffd\ufffd\ufffdt\ufffd\ufffd|\ufffd\ufffd\u003c\ufffd%P\t\ufffd\ufffd\ufffdޤS{kn~\ufffd\ufffd\r\ufffdx\u0002\ufffd\u003eZP\ufffdf\ufffdF\u0015\ufffdfG\u0008i~\ufffd;\u001akO0j\u0004?1\ufffdb\u001cE\ufffd\ufffd\u001el\ufffdd\ufffdj\u0008\ufffdmQ\ufffd\ufffd\ufffd\ufffd\ufffdK\u0005{\ufffd\u003e\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd;\ufffd\ufffd\ufffd\ufffd\u0012\ufffd\u0002\r\ufffdN\u0003\ufffd\ufffd\ufffdG0\ufffd\ufffdr|\ufffd\ufffd\ufffd+\ufffd~\ufffd\ufffdIH`\ufffd\ufffd\u0013h\ufffd\ufffd\ufffdnv$\ufffdW\ufffd\ufffd\ufffd\ufffd\u000c\\\ufffd\u000e9ﯟ\"\u003e\ufffdu\ufffd\u0014\ufffd\ufffd/\ufffd\ufffdk\ufffd\ufffd[\ufffd\ufffd\ufffd\u001ev\ufffd\u000e\ufffd%\ufffd\ufffd\ufffd\u0018\u000e\ufffd)d%\u0011\ufffd\ufffd/ 7ڿ\ufffd\ufffd\u0013o\u0004\ufffdo\ufffd!\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2075\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:07 GMT\r\nSet-Cookie: AWSALB=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s; Expires=Thu, 14 Sep 2023 15:34:07 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s; Expires=Thu, 14 Sep 2023 15:34:07 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:04:08+05:30","url":"https://ginandjuice.shop/logger","request":{"header":{"Accept":"*/*","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Content-Length":"34","Content-Type":"text/plain;charset=UTF-8","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s; AWSALBCORS=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s","Origin":"https://ginandjuice.shop","Referer":"https://ginandjuice.shop/blog/?search=dadad\u0026back=%2Fblog%2F","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"empty","Sec-Fetch-Mode":"cors","Sec-Fetch-Site":"same-origin","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"POST","path":"/logger","scheme":"https"},"body":"{\"search\":\"dadad\",\"back\":\"/blog/\"}","raw":"POST /logger HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: */*\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nContent-Length: 34\r\nContent-Type: text/plain;charset=UTF-8\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s; AWSALBCORS=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s\r\nOrigin: https://ginandjuice.shop\r\nReferer: https://ginandjuice.shop/blog/?search=dadad\u0026back=%2Fblog%2F\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: empty\r\nSec-Fetch-Mode: cors\r\nSec-Fetch-Site: same-origin\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"0","Date":"Thu, 07 Sep 2023 15:34:08 GMT","Set-Cookie":"AWSALB=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g; Expires=Thu, 14 Sep 2023 15:34:08 GMT; Path=/, AWSALBCORS=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g; Expires=Thu, 14 Sep 2023 15:34:08 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\nContent-Encoding: gzip\r\nDate: Thu, 07 Sep 2023 15:34:08 GMT\r\nSet-Cookie: AWSALB=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g; Expires=Thu, 14 Sep 2023 15:34:08 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g; Expires=Thu, 14 Sep 2023 15:34:08 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:04:10+05:30","url":"https://ginandjuice.shop/blog/","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g; AWSALBCORS=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g","Referer":"https://ginandjuice.shop/blog/?search=dadad\u0026back=%2Fblog%2F","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/blog/","scheme":"https"},"raw":"GET /blog/ HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g; AWSALBCORS=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g\r\nReferer: https://ginandjuice.shop/blog/?search=dadad\u0026back=%2Fblog%2F\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"2651","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:10 GMT","Set-Cookie":"AWSALB=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+; Expires=Thu, 14 Sep 2023 15:34:10 GMT; Path=/, AWSALBCORS=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+; Expires=Thu, 14 Sep 2023 15:34:10 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdZێۺ\u0015}?_\ufffd\ufffdh\u0026\u0001\ufffd֙KrZ\ufffd\ufffdAng2An\ufffd\u0004Mӗ\ufffd\ufffdh\ufffd\u0019\ufffdTEʎ\ufffd\ufffd\ufffd\ufffd'\ufffd7\ufffd)\ufffd\ufffd\ufffdM\ufffd\u001e\ufffd\ufffd\ufffd\u0026\ufffd\u0000y8A0\ufffd\ufffd\ufffd\ufffdM\ufffd\ufffd}\ufffd\u0006w\ufffd\ufffd}\ufffd\ufffd\ufffd\ufffd\ufffd,s\ufffd\u001a\ufffd4\u0008?\u000c\ufffd\u0006\ufffd\ufffdix\ufffd\ufffdJ\ufffdK\ufffd\ufffdb2\ufffdKaMU\u0026\ufffdƊ\ufffd\ufffdL\ufffdqbml\u0013\ufffd_\ufffd\u0017\ufffd\ufffd\ufffd\u0006V\n5\ufffd\ufffdV\ufffdfB\ufffd}̈\u0005\u0018\ufffd'\ufffdL\ufffda\ufffd\u0005\ufffd\ufffd\ufffdn\u0006\ufffdp\ufffdi\ufffd\ufffda4\ufffdb^\ufffd\ufffdE,1\ufffd\t\ufffd\ufffd\ufffd\\\ufffd.\u001b\ufffdb\u0026\u0013\ufffd\ufffd/\ufffd\ufffd\ufffd\ufffd\ufffda\ufffd\ufffdA\ufffd\ufffd6Q\ufffd\ufffdMJY8f\ufffdd\u0018\ufffd\u0004\ufffdl\ufffd\ufffd\u0013\ufffd\u0007'\ufffdL\ufffd\ufffdy\ufffd\ufffd\ufffdF\ufffd8\ufffd\ufffd΢\ufffd\ufffd\ufffd\u0006l\\]`eN|q\ufffdg\u003e\ufffd\ufffd5\ufffdĝ\ufffdi\ufffdx\ufffd\ufffd\ufffd\ufffdK\ufffd\ufffdm\\\ufffdtJ\ufffdH-\ufffd\ufffdΤfwy^\u003cb/+\ufffd\u000f\ufffd\ufffdL1\ufffd\u0003I\u0000N|\ufffd\ufffd\ufffdؤ5\ufffd\ufffd\u001e/\ufffd\u0016\ufffdTΘL\ufffdQ\u001b0\ufffd\r\rK\u0011\ufffd\ufffdF\ufffdDqk\ufffdQ\ufffdY/\u0015A:\ufffd\ufffd\r\ufffd\ufffd\ufffd\ufffdm\ufffd\ufffd}\u0026-\ufffd\ufffdR\ufffd\ufffdX\ufffd\ufffd\tU\ufffdY\ufffd4\ufffd\ufffdP6\u0017c\u0006\u0019\ufffdL\ufffd\ufffd\u0015\ufffdȩ\u0016)\ufffd\ufffd\ufffd9a\ufffd\ufffdS\"\ufffd\ufffdǶx\ufffd\u0018(\ufffdtuh\ufffd\ufffd\ufffd\ufffd\ufffd7\n0\ufffd\u0005_.\ufffd\ufffd\u001b1\ufffds\u0003PA\ufffd\ufffd$\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u003e}9bCv\ufffd\ufffd6\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u001c\ufffd\ufffdޓл\ufffdF\ufffdnw\ufffd\ufffd\u000c\ufffd\ufffda$'\ufffd^{\ufffd\ufffdwY{\ufffd\ufffdד\ufffd\ufffd\ufffd\ufffdL\ufffdf\ufffd\ufffd\ufffd;3\ufffdw\ufffd\ufffd\ufffd\ufffdG\ufffd\ufffd\u0007*\ufffd\ufffd\ufffd×\"=S*\ufffd\u000f\ufffd\u0007\ufffd\ufffd\ufffd*\ufffdIf\ufffdA8\ufffdbw\ufffd\ufffd]k;\ufffd\ufffdv\ufffd\ufffd\ufffd4\u001a\ufffd\ufffd\u001c\ufffd\u0001k\ufffd\ufffd\ufffd9\ufffd\\\u0026\ufffd\ufffd\ufffd\u0000\ufffd\ufffd\ufffd\rp\ufffd\ufffd\ufffd\u0002€\ufffd\ufffdX\u003c\u0008\ufffd}\ufffd':\ufffd\ufffd\ufffd\ufffd\u001dHɤp\t\ufffd͸\u001c\ufffd`\ufffd\u000e\ufffd\rl6\ufffd\ufffd޴\ufffdp\u0010\r\ufffd)ڷ\ufffd\ufffd\ufffd\ufffd\ufffd+V\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdh\ufffde\ufffd%E\ufffd\u0016\ufffd\u0006\ufffd\ufffdzL\ufffd\ufffd\u0003K`\ufffd\ufffd(e\ufffd\ufffd:\ufffdm8\ufffd\ufffdX*\ufffd\ufffd\ufffd\u0013\ufffd\ufffd\ufffd\ufffd\ufffd9\u0018\ufffd\ufffd\ufffdQ\u000cdr\ufffd\u0013\ufffdQ\ufffd\ufffdJ\ufffdݺ)+\ufffd\ufffd.\u0013\ufffdt\ufffd\ufffd\ufffd8\ufffd.\ufffd\u001c{\u0019ɴ\ufffd\u0008\ufffd-\ufffd\ufffdcS\ufffdh\ufffd\ufffd\u000e\ufffd3e\ufffd\ufffd\ufffd\u001bĕ\ufffdf,\ufffd0\u001a\ufffd\ufffd2\ufffdx\ufffd\ufffdJ\ufffd\ufffdLZ\ufffd\ufffd׽\ufffd\u001dn\ufffdΦ\ufffd\u0001\u000cڢ\ufffdʭ\u0002\ufffdS\ufffd\ufffd\ufffd\u0005}\u001f\ufffdX\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdm\ufffdO\ufffd\ufffdm\ufffd\ufffd\u001bፖ}}\ufffdkph/\ufffd\u0015\ufffd\u0000\ufffd;K\ufffd\ufffdvB\ufffd\ufffd\ufffd\ufffd\ufffdf\ufffd\ufffd-\ufffd\ufffd\u001f\ufffd\ufffd\ufffd}\u0005`\u0013^\ufffd\ufffd\ufffd\ufffd\ufffd1\ufffd\u0010^\ufffd\u0005\u001e5\ufffd\ufffd#zi\ufffd\ufffd\ufffd\u0015\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd[\u0007\ufffd\ufffd\ufffdt\ufffd\u0004f\ufffd+\ufffd\ufffd\ufffdN)T\u000c\ufffdY.f\ufffd\u000b\ufffd\ufffdV\ufffd4\ufffd\ufffd:\ufffd-.xC\ufffd\ufffd\ufffdb\ufffdʻfD,\ufffd\ufffd\u0004\ufffd\ufffd3\u001c\ufffdᵗ\ufffd{S$|\ufffd\ufffd\ufffd%\u001dB\ufffd^\u0001\ufffdn1\ufffd\ufffd`f\ufffd\u001bn\ufffd\ufffd4\ufffdpc\ufffd\ufffd\r\ufffdC\ufffd6\ufffdG\ufffd\ufffd\ufffdÖ\ufffd\ufffd*\ufffd9X\ufffdORQ\ufffd\ufffd\ufffd\ufffdL\u000eZ\ufffdɎ\u0001V\ufffd2\ufffd`\ufffd\ufffd\ufffd\ufffdQ\ufffd\ufffd^\ufffd\ufffd0z\u0007\u0018\ufffd\u000e\ufffd0$4d\ufffd\ufffdw̐Kf\u0026\u001d\ufffd=\ufffd\u0007fR\u0017U\ufffd\ufffdQj\ufffd\n\ufffd\u0013\ufffd\u0019\ufffd\ufffd\u001b\u001e\\\ufffd\ufffd}\ufffdJl\ufffd\ufffd\ufffdAHP\ufffdEj\ufffd\ufffdd\ufffd\n\u001d\ufffd\ufffd\u003c\ufffdd3\ufffd*ф\u0015\ufffd\u001e\ufffd8hb\u0012\ufffd\ufffdV\ufffd\\\ufffdf\u0017B\ufffd(\ufffd4\ufffd\ufffd\ufffd\u001d'\ufffdve\u001b46F\ufffd\ufffd\ufffd5X\ufffd\ufffdp\ufffd\ufffd퐽\rmO_\ufffd\ufffd\ufffd\ufffd\u001dl\ufffdD\ufffd+\ufffd9O\ufffd'\ufffd$2\ufffd6\ufffd\ufffd\ufffd|q\ufffd\ufffd\ufffdhm\ufffd\ufffd\ufffd\ufffd\ufffdF\ufffd\ufffd\ufffd ;\u001e=f/\ufffd,k\ufffd\ufffd#^\ufffd\ufffdv\ufffdb\ufffdѰ\ufffdB\ufffdy\ufffd=\ufffd\ufffd2\ufffde\ufffd\u0014\ufffdOqN\ufffd\ufffd\ufffd͹v\u003e\ufffd\u0011JQ^\ufffd|@\ufffd\ufffd\u0019w,C\ufffd*(5E\"Np\ufffd\u0000M\ufffd'\ufffd?%Ky\ufffdg\ufffd\ufffd\u003e\ufffd|,\ufffd\n\ufffd\ufffdW\u000bvr\ufffd9F9%\ufffd\ufffd\ufffd1\ufffdT\ufffdr\ufffd\ufffd\ufffd;e\ufffd\ufffd:\ufffd\ufffdX\ufffdI\ufffdLX\u0004\ufffd}va\ufffd\u000fx\ufffd\u0018\u0019t\ufffd\u0019-X•\u0002B\ufffd\ufffdOk۾\u001a\ufffd\ufffd\ufffd\ufffd\u001c\u0003\ufffd]\ufffd\ufffd\ufffd\u0014sF\ufffd\ufffdw{{\ufffd㻿\u001fBN;!\ufffd\ufffd#B\ufffdCIOM\ufffd\ufffd/)\u000eك\u0011\ufffd,d\ufffd\ufffdG\ufffdw\ufffdxK\ufffdA\n2\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd^\ufffd:2b\ufffdA\ufffd9\u0014d\u0026\u0013\u0001\u003c\ufffd\ufffd%\ufffd\ufffd\ufffd\u0002O\ufffd\u0014\ufffd\u001a\r];v\ufffd\ufffd\u001cp2\ufffdr\ufffd\ufffd\ufffd\ufffdx\ufffd\ufffd\ufffd\ufffd\ufffd1\u0001i!\ufffd\u001ch\u0012%/E\ufffd=\ufffd\ufffd\ufffd\ufffd \ufffd\u000bp\u0001\ufffd\u003eW\u0016\u000f\ufffd\ufffdL\u0012_\ufffd'\u0001D\u000f\u0017\ufffd\u0012\ufffd\u001a\u0012\ufffd\ufffd@\u0002\u0014\ufffd\u0002tN\u003c\ufffd\u003c\ufffd\u0004\ufffdӮ\ufffd)\u0001\u0005:\ufffd]\ufffd\u000bpVӎ3m\u003cz\ufffd\ufffd\ufffdٹUPg\ufffd:\n\u003c\ufffd\ufffd\u0000\t\ufffd\ufffd2\u0018\u001a\ufffd\ufffdR`\ufffd\u0001C\ufffd5\ufffd\u000emx\ufffdA\ufffdWt\ufffdV@a\ufffdnn\ufffdK@\u0007\u0000jA%\ufffd\u0001\u001e\ufffd\ufffd\ufffdPe\ufffd\ufffdp\ufffd\ufffd\u000b4\ufffd\t\ufffd\u0018L\rz@꥘\ufffd\ufffd\u0000\ufffd[\ufffd\ufffd\ufffd\u001f\u000f#ǝ0\ufffd\ufffd\u0006\ufffd\ufffd}\u0026˔}ȄP]\ufffdPfrI^Ȼ\u001c\ufffd\u001b\ufffd\ufffd\u001bs\u0026\ufffd\ufffd\ufffdZ\ufffdƲt\u0019\ufffd\u0018,\u001b\ufffd\u0016\ufffd\ufffd߸\ufffd\u0014\ufffd\ufffd\ufffdI\ufffd\"N\ufffd\ufffdF\ufffd\ufffd\ufffds\ufffdi\ufffdI\ufffdD\u003c/\ufffd\ufffd\ufffdz*\ufffd.\u0019\u00121\ufffd;\u00184Gh\ufffd\u0014\u000b\ufffd1[\ufffdD\ufffd\ufffdǬ7N\ufffd\ufffd\ufffd,դ\ufffdyI\u0019\ufffd\ufffdM$WD^\ufffd\u0016\u0000s\ufffd\ufffd\u0001\ufffdA'\ufffd\u001cu\u0004L\u0013{\ufffdF\ufffd`\ufffd\ufffd\ufffd]\ufffd3{\ufffds\u003e񱉷\"\ufffd8\ufffd\u0017\ufffd\u0005\ufffd\u000e-u@ת\ufffd\"S\ufffd\ufffd2P\ufffd۸\ufffd\ufffd\ufffd䀠\u0013\ufffd\ufffd\u001d\u0018\u000cC\ufffdm\ufffd\ufffd\ufffdk\ufffd ,*\ufffd\ufffd2\ufffd\u0010c\ufffd\ufffd\ufffd\ufffd\ufffdG4\ufffd~\ufffd$\u0013!\\!\u0007FȂ\ufffd\u0003~\u0010d\u0001P~\ufffd\ufffd\ufffd\u000c\ufffdQ4\u0003\ufffd\ufffd\\3\ufffd\ufffd\ufffd\ufffd[\u0000σ\u001f\u000f\u003cG\ufffd\ufffds\ufffd\u0011\u003c\ufffd\ufffd\ufffdq\ufffd7\u0012\ufffd\ufffd\u0012T\ufffd\ufffd\ufffd[d\ufffdЌN\u0004\ufffd\ufffd\ufffd\ufffd\ufffd'\ufffd\tp\u003c\ufffd\ufffd\u0010\ufffd@se\ufffd\ufffd\"\ufffd\ufffd\ufffdP\ufffd\rT\ufffd+\u003c\ufffd\ufffd58_\u0018\ufffd\u0015\ufffd\u0014\ufffd\ufffdM\ufffdRf+\ufffd\ufffd_\ufffd\u0008=xx\ufffdc\ufffd\ufffdνo\ufffd\u0015h\"^\u0002\ufffd8L\u0011\ufffd\ufffd\u003c\ufffd\u0003A\ufffd[@\ufffd\ufffdw@̎|\ufffd[Y\ufffd7\ufffd 71\u0006\ufffd\ufffd7/)\ufffd\ufffdXiXK\ufffd€m\ufffd\ufffd뜕\ufffdPyjǒ\ufffd\ufffdI\ufffd\ufffdٮC\u00004\ufffd\u0011\ufffd\ufffd\ufffd\u0008~\ufffd\ufffd_\ufffd#՘\ufffd\u0001c\u0001 \ufffd\ufffdb\ufffd%`\ufffd\ufffdR\t\ufffd\ufffdJ\ufffd\u0000,\ufffd\ufffd畖\ufffd\u0026\u000b5E7\ufffd\ufffd\ufffd\u001e\ufffd\u0013\ufffd\u0001\ufffd\u0004q\u000c\ufffd8\ufffd%Q\ufffd\ufffd@\ufffd\u0016soۈ\ufffd\ufffdt!\ufffd\ufffd\u0001\u001dz\ufffd\ufffdZp+\ufffd\ufffd\u0018\ufffd\ufffdvK_p\ufffd;q΁\ufffd=8\ufffdQ\nY,\ufffd\ufffd\ufffdw(\ufffd,\ufffd\ufffd\ufffd\ufffd.\ufffdFW[_\ufffd\u0011\ufffd\u0000\ufffdk*\u001e\ufffd\ufffdD\ufffd\ufffd)\ufffd\\U\u001d\ufffd\u0018t\ufffd\ufffd\ufffd\ufffd\u001f\ufffd,)\u0006\ufffdj\ufffd@ޣBY#\u003c\ufffdK%\ufffds\ufffd\ufffd\ufffd\u0014\ufffd\ufffdW\ufffd\ufffd\ufffd\u003e\nu\ufffd\ufffd\ufffd\u0002 \ufffd\ufffd$Z\ufffdN.N\ufffd,\ufffdt~\ufffd\ufffd\ufffd\ufffd_\u0026\u0017g\ufffd\ufffd\ufffd\ufffd?\ufffd\u003e\ufffd\ufffd\ufffd7\ufffd/\ufffd\u0007\u001f_\ufffd\u000f\u001d\ufffd*k\u0005\ufffdV\ufffdet\ufffdؙ.\ufffd\ufffd}\ufffd\ufffd\ufffd\ufffd+\ufffd\u0002j\ufffd{\ufffde\ufffd\ufffd+\ufffdA?n\ufffd\ufffd޳\ufffd\u0004\u0001\ufffd\ufffd-\ufffdit\ufffddr\ufffd4?\ufffd\ufffd\ufffdy\ufffd\ufffd\ufffd4jw\ufffdN\ufffd\ufffdU\ufffd\u0010S\ufffd\ufffd\ufffd \ufffd\u001f\ufffd\u001d\ufffd\ufffd\ufffd#\ufffd鄑%\ufffd)o\ufffdQ\ufffd\ufffd4{Q\ufffdr\ufffd\ufffdE8itS\ufffd\ufffd\ufffdA/\ufffd\ufffdFac\ufffd3\ufffd\ufffdJ\ufffd\ufffd\ufffd\ufffd\u0006l\u0017\u001e\ufffdaݰ]*a)4\ufffd\ufffd+\u0006\ufffda\ufffd\ufffd\ufffd\ufffd\ufffd޴P\u0007\ufffdR\ufffd=X\ufffd\ufffdhm\ufffdZ]\ufffd9v\ufffd\ufffd\ufffd\ufffd\ufffdn@Z\ufffd\u001e\u0017\ufffd\ufffdC\ufffd*LOў\ufffd,\ufffd\ufffdR)\ufffdw#\ufffdu\u0008\u001e'\u00142\ufffdU\ufffd6OaJ\ufffdji\ufffd\ufffd\ufffd\ufffdP\ufffdNn\u001c\u000ej\ufffd\ufffdo\ufffd\ufffd\ufffd:\u001f\ufffd\ufffd\ufffdxӗMv\ufffdoM\ufffdg\u0003\ufffd6\ufffd\u0018\ufffd\ufffd\ufffd\u001fv\ufffd\ufffd\ufffd\t{\u0007\u000e\u0017\ufffd\u0003{\ufffd\ufffd~\ufffd\ufffd\ufffd\ufffd\ufffdd\ufffdj\ufffd\ufffdn\ufffdc\ufffd\u001e\ufffdl\u000cH\ufffd\ufffd\ufffd\r’7\ufffd\ufffd玕hk(U}\ufffd\ufffdFz\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdw\ufffd\ufffd\ufffdo-\ufffd\ufffd\ufffd\ufffd\ufffd\\a\ufffd\u001as\ufffduKt\u001a\u000e\ufffd\u000fR\ufffdך\ufffd\ufffdSx%\ufffd֣\ufffd\ufffd\u001b\ufffd7\ufffd\ufffd\u0007ts\ufffdѫ*\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2651\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:10 GMT\r\nSet-Cookie: AWSALB=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+; Expires=Thu, 14 Sep 2023 15:34:10 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+; Expires=Thu, 14 Sep 2023 15:34:10 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:04:13+05:30","url":"https://ginandjuice.shop/blog/post?postId=3","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+; AWSALBCORS=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+","Referer":"https://ginandjuice.shop/blog/","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/blog/post","scheme":"https"},"raw":"GET /blog/post?postId=3 HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+; AWSALBCORS=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+\r\nReferer: https://ginandjuice.shop/blog/\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"3942","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:13 GMT","Set-Cookie":"AWSALB=85BJo1Zpdk9Zp+yPBrFftDnRpkda2GE1/JD01eSP9Ex//O3wSyqO/AF6ykA8rziJvN/Q7QNg9UV1oNa53YrhOnHvoGPe6yk0hzUoN1V5Rcp/SYNljF/y+T7fkl7C; Expires=Thu, 14 Sep 2023 15:34:13 GMT; Path=/, AWSALBCORS=85BJo1Zpdk9Zp+yPBrFftDnRpkda2GE1/JD01eSP9Ex//O3wSyqO/AF6ykA8rziJvN/Q7QNg9UV1oNa53YrhOnHvoGPe6yk0hzUoN1V5Rcp/SYNljF/y+T7fkl7C; Expires=Thu, 14 Sep 2023 15:34:13 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffd[\ufffdn\u001c7\ufffd}\ufffd\ufffd\ufffd;\u0018\ufffd\u0006\ufffd\u0012I\ufffd\u003cHj×\ufffd\ufffd\u001c[\u0016\ufffd\u001e\u0004\ufffd\ufffd\ufffd]\ufffd\ufffd\ufffdUU\ufffd!Yݮ\u0003?\ufffd3f\ufffd9\u001fp~\ufffd|J\ufffd䬽\ufffd*UK}\ufffd\u0012\u0007\u0018`\ufffd\u0008\ufffd.\u0016\ufffd\ufffd/k\ufffdX\ufffd\ufffd\ufffd\u0017o\ufffd\ufffd\ufffd\ufffd\ufffd;\ufffd\ufffd\"\ufffd\ufffd\ufffd4\ufffd%\ufffd\ufffd4S2\r?\ufffd1\ufffd\ufffd\ufffdȬ\ufffd\ufffdM\ufffdr\ufffd\ufffd\ufffdr\ufffd\\\ufffdh\ufffd\ufffd\ufffdĹ\ufffdKd\ufffdF\ufffd\ufffd\ufffd1\u0006\ufffdU\ufffd\ufffd\ufffdM\ufffd\\\ufffd\ufffd\ufffdG\ufffdH\ufffd\ufffd{\u00072\ufffd\ufffdM\ufffdP^\ufffdR\u0016\ufffdl\ufffd\ufffdjU\u0019\ufffd\u0007\"1\ufffdW\ufffd?\u001b\ufffdt곳T-u\ufffdF\ufffd0\u0014\ufffdSv\u0004\u000e\ufffdC\ufffd\ufffdJ3\ufffdQs\ufffdՕ\u0017\ufffd\u0026g\ufffd\u001eC\u001f\u001c\u001ed\ufffdǠ\ufffdrS\u0015 \u003e\ufffd\ufffd\u0006\ufffd\ufffdIXqw\u0012\ufffd\ufffd\u0014\ufffd \ufffd\ufffd\n\ufffdy\ufffd\ufffdO\u003eȥ\u000c\ufffd\ufffdM\ufffde\ufffd\ufffdsi:\u001c};\ufffdv\u001bU\ufffd}\ufffd\ufffdO\ufffd+\ufffdm#^\ufffdF\ufffdij\ufffd,\ufffd\ufffdK]\ufffd\ufffd\ufffd\ufffdN\ufffd\ufffd5\ufffd%\ufffde\ufffd:\ufffd\ufffd\u0005\u0001\u0007\ufffdk \ufffd\ufffdLڈr1\ufffdU\ufffd#\ufffd\ufffd\ufffd\ufffd\ufffd٠o\ufffd\ufffdz\ufffd`*\ufffdڔ\"ɥsg\ufffd\ufffd\ufffdQ\ufffd\u0002\ufffdxsc\u0001/\ufffdn\ufffdџ\ufffd\ufffdv\u0002\ufffdI\ufffd\ufffd\\ϔ\ufffd^\ufffd\ufffdX\ufffdy\ufffd\ufffd0\ufffdX\ufffd\ufffd\u0000\ufffd\ufffdN$\ufffd\ufffd}\ufffd\ufffdT\ufffd\ufffd\u001b+\ufffdr^\ufffd\u000b\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd]\ufffds\ufffd\ufffd0D\ufffd`̍72p\ufffd*ىBX\u001e\u0008S\u0016\u0006\u0010\ufffdi\ufffd%\u0000\ufffd\ufffdY\ufffd\ufffd\ufffd\ufffd\ufffd8\u0013\ufffd\ufffdk\ufffd\\{\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd}\ufffdѲ\ufffd\ufffd\ufffd\ufffd\ufffduu6\ufffds\ufffd\ufffd\ufffd\ufffdÇ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdW\ufffdEn\ufffd\ufffd΄\ufffdf\ufffd\ufffd\ufffd\ufffd\ufffd_\u000fg/\ufffdΓ\ufffdOZ\ufffd\ufffd\ufffdJ_\ufffdy\ufffd:;;x\ufffd\ufffd\ufffd+I\u003c\ufffd\u000eʫ\u001e\ufffd\ufffd\u0013qk\ufffdh\ufffd\ufffd񆱯\u0006\ufffd\ufffdڋ\u0006 \u0017\ufffd\u000e\ufffd\u001b\ufffd3Ŧ{\u0000\ufffdC\ufffd\u001b\ufffd2\ufffd\ufffd\u0017L\u000c\ufffdۉŃ\u0000\ufffdg\ufffd\ufffd\ufffd\u0006\ufffd\u0004\ufffd0\ufffd\u0002\ufffdԘ\ufffd\u0019\ufffd\ufffd2Ĵ\ufffd\ufffd\u00062\u001b\ufffd9\u000et#8\ufffd\ufffd\ufffdb|\u000bى\ufffd\ufffd\ufffd\ufffd\u0014;\ufffdh\u000f\ufffd\ufffd\ufffd[\ufffd\ufffdf\ufffdy\u000bm\ufffd\ufffd\u000b\ufffd\ufffd(:,\ufffd݉\ufffd\ufffdB\ufffd\u0026\u003cmp\ufffd[$s\ufffd\u0012O\ufffd\ufffdֳ\ufffd{\u0004\ufffd\ufffd\ufffd\ufffd\u0004Ȕ\ufffd\u0013\ufffdaMZ'\ufffdmU\ufffd\u001a\ufffd\ufffd]6\ufffd\ufffd\ufffd\t\ufffd\ufffdUڱ9c\u001e)\ufffd\ufffd+\ufffdױ%g\ufffd\ufffd\ufffd\ufffd[r(ol\ufffd\ufffd\ufffd;\ufffd\ufffd\ufffdo\ufffd\u0012\ufffdє\ufffd\ufffd\ufffd\u0026\ufffd$\ufffd.\ufffdH'=\ufffd\u0014\ufffd(\ufffd#i\ufffd\ufffdbz\ufffd\ufffd־\ufffdN\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdv\ufffd\u0018\ufffd\ufffdX\ufffd\t\ufffd\ufffdbʽD\ufffd.\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd{\ufffd}[\ufffd\u001bp\ufffd\u000b\ufffd\u001aU\ufffd.\ufffd\ufffd1ӽ\u0013j_\ufffd\ufffd4\"\ufffd\ufffd\ufffdXߏ\ufffd\ufffd\ufffd\ufffd\u0015\ufffdM\ufffd\ufffd\ufffd\ufffd\u0018\u0001'\ufffd\ufffd\ufffdJ)\ufffd0\ufffd\ufffd;\ufffd\ufffd\ufffd2t\ufffd\ufffd\ufffd\u001f\ufffdw\u0016\ufffd\ufffd\ufffd#o\u0016\ufffd\\a7\ufffd6j\ufffdbA\ufffdb\ufffd\ufffd\u0013f\ufffd\u0003\ufffd\ufffd\ufffdQ\ufffdt\ufffd5\u0001nI\ufffd\u001b\ufffd7\u0014\u00167fqjF\ufffdB\r\n%\ufffd\u00114\ufffd\u0013R\ufffdβ\ufffd@\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd:\ufffd\u000f\ufffdy(\ufffdG\u0015\ufffd\ufffd%t\ufffd\ufffdp\ufffd)\ufffdx=\ufffduq\ufffd\ufffd\ufffd\u0016N\ufffd\ufffd-o\ufffdC\ufffd\u000c-N\ufffdغ\ufffd\ufffd\n\ufffd\ufffd\u0005m\ufffd4\ufffd\u000c\ufffd\ufffdL\ufffd\ufffd[t}\ufffdz~ݿ\ufffd\ufffd\ufffdl\ufffd\ufffd\u001d0}u\ufffd\u0015+\ufffd\ufffd\ufffd\ufffdy\ufffde\ufffdnIv\ufffd\ufffd+\ufffd\ufffd\ufffd\u0010\t4\ufffd:b4k\ufffd3P?\u0015pS\ufffd\ufffd\ufffd\ufffdsis\ufffd\ufffd\u001b\ufffd\ufffd42\ufffd\ufffd\ufffd$\ufffd'\ufffd 2=\u003c\u0012O\ufffdE\rF\ufffd\ufffd\u003c:\ufffd\\gs\ufffd̜\ufffdb\u0011[K\ufffd\u0016\ufffdP\ufffd\ufffd\ufffd'$\ufffd\ufffd|=\ufffdP-\ufffdJ\ufffd\r!\ufffd\ufffdG#\ufffd\u001a\ufffd\ufffd\u0003\u0011\u0005ȔUB.\ufffd۱8\u0017+Yz.\ufffdU\ufffdS\ufffd/\ufffdP\u0011\ufffdLz\ufffd\ufffdSԲ\ufffdA\ufffdҟ\n\u0017ſ\u000c\ufffdgE*\ufffd\ufffdx#\ufffd!\ufffdqb\ufffd\u003cwu\u000e\ufffd\ufffd\"\ufffd\ufffdz-\ufffd=^C\ufffd5\ufffd\ufffd\ufffd\ufffd\ufffd?R1#\ufffdH\u0001\ufffdL*\ufffdʡ\ufffd\u001b\ufffdw\ufffd\ufffd\ufffd\u0006\u0026\ufffd\ufffd\ufffd2S*\ufffdH\ufffd\u0005VJxY\ufffdFa,\u001eA\ufffd\ufffd\u000e\ufffd\ufffd\ufffd\ufffd̘\ufffdV\u0002=\ufffd*]xQ)S\ufffd\u001b\ufffd`|a\ufffd*#*\ufffd\ufffdX\"\ufffdR;\ufffd\ufffd\"\ufffdTr%\ufffd\n=U\u0010\ufffd\ufffd,\ufffd\ufffd\ufffd\ufffd\ufffd4 y\ufffd8\ufffd\ufffd;\u0016(\ufffd\ufffd\ufffd\u0016\ufffd\u000f\ufffdc\ufffd+E[\ufffd#W\ufffdh\u0004:F\ufffd\u0019\ufffddC\u0026\ufffd\ufffd\ufffd|%\u001bh\ufffdx\ufffdR\ufffd\ufffd6\ufffd\ufffd\u0002\ufffd\ufffd\u0010\ufffdӨ\ufffdB^\ufffd2%6\ufffd`\ufffd\ufffdQ\ufffd\ufffd9\ufffdi\u0002/\u0019\ufffd\ufffd6i5\ufffd\nK\ufffd%:9+~\ufffd0\ufffd\u001d2]X\u001a\ufffd\ufffd\ufffd_\ufffdݭR\ufffd\ufffdK\ufffd\u003c\u0018\ufffd\ufffd\ufffdT^\ufffdF2\ufffd\ufffd49F\ufffd/=\ufffdvЊ*ɔ$5\u0018\ufffdp\ufffdN\ufffd+k\ufffd\ufffd\ufffdV\ufffd\ufffd\u0003a`G\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd-Iۿ\ufffd\ufffdw\ufffd\ufffd\ufffd2U\ufffd`x\ufffd\ufffd\u00131k\ufffd\ufffdrؚa\u0005\ufffd\u000c[\ufffdT)*c\ufffd\ufffd\ufffdJ\ufffd$\ufffd\u00120S\ufffde\u0001\ufffd8\ufffd\u0010\ufffd\u000b\ufffd\ufffd@E\ufffd\ufffdC\ufffd\ufffdN\u0000\ufffd%\u000b\ufffd\ufffdy5\u001dcd\u0018 \ufffd\u0010\u000c\ufffd/,\u000cʁ\u0026\u003e\ufffd\ufffdgƣ\ufffds\ufffdB\u001fjG+\u001d\ufffd8\ufffd\ufffd\ufffd08\ufffdFW\ufffd]\ufffd\u0001\ufffd8v\u0002\ufffd\u000f\ufffd\\F\"95\ufffd\ufffd\ufffd\ufffd!CD\n\ufffd\u0000\u0014\ufffdഠ\ufffd\ufffdg\u0001\ufffd\ufffd\\\ufffdWb.\ufffd\u0016k\ufffdFL\ufffd\t\ufffd\ufffd\ufffdӲ\t\ufffd\ufffd\ufffdt\ufffd*d2ca\ufffd\ufffdf\"Χn\ufffd\ufffd\ufffdY \ufffd\u00042^\ufffdfEk\ufffdbӚε@\ufffd\u0013bH\ufffd\ufffd\ufffdΌ\u001c2\u0008p\ufffd\ufffd]VL{\u001c\ufffd\ufffdA!}\ufffd\ufffd]\u0017\ufffd\ufffd\ufffd\u0005)ׇʒf\ufffd\ufffdj5\u001b\ufffd\ufffd\ufffd\ufffd\u0002~\u0012\ufffdӀ\ufffdaD\ufffd--\ufffd_\u0003(\ufffd\ufffdKl\ufffd\ufffd\u0011\u000c\u0004\ufffd\ufffdmx®\ufffd\ufffd!ۊ\ufffd\u0026\no\ufffd1, \ufffd\ufffdD\u0008{@\"\"\u0003\ufffdy\u0013\ufffd\ufffd\u0002b\ufffdo\ufffd\u0004re-\ufffdsc;\u0006\ufffd38E\ufffd\rǭ\u0010\u000c\ufffd\u001c=\u0007\ufffdT\ufffd\ufffdk\ufffd\ufffd.\ufffd\u0026_\ufffdy\ufffdکI\u0017\ufffdH\ufffd/K\ufffd8\ufffd\ufffd:\ufffd\u000bf\ufffd0 [ J\ufffd\ufffd\u0005m\u0002W\ufffd\ufffd\ufffd\ufffd|\ufffd\ufffd.\ufffdCԕW}\ufffd\ufffd\ufffd}\u00151\ufffd\u003e\ufffd\ufffd\ufffd$\ufffd\ufffdQ\ufffd\ufffdHn,\u0006\ufffd\u00036|aXk\ufffd\ufffd\ufffdj\ufffd\ufffd\ufffd\ufffd$\ufffd\u0018@\ufffd`\u000bC\ufffd\ufffd\u0008\ufffd?*\ufffd`\ufffd\ufffd\ufffdiz=\ufffd\u001b\ufffd֕\ufffd+\ufffd\u0016P\u0005Ŕ\ufffd)\ufffd\ufffd\u0003\ufffd\ufffdw\ufffd|(\ufffd\ufffdx!S\ufffd\ufffd\u0000\u001e\ufffd\u0012\ufffdd\u0008\ufffd\ufffd!HєY\ufffd\ufffd\u000bc:!\ufffd8#\ufffd\ufffd\ufffd\ufffd\u00033J@i)\ufffd\u0010BD]f\u0026o\ufffdz\ufffdb\u001agFb\ufffd\ufffd\u001a\ufffd\ufffd\ufffd4\u0026\u000fFR\u000cC+\ufffdƾ\ufffd\ufffd\ufffdo\ufffdku\ufffdeI\ufffd\ufffd{\n\ufffd\ufffd\\\u0016\ufffd\ufffd\ufffd\u003c\ufffd\u000bVl\ufffd\ufffd\ufffd\n\u001cϮ\u0003\u0019\ufffd\ufffd\ufffdH\ufffd\\\u0026\ufffd\ufffd\u0008\ufffdz\ufffd\ufffdx9\ufffdY\ufffd$D!\u0018Й\ufffdI\ufffd\ufffd6ף\nP\ufffd\u0019\u0016\ufffd\ufffd\ufffd\u0007\u003exf\ufffd \u0008\ufffd\ufffd\ufffd\u0005\ufffd\u0004w)\ufffdϠ̑eC\ufffd\u0007r\ufffd\u001b S\ufffd\u0005Q_\u000bS\ufffd\ufffd\ufffdN\ufffd\u0004\ufffd\ufffd$넅\ufffdb\u0010\ufffd\ufffdf\ufffd\ufffd%\ufffd\ufffd\ufffd\u000bF%\ufffd\u000eXI2\ufffd\ufffd1`\ufffdw\ufffd\ufffdr\ufffd\ufffd\ufffdԔ\ufffd(\ufffd\ufffdW\u0014t\ufffd\ufffd**\ufffd\u0000\ufffd\u00172\ufffdxR!-\"\ufffdk\ufffd2g\ufffd\ufffdT;S\ufffd\ufffd\u001b1\ufffd̞\ufffdJp\u0008\ufffdm\ufffdU1\u003e\u0007\u0001\ufffd)ڍ\u000e\ufffd\ufffd𘠂\u0000\ufffdB\ufffd9\u000f\u0018\ufffd\ufffd\ufffd\u0014\ufffdK\ufffd\ufffd8*\ufffd\u0004\u0006\ufffd\u0013֘\ufffd\ufffdi\ufffd\ufffd\ufffd\"\ufffd\ufffdt\u001c+'\nFe,T\ufffd\ufffd\ufffd\ufffd\u000c\ufffd\ufffd\ufffdg \ufffd\u0014BLp\u0012B\ufffd\ufffdLQ\ufffd\ufffd$4\ufffd~\ufffd\ufffd6\ufffd^\ufffd\ufffd\ufffd\ufffdI;-@\ufffd2(E\ufffd6\ufffd\ufffd\ufffd\ufffd1J\ufffdy\u001a\u00177\ufffdץ\ufffd\ufffd\ufffdy[Ǡ\ufffd\ufffd|\u003e\ufffd(\u0007\ufffdY{\ufffd#\ufffd#\ufffd-j\ufffd\u001f{\ufffd\u0013qަ\ufffdX4 \ufffd\ufffdP\u0008yJ`\"\ufffdЁ\r8\ufffd\u003e\ufffd\ufffd\ufffd\u0026\ufffdIr]͌\ufffd\ufffd\ufffd\u000f\u0011f\u001c\ufffd\u0002I\ufffd\u0019\ufffd|\ufffdp\ufffd\u0017\\\u000f\ufffd\ufffdq)c\ufffdX\ufffd\ufffd\ufffdl\n\ufffd\u0015p\u0010У]\ufffd\ufffd'!ER\u0015\u001a\u0012\u0004D%\ufffd\ufffdB\ufffd\ufffd\ufffd t \ufffd\u0015\ufffd#\ufffd\u001eV\ufffd4\u0008\ufffd\u0014a\u0015\ufffd̦u\ufffduB\ufffd\ufffd'\u0008D\ufffd\ufffd\ufffd%\u0005\ufffd\ufffd\ufffdw+*\ufffd\ufffd\t\ufffd\ufffd\ufffd'\ufffd\ufffd \ufffd1\ufffd\ufffd\ufffdNFe\ufffd\ufffd\ufffdg\u000f\ufffd\ufffd\u0014\u0015I]\u0015\u001c\ufffdh\ufffd\ufffd\u0010\ufffdP\ufffdH\ufffd1fo\ufffd\u00011\u0002r\ufffd7G\ufffd\u000f\ufffd`\ufffd'\ufffd\r[\u003e\u0010/\rj1\ufffd\u0000{\ufffd\ufffd\ufffd\ufffd-\u0004^\u0012\u0004\ufffd$\ufffdD4\ufffdN\ufffd_\\\ufffd\ufffd\ufffd\u0010\ufffdo\ufffdaN\ufffd\ufffd\u001eu\ufffdS\u0011t\u003c\u0026\ufffd^1/m\ufffd\ufffd\ufffdg\n\u0018\ufffd,\ufffdV1w(\ufffd:d\u0005\ufffd\u001a2C}À\ufffd\ufffdĺ\ufffd\ufffd\ufffd΄\u0013\ufffd\u0013\u0012\ufffdy\ufffd\ufffd\u001b\ufffdU\ufffd\ufffd\ufffd\ufffd\u001dT\ufffd\ufffdܤ,8fõU\ufffd~4\u000b.\ufffd6(\ufffd\ufffd\u00083\ufffd\u001b\ufffd\u0008\u0002\ufffd`\ufffd\u0017¾\r\ufffd\ufffdU\u0002\u0017c\ufffd\ufffd\ufffd\ufffdF\ufffd;\ufffd\ufffd\ufffd}\n\u001e\ufffd\ufffd\u000c\ufffd\ufffd2:\ufffd\ufffd+\ufffdC\ufffdL\ufffd8\ufffd\u0005]\ufffd\\8\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd@uȲĂk\ufffd\u0018|\u001fJ\ufffdЪ\ufffdk\ufffdmd\ufffd\ufffd1Ss\ufffd\ufffd\ufffd\ufffdX\ufffd\ufffd\ufffd\ufffd:\ufffd\ufffd/\ufffd\ufffdZU\ufffde\ufffd\n\ufffd\tz\u0002*\u0013\ufffd\ufffd\u001dۈ\ufffd\ufffd\"\ufffd(\ufffd޹5E?\ufffd\ufffdX\u0016\ufffd\u0013\ufffdR.\ufffdv)e\u000c\ufffd\u000b\ufffd\ufffd^6\ufffd\ufffd\u0005\ufffd\"\ufffd\ufffd@\ufffdK\u000cf\ufffd~2\ufffdJ*\u001a\u0010\ufffd\ufffd2Ĺ\ufffd\ufffd9\ufffd\u003ePQ\ufffd\ufffdH;RJ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd)\ufffd\ufffdݮc\ufffd\ufffd\u0013\ufffd$\ufffd\ufffdu\u000e\ufffd\ufffdG~w}\u0014q\ufffd\u0008S.%\u0002\u0008\ufffd_\u000e\ufffdc}\u001e\u001aL\ufffd\ufffd\ufffd?\u0017\u001a\ufffd\ufffd\ufffdCY\u0019w\ufffd\u003cӕ\u0013\ufffd\ufffd\ufffdq\ufffd\ufffdd\ufffdY\ufffd.V\ufffd\ufffd\ufffdt@qK/Ȳ\ufffd\r\u0010Wl7\ufffd\ufffdO\ufffd\ufffd\ufffdu-\ufffd\u001d\ufffd\ufffd2\ufffd\ufffd\ufffdo?\ufffdn\ufffdo\ufffd\ufffd\u001fFߞQ\u0004e_D\ufffd\t\u0006]r\ufffdD\ufffd/|\ufffd\ufffd\u0019\u003e\u0015z\ufffd_\ufffdpPT捈\ufffd[\ufffd\ufffd\ufffdö Q\ufffd\u0007\ufffdWtK\ufffd\ufffdF\ufffdЈho\ufffd\u0026\ufffdS\u001c\ufffd$\ufffd\u000f?\ufffdg\ufffd\ufffd\ufffd\ufffd1\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd3T\u003c\ufffd%r3\ufffd\u0008\ufffd\ufffd\ufffd\ufffdx\ufffd*\ufffd\ufffd\u00192\ufffdoR\ufffd\ufffd\ufffdJ\u0018\ufffd\ufffd\u001b\ufffd*\ufffdL\ufffdю\ufffdb\u0015\ufffdV\u003c\u0026E\nJ\ufffd\ufffdH!E\ufffd-e\ufffdBQ7\ufffd}(\u000b\ufffd\ufffdn\ufffd靟k\ufffd\u001fx)\ufffd\ufffd\ufffdO{\ufffd\ufffd\ufffd\u0003\u0008\ufffdz:\ufffd:;\ufffd\u003c\ufffd\u001f\ufffd\ufffd\ufffd\ufffd_\ufffd\ufffd\ufffds\ufffd\ufffd\ufffdI\ufffd\ufffdJ\ufffdV\ufffd/~\ufffdNQ\ufffdu)\ufffd,ܣ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd1j\ufffdG\u0003f\ufffd\ufffd\ufffd\ufffdI\ufffd\ufffd\t'\u0007\ufffdg\ufffdڡ\ufffd\ufffd\u001f\ufffd\ufffd硛ژ\u001b\ufffd\ufffdi\ufffd\ufffdtʽ\ufffdK\ufffd\rh\ufffd\u0005ێ\ufffdoS\ufffdzj\ufffd\ufffdV\ufffd\ufffd5\t\ufffd^\ufffde\ufffd\ufffdhz\ufffdek\ufffd]\ufffd%s1\u0012\ufffd\ufffd\ufffd=.\u0014\ufffd\ufffdXP+\u001c\ufffdD\ufffd\ufffdh\u0026\u0004\ufffdc\ufffdf\ufffd\ufffd\ufffd\ufffd#ǭK\ufffd\ufffd\u0016\ufffd\ufffdN\u0006B)N\ufffd\u0002s4\ufffd^'(\ufffd\ufffd}S\u001f\ufffd\ufffdv\ufffdtaq\ufffd\u000eL(\ufffdt1\ufffd\ufffd\ufffd\u003c‚U|V\ufffd\ufffdĠ\ufffd\ufffd\u0019\ufffdP\ufffd'\ufffdt\ufffd\ufffd'(\ufffd\ufffd\ufffd\u001d\ufffd߸넽0\ufffd\ufffd\u0007w\ufffd\ufffd\ufffd\ufffdF]\ufffd\r\ufffdv\ufffd\ufffdZ\ufffd\ufffd\u0018\ufffdZ\ufffdrDM\ufffd\ufffd$\ufffd\ufffd\u0012\ufffd$c\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd[\ufffd\ufffdo\ufffd\u000b\ufffd\ufffd\ufffd(n\ufffd\ufffdd\ufffd\ufffdǦh\ufffdR\ufffd\ufffd\ufffd\u001d\u000f\ufffd4\ufffd\ufffd\ufffd\ufffdal$?\u0008'\u000b\ufffdx/3qv\u003e\u0010K\ufffd\ufffdx\ufffd[\ufffd\ufffd\ufffd\"\ufffde\u0017\ufffdO\ufffd\\\ufffdy\ufffd\ufffd\ufffd.|u\ufffd\ufffd\ufffd]\ufffd\ufffdw\ufffd/\ufffd߷_\ufffdps\ufffdb\u0000o\u000b\ufffd\u0014\ufffdOߵ\ufffd9\ufffd\ufffd׻\ufffd\u0001o\ufffd\ufffdh-\ufffd5\u001b,1ue\ufffdQ\u0018\ufffd\ufffd\ufffd\ufffd\ufffdY^\ufffdݿ\ufffdH\ufffd\ufffd1٨\ufffd\ufffd\ufffd\ufffda\ufffd9\ufffd~\ufffd,=\ufffd\ufffd\ufffd\ufffd\u003c}\ufffd\ufffd;\ufffd\ufffd\ufffd\ufffd܁G^\ufffdF\ufffd\ufffd\ufffd?\ufffdy\ufffdus\ufffd\ufffd?\ufffd\ufffd\ufffdU\ufffd+g\ufffdf\ufffd8ڦ\ufffd\u0002n\u0014\ufffd\u000c\ufffdA1\ufffd)5u\ufffd\ufffd\ufffd\ufffd{\ufffd\ufffd\u0016hIG\ufffd\ufffd\u0011:\ufffd\ufffd\ufffd\ufffda0\u001a\ufffd\ufffd\u0015\u0016Y\ufffdM\u000fu \ufffdU:B\ufffd\ufffd\u001a\ufffd\ufffdY\ufffdU\u003c\ufffd\ufffd\u0019\ufffdo\ufffdt\ufffd\ufffd\ufffd\ufffdiU\ufffdBډ\ufffd=\ufffdJ\ufffd\ufffdC\ufffd\ufffd+\ufffd\ufffd\ufffdS\ufffd\u0013\ufffdn\ufffd*4\ufffd3P~\ufffd=}\ufffd=\ufffd\ufffd繓\ufffd\ufffdw\ufffd}\ufffde=\ufffd\ufffd\ufffd]\ufffd\ufffd\ufffdt\ufffdܭ\ufffd\ufffd\n_߼\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u000b?q\t\n\ufffd\u0002\u0005\ufffdڧ\ufffd;Hv\ufffdk+[\ufffd\ufffd\ufffdn-f\ufffd\ufffds\ufffd\u000b\u0012H\ufffd\ufffdGYr\ufffd]\u0002\ufffdUe\ufffd\ufffd9\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u000cܧ\ufffd\ufffd\ufffdy\ufffdl\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd]\ufffd\ufffdW\ufffdq\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd48\u0019\u0017\ufffd\ufffd\ufffd\ufffd\ufffd.O\ufffd\ufffdL\ufffdL\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0001j\ufffdl#\u00024\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 3942\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:13 GMT\r\nSet-Cookie: AWSALB=85BJo1Zpdk9Zp+yPBrFftDnRpkda2GE1/JD01eSP9Ex//O3wSyqO/AF6ykA8rziJvN/Q7QNg9UV1oNa53YrhOnHvoGPe6yk0hzUoN1V5Rcp/SYNljF/y+T7fkl7C; Expires=Thu, 14 Sep 2023 15:34:13 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=85BJo1Zpdk9Zp+yPBrFftDnRpkda2GE1/JD01eSP9Ex//O3wSyqO/AF6ykA8rziJvN/Q7QNg9UV1oNa53YrhOnHvoGPe6yk0hzUoN1V5Rcp/SYNljF/y+T7fkl7C; Expires=Thu, 14 Sep 2023 15:34:13 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:04:18+05:30","url":"https://ginandjuice.shop/about","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=FuPzkvEYylog+dQhM7EDdfOxlO4LNNlDYYFUu86LrSuM/kx3kGryxBucqnFtsBKkCp4hyX30SKkFqbN+LcVwM135w3aUgOugwu1lG3qZ1Hig6rAW0C0h0PS0EdtD; AWSALBCORS=FuPzkvEYylog+dQhM7EDdfOxlO4LNNlDYYFUu86LrSuM/kx3kGryxBucqnFtsBKkCp4hyX30SKkFqbN+LcVwM135w3aUgOugwu1lG3qZ1Hig6rAW0C0h0PS0EdtD","Referer":"https://ginandjuice.shop/blog/post?postId=3","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/about","scheme":"https"},"raw":"GET /about HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=FuPzkvEYylog+dQhM7EDdfOxlO4LNNlDYYFUu86LrSuM/kx3kGryxBucqnFtsBKkCp4hyX30SKkFqbN+LcVwM135w3aUgOugwu1lG3qZ1Hig6rAW0C0h0PS0EdtD; AWSALBCORS=FuPzkvEYylog+dQhM7EDdfOxlO4LNNlDYYFUu86LrSuM/kx3kGryxBucqnFtsBKkCp4hyX30SKkFqbN+LcVwM135w3aUgOugwu1lG3qZ1Hig6rAW0C0h0PS0EdtD\r\nReferer: https://ginandjuice.shop/blog/post?postId=3\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"2591","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:19 GMT","Set-Cookie":"AWSALB=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB; Expires=Thu, 14 Sep 2023 15:34:19 GMT; Path=/, AWSALBCORS=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB; Expires=Thu, 14 Sep 2023 15:34:19 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdZ\ufffdr۸\u0019\ufffdߧ\ufffd\ufffd\ufffd(\ufffd\tŵ\ufffd6\ufffdF\ufffdL\u000e\ufffd\ufffdns\ufffd؝4\ufffd\ufffd@$$\ufffd\u0006\t\ufffd\u0000%\ufffd\ufffd}\ufffd\ufffd\ufffd5\ufffd(}\ufffd~?@ɔDIT\ufffd̴\ufffdx\u003c6\ufffdÏ\ufffd|\u0002\u0007\ufffd?{\ufffd\ufffd\ufffd\ufffd۟Xj35\ufffdn\ufffd\ufffd1\ufffd\u000cR\ufffd\u0013\ufffd\ufffd^\ufffd̯XZ\ufffd\ufffd0*\ufffd\ufffdU\u0019\u000b\u0013)\u003e\ufffde\ufffd\ufffdbc\"\u0013\ufffd\ufffd\ufffdx\ufffd\u0006\ufffd\u0018`\ufffdPCc\u0017J\ufffdT\u0008{\u0008\u0018\ufffd\u0000@s\u000e0\ufffd\ufffd\u000f \u0013\ufffd\ufffd\ufffdgb\u0018̤\ufffd\u0017\ufffd\ufffd\u0001\ufffdunEn\ufffd\ufffd\\\u00266\u001d\u0026b\u0026c\u0011\ufffd\ufffd\ufffd\ufffd2\ufffd\u000c\ufffd!NPb\ufffd\ufffd\ufffd\u0001\ufffdĥ,,3e\u003c\u000c\u001a\u0008]\u001a\ufffd\ufffd\ufffd\ufffd\u0001I(]d\u0000޿4\ufffdh\u0010\ufffd\u001d\ufffdA\ufffd\ufffdΎ\u0000c\u0017\u0005(\ufffd\ufffd\ufffdF\ufffd|\ufffd\ufffdh\ufffd\u0006\ufffd\ufffd\ufffdJ\ufffd\ufffd\ufffdI\ufffd0|\ufffd\u000b\ufffd\ufffdV\ufffdћ\ufffdd\ufffd\ufffdr\ufffdB\ufffd\\\ufffd\ufffd\u000eϊG\ufffd\ufffd\nLb\ufffd\ufffd.\u0006\ufffd_\ufffd\ufffd\u001f\ufffd\ufffd0\ufffdɂ\ufffdӐ\u0017E\u0003j\"gL\u0026à)\ufffd\u0006W==\"\ufffdR\ufffd,Vܘa\ufffd\ufffd%L\ufffdG\u00113\u001b\u001bܦb{\ufffd~.Ri\u0018~9K\ufffd\ufffdcQr+Ԃ\ufffd*\ufffd\ufffd\u0019Res1f\ufffdQɘ\ufffdSq\ufffd\ufffd\ufffd\"a\u0013]2+\ufffd\ufffd\ufffd\ufffd\u0016\ufffd\ufffdǦx\ufffd\ufffd(\ufffd\ufffd\u000b?D\ufffd`\ufffd\ufffd[\u0011\u0018\ufffd\ufffd\ufffdH!\u0015\u000e\ufffd\ufffd3\r͂DEIz\ufffd'z\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\r\ufffd\ufffdjZ\ufffd\ufffdgO\ufffdf3L7g\ufffd\ufffd\ufffdr\u001a\ufffdys\ufffd\ufffd\ufffd\ufffd\ufffd\u0018\u0006r\ufffd\ufffd6\ufffd\ufffds\ufffd5\ufffdZ{=[}p\ufffd)]skȸ\ufffd㻽\ufffdg=\u0019?\ufffd\ufffd\ufffd\ufffd\ufffd%]$ϕ\ufffd\ufffd\ufffda\ufffd\ufffd\ufffdT%8\ufffd\u000c9\u0008+\u001a\ufffdN\u001e\ufffd\ufffd\ufffdӖ\ufffd\ufffd\ufffd\ufffd\u0007\ufffd\ufffdme\ufffdB\ufffd\ufffdz90\ufffd\ufffdM\ufffd\u0013\ufffd\ufffd\ufffdk\ufffd\ufffdE]\ufffd\r}\ufffdB\ufffdu{u\ufffd\ufffd\ufffd\ufffd\ufffd;\ufffd\ufffd\u0002\ufffd\ufffd\ufffd/%\ufffd\ufffd%\ufffd\ufffd\ufffd\ufffd\ufffd{W֋Z\ufffd\ufffd\ufffd3ο\ufffdJO5\ufffd\u0015\ufffd;\ufffdF|\ufffd\ufffd\r(g\ufffd\ufffd\u0001\ufffdܞ\ufffd\ufffd8f\ufffd\ufffdRK\ufffd\ufffdZ9u:\u0011\ufffd\u0006K\ufffdnXQʌ\ufffd\u000b\ufffd\ufffdb\ufffd[ \ufffd\u003c\ufffd\ufffd-\ufffdˣǕ\ufffdp\n\ufffd\ufffdA\u0004\ufffd\ufffd\ufffd\u00134\ufffd\ufffdI\u0015[\ufffd\ufffd)k\ufffd\ufffd.\u0007\ufffd\u001e\ufffd\ufffdC\ufffd\t\ufffd\ufffd7\ufffd\ufffd\ufffd,c+\ufffd\u0015~|\ufffd+\u001b\ufffd\ufffd\ufffd/\ufffd\ufffd \ufffdԭu\tƨ\ufffd\ufffd+k\u0013\ufffdc]\ufffd6\ufffdqCj\ufffd\"\ufffd\ufffd\u0011+\ufffdl:\u001a\ufffd\ufffd-'ob+T}J\ufffd\ufffdu\ufffd\ufffd\ufffdտY\ufffd\t\ufffd\ufffdf\n\ufffd\u0004\ufffd\u000b{k\ufffdt[x\u0014\ufffd\ufffd\ufffdo\ufffdk\ufffd\ufffd\ufffdz\ufffdd\ufffd\u0019c\u0007\ufffd\ufffd\ufffd~]\ufffd_-X\ufffd\ufffd\ufffdP?\ufffd\ufffd\ufffd\ufffd}\ufffd\ufffdƼ\ufffd\ufffd\ufffd\ufffd\u0003F4C\ufffdJ!\ufffd\ufffd\u000e\ufffd\u001d\ufffd\ufffd\ufffd:ս\ufffd~\ufffd\ufffdv\u0026\ufffda\ufffd\ufffd\ufffdө\u00128\ufffd\ufffd\ufffd\ufffdr:\ufffdT\ufffdS\ufffd\"f\ufffd\u000b\ufffd\ufffd\ufffdW\u001aD;\u0003\ufffd\ufffd\u0010\ufffd2ܒXl\ufffdr\ufffd\u0019\u0019\u000b\ufffd%\ufffd\ufffdݛ\u000fg\u0008۫\ufffdݖ\n߄\ufffd`\ufffd\u000e9pX@\ufffd;|\ufffd\ufffd\ufffd\ufffd\u0015ǵ\ufffd\ufffd:!\ufffd]𮍑\ufffd\ufffd1\ufffd\ufffd\ufffdK=|\ufffd\ufffd\u0013^k\u001e\ufffd\u0005\ufffdj\u0008\ufffd\ufffd9\ufffd\ufffd=q`Uo8\ufffd^\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdP\ufffdP:\u0010I\u0006\ufffd\ufffd\ufffd߱͟\ufffd\ufffd7\ufffd\u003e\ufffd\ufffd\ufffd}:\ufffd37\ufffd\ufffd\ufffd!\ufffd\ufffdן\u001bX\ufffdU\ufffd!\ufffd\u001a\ufffd\ufffd\ufffdf2\u0011:\ufffd\ufffd(hv\ufffd\ufffd\u0003\ufffd\u000e;\ufffdn\ufffdؕХV\ufffde\u00152\tVh\ufffd\n\ufffdm\ufffd2\ufffd\u0008\ufffdy\u0012^R\ufffd\u0018Z\ufffd\ufffd\ufffde1\ufffdx\ufffdg\ufffd\u0003\ufffdU\ufffd\u0015\u000f\ufffd\ufffd2v\ufffdF4\ufffd\ufffd\ufffd\u000fTX\ufffdK=70\ufffdD\u000b\ufffd`\u001a\ufffdT\u00055\u000f\\\ufffd\ufffdY`\ufffd\ufffd\ufffd\ufffdۢ r\u001b\ufffd\ufffdν\ufffd\ufffdZ\ufffd%@Nj\ufffd\u0002\ufffdٴ\u001b3\u0013\ufffdBX)Q.\ufffd\ufffd\u0018Wv\u0018PC\ufffdf\"\ufffd\ufffd\u0008U]\ufffd\ufffdY\ufffd\ufffdFc`\ufffd\u0015\ufffd[\u0006\ufffd\ufffd^\ufffdk{\ufffd\ufffd+0֕\ufffd\ufffd\ufffd\ufffd\u0002\ufffdQTe\ufffd\u0004\ufffdD\ufffdc\u0001\ufffdq\ufffdq{1z\ufffdK\ufffd\r{\u0005\ufffd\ufffd\u000b\ufffdg\ufffdY\ufffds\ufffd'\ufffdo\ufffd\ufffdX\ufffdGv\ufffdS\ufffd\ufffd#\ufffdy\ufffd\u001f\ufffdߘ\u00163\ufffd\ufffdT\ufffd\ufffd2\u0004\u0003\ufffd\ufffds,d\u0010\ufffd\ufffdu\ufffd\ufffd\u0002\ufffd\ufffd\ufffd\ufffd\ufffd,\ufffdՙ\ufffd]8\ufffdC\ufffd\ufffd\ufffd\ufffd\ufffd\u0017\ufffdY\ufffd\ufffd\u0004\ufffdU\u0002\ufffdw=\ufffdF\u0008\t#;\ufffd\ufffd\ufffd\ufffd\ufffdE\ufffd]\ufffd!\ufffd/*\ufffd\ufffd=\ufffd\u0026\ufffd\ufffd]g\u0026\ufffd\ufffdk\ufffdɸR\u001ee\u003c\ufffd,\ufffd{\ufffdΐ\ufffd\ufffd\ufffd\ufffd0l.m\n\ufffd\ufffdRp%\ufffdF}\u001ank245\ufffd\ufffds*}\u0003k\ufffd\raC\r\ufffd\u00144\ufffd\ufffdV\u0013\ufffdU\u0012\ufffdk\ufffd\ufffd\u001ci\ufffd\ufffdÑ\ufffdY\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdo\ufffd\ufffdQ\u001e൞\ufffd\ufffd/\ufffd\ufffd\u0016\ufffd2\ufffdgc\ufffd\ufffd\ufffd\ufffd}6\ufffd\t=\ufffd\ufffdk◶\ufffd\ufffd‚\ufffd\ufffdЙ̷\u0016\ufffd\ufffdnQ\ufffd\u003c\ufffd:\u0007\ufffd\u001c\u0015\ufffde̚}d)L]\ufffdx\ufffd5G\u003c\ufffd\ufffdT\u0005\ufffd5I\ufffd\ufffd\ufffd\ufffd \ufffd\ufffd0?F\u0011\u0019\ufffd\ufffd\ufffd\ufffd\ufffd\u000f\ufffdFc8\ufffd\ufffd\t\ufffd(\ufffd\u0011\r\ufffdN\ufffd\u001c\u0015U(f\\U܊\ufffd\ufffdЋp\ufffd\ufffd\u001dֺ\u0019\ufffd\n\ufffdR\n.\ufffd:kK\ufffdz\ufffdTa\ufffd\ufffd\u0013l\ufffd\ufffd\u0007\ufffd\ufffd\u0011\u001b\ufffd~\ufffdh\ufffd\ufffd\u000bͪ\u001d\ufffd;\ufffd\ufffdT\ufffd\u003e\ufffd\ufffd\ufffd)9\u0011\ufffdG(̗w\ufffdk6}v\ufffd\ufffd\u003e\ufffd,\ufffd\ufffdQ\ufffd\ufffds\ufffd3\ufffdV\u0019\ufffd\ufffdX\ufffd\ufffd\ufffd\ufffd\ufffd`D\ufffd\u003c(]\ufffd\u0018ꆪ\ufffd$\ufffd\ufffd{\u0008\ufffd\ufffd~c\ufffdc\u0014_\ufffdk\ufffd\ufffd_\ufffd\ufffd\u0014q\u000e\ufffd\ufffd\ufffd\ufffd\ufffd1\u000e\u0003_\ufffd\u001f\ufffd6uP\ufffd\ufffd\ufffdR\ufffdW\ufffd3\ufffd\ufffd\\\ufffd*\ufffd\ufffdLE\ufffd{\ufffd\ufffd\u0026\ufffd\u0000\ufffd#~\u0016\ufffd\ufffd\ufffd\ufffdޤ\ufffd\ufffd\ufffd?\ufffdA\ufffd7K\ufffd\ufffd*0!ZkL\ufffd[\ufffd\ufffde\ufffdj\ufffd\ufffd)\ufffd\ufffd䦴\u000fÊ\ufffd\ufffd\ufffd\ufffdٟ\ufffd(\ufffdyL,\ufffd\u0026P\ufffdt$\u0017s\ufffd\ufffd\ufffdH\ufffd\ufffd5\u0002\ufffd\ufffd\ufffd\ufffd\u0015\ufffd\ufffd6NE^\ufffd/\ufffd\rYEi\ufffd۶a\\\u001e\ufffdA6\u00033\ufffd\ufffd8\rFo1s\ufffdgػzԫ\ufffd{уmre4\ufffd\"\ufffd\ufffd\ufffd9\ufffd\u0004p\ufffdr\u0016i\ufffdG\u0026\ufffd\u000b\\\u0011\ufffd\ufffd\ufffdY\ufffd\ufffd\ufffd\ufffd\ufffd\u000e\ufffd-\ufffdG\ufffd\ufffd\ufffd8\ufffdH\ufffdIDD\ufffd\ufffd:\ufffd\ufffd\ufffd'\ufffd\ufffde¡\u003e\ufffd\ufffd\ufffdn=\u001c7\ufffd`\ufffdDkx\ufffd=\ufffd\ufffdf+\ufffdo\ufffd\ufffd\u0005ن\ufffdĄzy{\ufffd~\ufffd\ufffd\ufffd.$\ufffdk\ufffd\ufffd\ufffd\ufffd\ufffdP\ufffd\ufffde\ufffd\ufffdKR\ufffdV\ufffd\ufffdlL\ufffd\ufffd\u0018\ufffd\ufffd\ufffdpu\ufffd{@\u0014\ufffd_\ufffd\u0004\u0018\ufffdwΉ̑\ufffd\ufffd\u0018\ufffd\ufffd\ufffd\ufffdCy\ufffd\ufffd\ufffdԩ\u0008\ufffd\ufffd\ufffd\u001c#6\ufffd\ufffd\ufffd\ufffdXUFΜ\ufffd\u001ar\u000c\u0004Hq\ufffd6\u00116XG\u0026]Rl6\u0002o\ufffdV\n*\\\ufffd\ufffd\ufffdw\ufffdL\ufffdg\ufffd\ufffd4\ufffd\ufffd{\ufffd\ufffd\ufffd+b\ufffdo\ufffd/M\ufffd\ufffd2\ufffd\ufffd\ufffd\ufffd\u001a{\ufffd7\ufffda}\ufffd\u0005,ᖇ\ufffd@y\ufffdE\ufffd\ufffd\ufffdC\ufffd\ufffd@\ufffdE{\u0003\ufffd\ufffd\ufffd˼p\ufffdΧJ\ufffd\ufffd\ufffd)\u0013\u0017\u0019GU\ufffd\ufffdh\ufffd\ufffd\ufffdB\ufffd\ufffdRR\ufffd\ufffd\ufffd'7\ufffd\ufffd\u0010\ufffd\u001e\ufffd+i\u0005\u001f\ufffd\u0012Y%\ufffdj\ufffd\ufffdDl\ufffdI\ufffd(\ufffd\ufffdK\u003e\ufffd\ufffdT.\ufffd/\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0017\ufffd\ufffdw\ufffd\ufffd2~\ufffd\ufffd\ufffd\ufffd\ufffd'\ufffdt\ufffdsU_?mt\ufffdݱ`I\u0026\ufffd\ufffd|əA\ufffd\ufffd?\ufffd\ufffd\ufffdH\ufffd\ufffd\u0013\u0018\ufffdD\ufffd\ufffdЏ\u001c\ufffd^\ufffd\ufffd\u001d\ufffdY[\ufffd0F\u003c\u0014\ufffd\ufffdN\ufffd\ufffd\ufffd˯\ufffd\ufffd\ufffd\u000e\ufffd3\ufffd\ufffd]Xcn\u001f\ufffdz\u000f\ufffd2\ufffdt.=ӓ\ufffd\ufffd\u000f\ufffd\ufffd\ufffdN\u0018y\ufffd\ufffdM\u0011\ufffd\ufffd79\u000f\ufffd^l5\ufffd\ufffd\ufffdpV˦XP\ufffd\u0012z8\ufffd\ufffd3\ufffd\ufffd\"\ufffd\ufffd\ufffd=\ufffdr`k\t,A\ufffd`WBX!\ufffd\ufffdm\ufffd`\ufffd\u001f\ufffd\u0019\u0012Y\ufffdMC\ufffd\u0000T\ufffd$\ufffd\ufffd\ufffd\n6x֘b\ufffd\ufffd\ufffds\ufffd['\u001d\ufffd\ufffd\u0018=FY\ufffd@V\ufffd\ufffd\ufffd\u001fO\ufffd\ufffd\ufffd\ufffd\ufffdRt\ufffd\ufffd \ufffd\ufffd\ufffdU'\ufffdґW\ufffd1\ufffdB\ufffd\ufffd\ufffd֗\ufffdM\u000eFqo\ufffd\ufffd\u003e\ufffdXE=\ufffd\ufffd\ufffd\ufffd~\ufffd0\ufffdF.辱\ufffd\ufffd|\u00020\ufffd\ufffd?\ufffd\ufffduƚ9\ufffdK\ufffd\ufffd;Pv\ufffd\ufffd\ufffd\u000ejkJw^H\ufffd\ufffdsZ\u0013\u0012P??\"-y\ufffd\ufffd\ufffd?t\tSo3:\ufffdD_\u001e\ufffd\ufffd?ļo\u001f\ufffd|y\ufffd\ufffd}\ufffd\ufffd:{\ufffd\u0015p{\ufffd\ufffdq\ufffd\ufffd\ufffd\ufffd#;\ufffdF\ufffd\ufffdT\ufffd}\ufffdvz\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd;\ufffdF\ufffd\u0019\ufffd\u0000j\ufffd\ufffd\ufffd\ufffd+\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2591\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:19 GMT\r\nSet-Cookie: AWSALB=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB; Expires=Thu, 14 Sep 2023 15:34:19 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB; Expires=Thu, 14 Sep 2023 15:34:19 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:04:25+05:30","url":"https://ginandjuice.shop/my-account","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB; AWSALBCORS=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB","Referer":"https://ginandjuice.shop/about","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/my-account","scheme":"https"},"raw":"GET /my-account HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB; AWSALBCORS=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB\r\nReferer: https://ginandjuice.shop/about\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"0","Date":"Thu, 07 Sep 2023 15:34:26 GMT","Location":"/login","Set-Cookie":"AWSALB=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/, AWSALBCORS=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"raw":"HTTP/1.1 302 Found\r\nConnection: close\r\nContent-Encoding: gzip\r\nDate: Thu, 07 Sep 2023 15:34:26 GMT\r\nLocation: /login\r\nSet-Cookie: AWSALB=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\nContent-Length: 0\r\n\r\n"}} {"timestamp":"2023-09-07T21:04:26+05:30","url":"https://ginandjuice.shop/login","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm; AWSALBCORS=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm","Referer":"https://ginandjuice.shop/about","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/login","scheme":"https"},"raw":"GET /login HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm; AWSALBCORS=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm\r\nReferer: https://ginandjuice.shop/about\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"1793","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:26 GMT","Set-Cookie":"AWSALB=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/, AWSALBCORS=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdY\ufffdn\ufffd6\u0014\ufffd\ufffd\ufffd`5\ufffdi\ufffd*\ufffd\ufffd^6\ufffd2\ufffd\ufffdm\ufffd\"M\ufffd\ufffdٚ\ufffd)(\ufffd\ufffd\ufffdR\ufffd\u0026Rv\ufffdH{\ufffd=\ufffd\u000eIY\ufffdmɒ{\u0001\ufffd\ufffdA\u0010\ufffd\ufffd\ufffd\u001f\u000f\u000fϕ\ufffd\ufffd{uq\ufffd\ufffd\ufffd\ufffd5JTʦ?L\ufffd\u0007\ufffd\ufffdIBpd\ufffd\ufffd!\ufffd\ufffd\u0013Jr2\ufffd\ufffd\ufffdHQ\ufffd!\ufffd\u001eÁf#\ufffd\u0017J\ufffd\ufffd\u0010\ufffdw\ufffdWC8\u0004\u0002\ufffd\t\ufffd\ufffd*\u0019\ufffd\t!\ufffd\u000fLC\u0000\ufffd\ufffd\u0002\ufffd\ufffd\ufffd\u0006H\ufffdˆ\ufffd\ufffd\ufffdΜ\ufffdE\u0026r\ufffd\ufffdPpE\ufffd\ufffd\ufffd\u0005\ufffdT\ufffdGdNC\ufffd\ufffd\ufffd#TH\ufffd\ufffd !\ufffd\ufffd\ufffdυ\ufffd@\ufffdaN3\ufffdd\u001e\ufffdNC\ufffd[\t\u0003\u001c\ufffdC@\"Ld)\ufffd\u001f\ufffdJg:\ufffd\ufffd\ufffd\ufffd\u0010n$\ufffd=`T\ufffd\ufffd\ufffd\u0014\ufffdS\ufffd-\ufffdcKu\ufffd\ufffd1\ufffd\u000b\ufffd\ufffd\ufffd#\ufffd\ufffd\ufffd\ufffd\u000bUQ\ufffd\ufffd\ufffdLĔ#\u0017\ufffd\ufffd\ufffd\ufffd8\ufffd^\ufffd\ufffd\u0005(\u0008]%\"\ufffdx\ufffd\ufffd^\ufffd\ufffd\ufffd\ufffdI \ufffd\u0012\ufffd\ufffd\ufffdY\ufffd@\ufffd\ufffd\u001c\ufffd\ufffdw\ufffdW\ufffdШ=\u000b\t\u0015\u0015\u001c\ufffd\u000cK\ufffd;\ufffdP܈X\ufffd`fc\ufffdY\ufffdm\ufffd\ufffd\ufffd\ufffd\ufffdJ\u0004\ufffd\u0018E\ufffdр\ufffdX\u0011V\ufffdy\ufffd8|\ufffd\u001bE\u000b\u0012 \ufffd\ufffd\ufffd\u0010\ufffd]a\u001f\u001as\u0012\ufffd\ufffdȑ\"RQ\u001ek\ufffd\ufffd\u003c\ufffdً\ufffdBʨ*-I\u001f\u0005h\ufffd\ufffdU\ufffd\ufffd\ufffdp}\u0014m\ufffd\u000e\u0012\u003c\u0015`Up\ufffd$\ufffd6\ufffd#\ufffd8\ufffd\ufffd\ufffdn\ufffd|4\ufffd\ufffd\ufffd|}\ufffdhm6\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd.\ufffd\ufffdʛ\ufffdO\ufffd\u0016\u0017\ufffd\ufffd\ufffd\u0019z\ufffd\ufffd\ufffd\ufffd}\ufffd\ufffdkm8^\u001f\u003ey\ufffd\ufffd\ufffd\ufffd\ufffd#\ufffdD\ufffd\ufffd\ufffdl\ufffd\ufffd(8}\ufffd\ufffd\ufffd\r\ufffd\ufffde\ufffd)c\ufffd\ufffd\ufffd\u000f\u001eֻ2\ufffd\ufffd\ufffdp\u000fD\ufffd\u0006\ufffd\ufffd\u0005ڢ\u001d\ufffd\ufffd\ufffd-\ufffd'\ufffd\ufffd\ufffdP\ufffd\u0004\ufffdF\ufffd= %\ufffdJ\ufffd\ufffd\ufffd{`Ӡ\ufffd\u0016s\ufffd6\ufffd\u0005\u0018\ufffd\ufffd\ufffd\ufffd\ufffd\u0003k\ufffd/\ufffdF\u0007-\ufffdڼ-\ufffd\ufffd)\ufffd\u0002[\ufffd]N\ufffd\rc\u0007^\u000bL\u000b\ufffd4\ufffd\ufffde\"\u0016\ufffd\ufffd@\ufffd\ufffd\ufffdp\ufffd\ufffd\n\ufffd8\ufffd\ufffd#\ufffdY\ufffdq\ufffd65G\ufffd\ufffd\ufffd\r\ufffd466\ufffdV\u000e\ufffd\ufffd]\ufffd,\ufffd)\ufffdK;jq\ufffd-HF\ufffd\ufffd\u000c#^n\u001d\u0014JAP\ufffd\nu\u003c\ufffdL\u000cz\u0002\ufffd\ufffdET\ufffdJv*e\r\ufffd\u001b\ufffd\ufffd\ufffdK\u0017\u0018\ufffd^\ufffd\ufffd\ufffd\ufffdX8\u0010\ufffdr\ufffd\u0017ڏ\ufffd\ufffd˯$\ufffd\ufffd+\ufffd\u0017\ufffd\u0010\ufffd\ufffd\ufffd\ufffd76\"\u001c\ufffd\ufffd\ufffdʥ\ufffd\u0017\ufffd\ufffd*T$\ufffdՓ\ufffdn\ufffd\u0000yR\ufffd\ufffd\ufffd\u0004\u0002\ufffdrr\ufffdW\ufffd\ufffdc\ufffd\u0001\u0018K\ufffdC\ufffd\ufffd\ufffd\t\ufffd\u0013-,\ufffd\ufffdV\ufffd\u000c\ufffds\ufffd\ufffda\ufffd{\ufffd[\u000f\u001bv\ufffd\u003c8\ufffd\u0001\ufffd\ufffd\ufffd\u0012\u001b\ufffdA\ufffd\ufffdm\ufffd~W\ufffd\ufffd\ufffd7\u0012\ufffd\ufffdȇ\ufffd}\ufffd\ufffd\ufffd8\ufffd,k#\u0002zzF۫N\ufffd\ufffd*\u0015\u000e\ufffd^\ufffdԘ\ufffd\n}\ufffd\u001d|\ufffdF\u0004p\ufffd\ufffdcF`7\ufffdF\ufffdi\u001c\ufffdRў\ufffd\u003eL\ufffd\u0000\ufffd\ufffd\ufffd\ufffd4\ufffd:\u0013`G\nn!\ufffd\u0014\u0016\u001b\\\u00265CŢ{\u0012\ufffdK\ufffd\ufffdz8\ufffd\ufffd]g\ufffd\ufffdRx\ufffdꝚ\u000fj`7\ufffd\ufffd\ufffd\u0008\ufffd\u0013\u001bpk\ufffd\u000bEgUA\\\ufffd⮅\ufffd\ufffd\ufffd\ufffd\u001d\ufffd\ufffd\u0001\ufffdF\u001d,\ufffdu\ufffd\u001a\u0007\u0014\ufffdi%\ufffdяk\u0008\ufffd\ufffd%\"\ufffd//\ufffdރk\u00080\ufffd6\ufffdm\ufffdQ\ufffdA͘\ufffd\ufffd\u000b\ufffdC\ufffdo{\ufffd\ufffdF\u0011\u0001\u0017\ufffd\ufffda(\ufffd\ufffd\ufffd\ufffd\ufffd\u001508M\ufffd\u000fD\ufffd\ufffd8\u003e\ufffd\ufffd\\\ufffdQ\ufffd\ufffd\ufffd\u0014o.\ufffd\ufffd\ufffd\ufffd\ufffdϮ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u001ad0\ufffd\ufffdNR\ufffdO\ufffd`\ufffd\ufffd\ufffd\ufffd\ufffd]\ufffdi\ufffd\ufffd\n\ufffd\u000e\u001e\ufffd;\ufffd1\ufffd\u0018\u000eI\"X\ufffd;\ufffd%\ufffd\ufffdq\ufffd\ufffd\ufffdW'*j6\\(1\u0013a!{\ufffd\ufffd!\ufffdR|50Ȳ\u0008R\ufffd\ufffd\ufffd\ufffd\u00030\u0001;\ufffdˑ\ufffd\ufffdu\u0019P\ufffd\u0001\u000c\ufffd\u0026Cn\ufffd\ufffdL\u0008\ufffd\ufffd\ufffdE\u000eM]\ufffdl\ufffd\ufffd]\ufffde\ufffd\ufffdȌ\ufffdtT\ufffdQ\ufffdo\ufffd\tM\ufffd\ufffd\ufffdP\u0026\ufffd\ufffd\ufffd\u001c\ufffd\ufffd\u001c\ufffdT\ufffdv\u00153\ufffd\ufffdA׺\ufffd\r\ufffd\u001a\ufffd\u0002\ufffd\ufffdh\u0007D6}+\ufffd\u003et/\ufffd\u00109\ufffd\u00164\"(\u0014iZphV\ufffd\ufffd!\ufffd\ufffdc\u001e\ufffd[ӿ\ufffd0\u0007\u000f\u000f\ufffd#\ufffd꒻\ufffd\u0015\ufffd\ufffda'\ufffd\ufffdд\u0019 \ufffdu\u001fe\ufffd\u0001\u003e\ufffd\ufffdO+\u0004K\u0002\ufffdP0\u0006ucn\ufffd\u0004\ufffd5r*rr\ufffd\ufffd]\ufffd\ufffd\ufffd\ufffdq\ufffd\ufffd\u0026Q\u001f\ufffd\u001c\ufffd\ufffd[y\ufffd\ufffd$Ѫk\ufffd\ufffd\ufffd\\\ufffd\ufffdT\ufffd\ufffd\u000e\ufffd \u0017\ufffdU\ufffdp2!UE\ufffdC\ufffd2Y\ufffd\u0000\u003e#|\u0010\u0008\ufffd\ufffdz\ufffd\ufffd\ufffd梯\r\tG\u0011\ufffd\ufffd\ufffd|\ufffd;:\ufffd\ufffd_\u0018~)f\u001f\ufffd\ufffd\ufffd\ufffd\ufffdwrL\ufffd]\ufffda\ufffd6\u003e\u001e/\ufffd\ufffdiܷߚ\ufffd\ufffduT\ufffdѯ\ufffd\ufffd\u0019\ufffd\ufffd\ufffd^Qݖֵ\ufffd0\ufffd\ufffd2\ufffd.\ufffd\ufffd'\ufffdZJ3\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd!\ufffd]\ufffd.\ufffd)x\ufffdh\ufffd\ufffd\"\ufffd\u0018\ufffdWF\ufffd\u0007\ufffd\ufffd\\=Ԟ\ufffdw\ufffd\u001dbR\u003e\u0013\u0003d4\ufffd!E\u001e=\ufffd\t\ufffdt\ufffdt$(UByܝ9\u0007\ufffd\ufffd\ufffd\ufffdnx͛\ufffd\ufffd\ufffd\ufffdJ\ufffd8\ufffdZ\u001cgj\u0015\u00031HA\ufffd(x\u0004R\ufffd\ufffd\ufffd\ufffdn`\t]\ufffd֗P\u000b\rS\ufffd\u0017\u0003T\ufffd\ufffdg\ufffdȚn\u001aV\u0007\ufffd\ufffdD.D\ufffdOΆ\ufffd\u001aS\ufffdr\ufffd]\ufffd~k\ufffd=X\ufffd\ufffdq\ufffd\ufffd\u0012\ufffd?*\ufffd\ufffd^\ufffd^\ufffd\ufffd,\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0013(\ufffdQ@ \ufffd\u0011\u0013U4\ufffdp\ufffd\\?\ufffd{\u0008\ufffd繞u\ufffdA\ufffdUuփ\ufffd\ufffd\ufffd=\ufffd3\ufffd\ufffdR\ufffd\"/\u0017\ufffd\ufffd7\ufffd]\ufffd\ufffd\ufffd\u0001\ufffd\ufffd\ufffd\ufffd\ufffd\u001e\u001f\ufffd\ufffd% \\Y\u0004t\ufffd\ufffd\ufffd\u0001'ۯ\ufffd\ufffd8mu\ufffd\ufffd֠\ufffd\ufffdi-H\ufffd\ufffd\ufffd=ʒ\ufffd\ufffd\ufffddW\ufffd\ufffdX\u0026E\u00087z\ufffd\ufffd\ufffdҧ\ufffd\ufffdO\ufffd__\ufffd\ufffdO\ufffd\ufffd\ufffd\ufffd{\ufffd\ufffd\u0016a\ufffd\ufffd\ufffd\u0018v\u0014\ufffdַLmj\ufffd\ufffd\ufffd]\ufffdB2\u0012Q9\ufffd\u0001R\ufffd\ufffd\u001f\ufffd}9\ufffdo\u001b\u001d\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 1793\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:26 GMT\r\nSet-Cookie: AWSALB=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:04:30+05:30","url":"https://ginandjuice.shop/login","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Cache-Control":"max-age=0","Connection":"close","Content-Length":"53","Content-Type":"application/x-www-form-urlencoded","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx; AWSALBCORS=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx","Origin":"https://ginandjuice.shop","Referer":"https://ginandjuice.shop/login","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"POST","path":"/login","scheme":"https"},"body":"csrf=GhyXet8wACfYUo1chNYuFPbOCI36SVSj\u0026username=carlos","raw":"POST /login HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nCache-Control: max-age=0\r\nConnection: close\r\nContent-Length: 53\r\nContent-Type: application/x-www-form-urlencoded\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx; AWSALBCORS=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx\r\nOrigin: https://ginandjuice.shop\r\nReferer: https://ginandjuice.shop/login\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"1877","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:30 GMT","Set-Cookie":"AWSALB=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI; Expires=Thu, 14 Sep 2023 15:34:30 GMT; Path=/, AWSALBCORS=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI; Expires=Thu, 14 Sep 2023 15:34:30 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdY\ufffdn\ufffd6\u0014\ufffdߧ\ufffd4\ufffdN\ufffdʪ\ufffdl\ufffd\u0010\ufffd\ufffdzK/i\u00134-\ufffd\ufffdOA\ufffd\ufffdŔ\"U\ufffd\ufffd\ufffdG\ufffdk\ufffd\ufffdvHʊlK\ufffd\ufffd\u000b\ufffd\u001f5\ufffd\ufffd\u003c\u003c\ufffdxxx\ufffd\ufffd\ufffd\ufffd'\ufffd\ufffd\ufffd\ufffd}\ufffd\u0014E:\ufffd\ufffd{#\ufffd\u0007\ufffdg\u0014QL\ufffdW;\ufffdL|FQJg\ufffd \ufffdJfiHU\ufffd\ufffd԰\ufffd4\u0008\ufffd\nT\ufffd\ufffdk\ufffd\ufffd\u0012\ufffd@@)\ufffdc\ufffdsNUD\ufffdn\u00033\u0010\u0000\ufffd\ufffd\u0001\u0026\ufffd\ufffd\u0001b\ufffd1\u00128\ufffdco\ufffd\ufffd2\ufffd\ufffd\ufffdP(\ufffd\ufffdB\ufffd\ufffd%#:\u001a\u0013\ufffd`!\ufffd\ufffd\ufffd\u0001\ufffd\u0014M}\ufffd\u0010v\ufffdt,\ufffdWASa\ufffd\u0012\ufffdT\u001a\ufffd\ufffd\ufffd@7\n\u00068\ufffd}@\ufffd\\\u00261\ufffd\ufffdo\ufffd7\u0019\u0005nEw\u0008\ufffd\ufffdx\u000f\u0018\ufffd'p2Moup\ufffd\u0017\ufffdQ\ufffd:t,\ufffd\u0019\ufffd駁\ufffd\ufffd4\ufffdj\ufffd9\ufffd\\\ufffd9\u0013\ufffdG\ufffd\ufffd\ufffd\u003e\ufffd\ufffd3\ufffd2\u0003\u0005\ufffd\ufffdH\u0026\ufffd\ufffd\ufffd\ufffd\ufffd\u000f\ufffd\ufffd~4\ufffd$Gb\ufffd\ufffd$\ufffd \u0012\ufffd@\ufffd\ufffd\ufffd\ufffd\ufffdW4\ufffd\ufffdBCͤ@!\ufffdJ\ufffd=g(\u003e\ufffdN\u003c\ufffd\ufffdX`\u0017%\ufffd4\ufffdy\u00171\ufffd\ufffd\u0007#B9\ufffd\ufffd\u0014k\ufffds\ufffdȸ\ufffd\ufffdp\ufffdhI\ufffd\u0008d\ufffd,\ufffdvW؇\ufffd\u0005%h\u0026S\ufffd\ufffd\ufffdL\ufffd\r\ufffd}1U\ufffd\ufffdj!\ufffdL\ufffd\ufffdd\ufffd\u00024կ\u0015`\ufffd\u0012\\\u001eŘ\ufffd\ufffd\ufffd\ufffd%X\u0015\ufffd\u0026M\ufffd\ufffd\t\"\ufffd\ufffdO\ufffdn\u0007h\ufffd\u0006\ufffd\ufffd\\\ufffd\ufffd\u000e\ufffdfc\ufffd\ufffd\ufffd\u001e\ufffd\ufffd\ufffd4PEu\ufffdxmq\ufffd\ufffd=6C\u0007խ\ufffd\ufffdGսֆG\ufffd\ufffd\ufffdC\ufffde\ufffd\ufffd1\ufffdZN\u000fz\u0017G\u001f\u0007\ufffd\ufffd\ufffdy\u0018?c\ufffd\ufffdmB\ufffd9\u000f\ufffd\ufffdq\ufffd\ufffdܕSld\ufffd{\ufffd\ufffdV\ufffd\u0006gh\ufffd6\ufffd\ufffd\u001d\ufffdЎ\ufffd\ufffdU\ufffdQ\u000ev\ufffd\ufffd{@Z\"\u001dQ{u\ufffd\ufffdM\ufffd\ufffdk\ufffd%ذ\u0017`tV\ufffd\ufffd\u0016{\ufffdl\u001fٍz5\ufffdƼ\u001d\ufffd\ufffd)\ufffd\u0001[\ufffd]\ufffd\ufffd\u000bc\ufffd\ufffd\u0006\ufffd\u0006N\ufffd\ufffd\ufffds9\ufffd\ufffd\ufffd@o\ufffd\rp\ufffd\ufffd\u001d\ufffduX\ufffdE\u003e\ufffdF\ufffd\ufffdmJ\ufffd\ufffd\ufffdL\u001bx\ufffd\ufffdڄ_8\ufffd1v\ufffd\ufffd\ufffd\ufffd8\ufffdݨ\ufffdq\ufffd 9kg\ufffd\ufffdx\ufffd\ufffd4\ufffd\u001a\ufffd\ufffdS\ufffd\u0017\ufffdeb\ufffd\u0013XF*I\u0016jը\ufffd5\ufffd\ufffd\ufffd\ufffd\ufffd.\ufffdԊ\ufffd\u0008~\ufffd\ufffd\ufffd\ufffdS\ufffdiori\ufffdH\ufffd4\ufffdN\ufffd\ufffd\ufffd\t\ufffd\u000fJA~\ufffd\u0011\ufffd0\ufffd\ufffd\ufffd\u003e\ufffd\ufffd\ufffd\tV\ufffd\ufffd\ufffdTO\ufffd\ufffd\u0005\u0003\ufffdI\ufffd\ufffdOF\u0010\ufffdV\ufffdwy\u0015L}n\ufffd\u0000Ɗ\ufffd\u000fܿV\ufffdM\ufffd\ufffd\ufffd\u0026\ufffd\u001a\ufffd.z.\ufffdԍq\ufffd\ufffdo\ufffda\ufffd.\ufffd\u0007\ufffd:\u00001\ufffdYb\ufffd\ufffd\ufffd|\ufffdدsT|\ufffdA\ufffd\ufffd\u001byw\ufffd\ufffd\ufffd\ufffd\u0010\ufffd\ufffdemD\ufffd\ufffd\ufffd\u0018{5)pP\ufffdŽ\ufffdk\ufffdZӽC\ufffd\ufffdn;\u001f\ufffd\u0012\u0001|-\ufffdsNa7\ufffdFM\ufffd|nJEw\ufffd\ufffd0\ufffd\u0003\ufffd\ufffd[\ufffd\ufffd(hL\ufffd\r)\ufffd\ufffd\\SXlp\ufffd\ufffd\u000c\u0015\ufffd\ufffdI\ufffd)\ufffd[\ufffd\ufffd\u0018\ufffdv\ufffd\ufffd\ufffdJ\ufffd\ufffdT\ufffd\ufffd|P\u0003\ufffd\t\\oC\ufffd\u001c\ufffd\ufffd[j\\j6+\n\ufffd\"\u00167-\u000c\ufffdt\u0013\ufffd\ufffdu\u000f\ufffd6h`\ufffd\ufffd\ufffd\ufffd8\ufffd\u0010\ufffd\u000bɬ~|K\ufffdN.\ufffdd|uy\ufffd\u000e\ufffd\ufffd@\ufffd1\ufffd\ufffdo\u000b\ufffd\ufffd\u0004jƔ~\ufffdX\nu\ufffd\ufffd\ufffd\"F\u0008\u0005\u0017r\ufffda\ufffdҙ\ufffd\u0016\ufffdg0\ufffd8%ǯ\u0014;_\u003c[\ufffd\ufffd\ufffd\ufffd,\u000c\ufffd/\ufffd\ufffd\ufffd\ufffd\ufffd/NO\ufffd̻\ufffd\ufffd\ufffdi\u0010\ufffdގ\u001f\ufffd\ufffd\ufffd\ufffdF\ufffd\u0013Sῇ\ufffd\ufffdl\ufffdmH\ufffd\u0004\ufffd\ufffdRV\ufffd\ufffd0@+\u0011WĶ\ufffd7\ufffd\ufffd\ufffd\ufffd\u0002\ufffdh\ufffd\te}\u000f\u001c\ufffdK\ufffd;k]Hd\ufffd\ufffd\ufffdtN\ufffdSN\ufffd\ufffdG\ufffd\u000br\ufffd[\u0013\ufffdwط\ufffd\u0004\ufffd\u0015}7\ufffdv;Z\u003c\ufffd\ufffd\ufffd\ufffd\u0015\ufffd\ufffdR\ufffdd\u0014L'(\ufffdLo5l\ufffd\ufffd\ufffd\u0000v\ufffd\ufffd\nv\ufffdv\ufffd\ufffd^\\FR\ufffd\ufffdL\ufffd\u0019hD\ufffdl\ufffd\u0012Va\ufffd\ufffd\ufffd\"\ufffdl\u001a3=A.ۂ\u001a\ufffd\ufffd\ufffd0e\ufffd\ufffd\ufffd=\u001bݫ[\ufffd\ufffd\ufffdJ@\ufffdI\t\ufffd\ufffd\ufffd)\ufffd\ufffd\ufffd\ufffdg#Z\ufffd\u0005M\ufffdc\u001b\ufffdә\ufffdY;Z\ufffd\ufffd5\ufffdm\ufffdw8\ufffd(\u001aN\ufffdPh\ufffdQ̔{\u000c\ufffd\u001c\ufffd\u0008tm\ufffdlJ\ufffd\ufffdK\ufffdQ\ufffd\u001d\u0010\ufffd䥄\ufffd0\ufffd\u0026\ufffd4'KF(\ne\u001cg\ufffd\ufffd\u001c\ufffd\u00192\ufffd$X\u0010tc_GfX@\ufffd\u000c\ufffd\u0003\ufffd\ufffd@oC\ufffd)\ufffd\ufffd\ufffd\ufffdRAKl\ufffd86]\ufffd\ufffd\u0006\ufffd`\ufffd\u003c\\Q\ufffd(\ufffdB\ufffd9T\ufffd\ufffd\r\ufffd06ȱLi\ufffdw]W\ufffd\ufffd{Ro\ufffdŧ\u003c\ufffd\u001b\ufffd\ufffdoT\ufffd\ufffd\ufffd\ufffdV\ufffd\ufffd\ufffdf\ufffdֻS}\u001a{\ufffd@\ufffd\ufffd\u0017\ufffd\ufffdK\ufffd\ufffd\u0005\ufffd\u000cȫR\ufffd\u000e\ufffd+\ufffd3\ufffd\ufffdǝ\ufffd\ufffd\ufffdk.\ufffdԒ0!pڶ\ufffd`\ufffd\ufffd|\ufffds\ufffd\ufffd\ufffd\u0017\ufffd$y{\ufffd\ufffd\ufffd\u0026\ufffd\ufffd񇷯\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd'o\ufffd\ufffd[s\ufffd\ufffdJ\ufffd:\ufffd\ufffdJ3]\u003c\ufffd\ufffd+\ufffd\ufffd2\ufffd\ufffd\u0017\u0006\ufffdn\u0002\ufffd\ufffdQڄ\\+\u0018\ufffd\ufffdf\ufffdn9a\u00089\ufffd\ufffd\ufffdsJ\u0011r\u0016~.ȏ-\ufffd\u0013+\ufffd\u0001x\ufffdЇ\ufffd\ufffd\ufffdN\ufffdCL\u0026f\ufffd\ufffd\ufffdv5\u0014 Ç\ufffd\ufffd\ufffdΐ\ufffd\u0004\ufffd\ufffd\ufffd\ufffd7\ufffd%\ufffdv7\ufffdx\ufffd\ufffd\":*\ufffd\u0026\ufffd\ufffdӣ\ufffdp\ufffd\ufffdS\u000c\ufffd \r\ufffd\"\u0013\ufffdâ\ufffd=`\ufffd\u001bXA\u0017\ufffd\ufffd%\ufffdB\ufffd\ufffd\ufffd\ufffd\u0000\ufffdm\ufffd\u00157\ufffd\ufffd\ufffd\ufffd\ufffd\u0001(\ufffdć\ufffd\ufffd\ufffd\ufffd\ufffdYe\n\u0015n\ufffd+\ufffdo\ufffd\ufffd\u0007k2\ufffd+Ix\u000e\ufffd5S\ufffdmo\ufffd\u0006\ufffd[\ufffdy\ufffdN\ufffd\ufffd\ufffdchoДB4\ufffd6\ufffd\u0018\ufffd\ufffd\ufffddN\ufffdݱv\u000f\ufffd\ufffd\u003c7p\ufffd\ufffd\ufffd)\ufffd\ufffdzpu\ufffd\u0015Em\u0026X]j\ufffdߡ\ufffd\ufffd\ufffd\ufffdo\ufffd\ufffd\ufffdo\u0000\u0026\ufffd\ufffd\ufffd\ufffd\u000f\ufffdG\ufffd\n\u0010\ufffd\u001d\u0002\ufffdФ\ufffd\ufffdd\ufffd\ufffdP\r\ufffd-N\ufffd\ufffdx5\ufffd9\ufffd\u0005\t\ufffd~\ufffdGY\ufffd\ufffd|\ufffd\ufffdUeT\ufffd)\u0019^\ufffd\ufffdW\ufffd)\ufffd\ufffd\u0003\ufffd\ufffd\ufffd\ufffd\ufffd\u0003luv\ufffd\u0017\ufffd\ufffd\u0016a\ufffdIcc\ufffdP\ufffd:߲\ufffd\ufffd\ufffd\ufffd\ufffdvU\n\ufffdH\ufffd|r\u000fR\ufffd\ufffd\u000f\ufffdZ\ufffd\ufffday\u001e\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 1877\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:30 GMT\r\nSet-Cookie: AWSALB=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI; Expires=Thu, 14 Sep 2023 15:34:30 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI; Expires=Thu, 14 Sep 2023 15:34:30 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:04:33+05:30","url":"https://ginandjuice.shop/login","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Cache-Control":"max-age=0","Connection":"close","Content-Length":"70","Content-Type":"application/x-www-form-urlencoded","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI; AWSALBCORS=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI","Origin":"https://ginandjuice.shop","Referer":"https://ginandjuice.shop/login","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"POST","path":"/login","scheme":"https"},"body":"csrf=gW8d4KsiGvFwrnUHucc4OumZ28lv879y\u0026username=carlos\u0026password=hunter2","raw":"POST /login HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nCache-Control: max-age=0\r\nConnection: close\r\nContent-Length: 70\r\nContent-Type: application/x-www-form-urlencoded\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI; AWSALBCORS=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI\r\nOrigin: https://ginandjuice.shop\r\nReferer: https://ginandjuice.shop/login\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"0","Date":"Thu, 07 Sep 2023 15:34:33 GMT","Location":"/my-account","Set-Cookie":"AWSALB=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; Expires=Thu, 14 Sep 2023 15:34:33 GMT; Path=/, AWSALBCORS=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; Expires=Thu, 14 Sep 2023 15:34:33 GMT; Path=/; SameSite=None; Secure, session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; Secure; HttpOnly; SameSite=None","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"raw":"HTTP/1.1 302 Found\r\nConnection: close\r\nContent-Length: 0\r\nContent-Encoding: gzip\r\nDate: Thu, 07 Sep 2023 15:34:33 GMT\r\nLocation: /my-account\r\nSet-Cookie: AWSALB=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; Expires=Thu, 14 Sep 2023 15:34:33 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; Expires=Thu, 14 Sep 2023 15:34:33 GMT; Path=/; SameSite=None; Secure\r\nSet-Cookie: session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; Secure; HttpOnly; SameSite=None\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:04:33+05:30","url":"https://ginandjuice.shop/my-account","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Cache-Control":"max-age=0","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; AWSALB=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; AWSALBCORS=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2","Referer":"https://ginandjuice.shop/login","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/my-account","scheme":"https"},"raw":"GET /my-account HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nCache-Control: max-age=0\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; AWSALB=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; AWSALBCORS=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2\r\nReferer: https://ginandjuice.shop/login\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Cache-Control":"no-cache","Content-Encoding":"gzip","Content-Length":"2092","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:34 GMT","Set-Cookie":"AWSALB=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta; Expires=Thu, 14 Sep 2023 15:34:34 GMT; Path=/, AWSALBCORS=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta; Expires=Thu, 14 Sep 2023 15:34:34 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffd[\ufffdr\ufffd6\u0012\ufffdާ@ywq2\u0013\ufffd\ufffdc[\ufffdD\ufffdL\ufffd\ufffdɤn\ufffd\u0019g\ufffdi\ufffdd \u0010\u0012a\ufffd\u0000\ufffd\u0000%\ufffd\u003e\ufffdk\ufffd#\ufffd5\ufffdQ\ufffdIn\u0001P2%\ufffd\u0012\u0019\ufffdN?ȓ\ufffdE`\ufffd\ufffdb\ufffd\ufffd\ufffdB\\\ufffd\ufffd\ufffd\ufffdëO\ufffd}|\ufffd\"\u001d\ufffd\ufffd7#\ufffd\u000b\ufffd\ufffd(\ufffd8t\u001f\ufffd#g\ufffd\u0006E)\ufffd\ufffd\ufffd\ufffd*\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0011\ufffdi@\ufffd\n\u0014\ufffd\ufffdg\ufffd\ufffd6t\ufffd\u0001\ufffd\ufffd\ufffd\ufffd\ufffd9U\u0011\ufffd\ufffd\u0010\ufffd\ufffd\u0000@u\u000501\ufffd\u000f\u0010S\ufffd\ufffd\ufffd1\u001d{\u000bF\ufffd\ufffdL\ufffd\ufffd\ufffd\u0014\ufffd\n=\ufffd\ufffd,\ufffd\ufffd8\ufffd\u000bF\ufffdo\u001f\ufffd\ufffdL\ufffd\ufffd\u0007\ra\u0006N\ufffdBz%4ER\ufffdh\ufffdR2\ufffdJ\n]+x\ufffdDw\u0000\ufffdr\ufffd\ufffd\u0000޹V\ufffdd\u0014\ufffd\u0011\ufffd!\ufffdP\ufffd-`t\ufffd\ufffd\ufffd4\ufffd\ufffd\ufffd5^`\ufffd\ufffdU\ufffdc1\ufffd8N?\ufffd\ufffd\ufffd?\ufffdC\ufffdLs:\ufffd9G\ufffd\u0010\ufffd\t\ufffd|\ufffd\ufffd\t\ufffd\u0004\ufffd\ufffdK\ufffd.\u0003+\ufffd\ufffdH\u0026\ufffd\ufffd\t\ufffd\ufffd\u000f\ufffd\u001c`4\ufffda\ufffd\ufffd\ufffd\ufffdIR\ufffd\r\ufffd\u0002\ufffdp\ufffd\ufffd\ufffd\ufffddV\ufffd J4\ufffd\u0002\u0011\ufffd\ufffd\u001a{\ufffd[\ufffd\ufffd:\u001d\ufffdgk\ufffd\u001d\ufffd춙\ufffdO\u0011S\u0008\ufffda\u0014RΦ4Ś\ufffd\u001c-2.\ufffd3l+Z\ufffd)\u0002\u001d9#\ufffd\ufffd\n󰹠!\ufffd\ufffd\u0014i\ufffd4\u0013s#\ufffdDLU\ufffdr5\ufffdq\ufffds\ufffdd\ufffd\u0002m\ufffdS\ufffd\ufffdH%x\ufffd\u0014\ufffd\ufffd\u001e\ufffd\"\ufffd\ufffdZ\ufffd\ufffd45\ufffd'B\ufffd\ufffd|\ufffd|\ufffdCc\ufffd[w\ufffd\ufffdfo\ufffd7\ufffd\ufffdr\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdU\ufffd\ufffdO7\u0006g\ufffd\ufffdc3\ufffd\ufffd\u003c\ufffd\ufffd'\ufffd\u003c\ufffd\ufffd\ufffd`\ufffd\ufffd\ufffd\u0019Ⲱ\ufffd\u0018a-\ufffdOO.\u0007\ufffd\ufffd\ufffdo\ufffd8\ufffdd\ufffd\ufffd\ufffd$|\ufffd9Y\ufffd\ufffd'\ufffdֳr\ufffd\ufffdΰ\u000fT\ufffd\u0012\\\ufffd%\ufffdi\ufffdW\ufffd\r*\ufffdN\ufffd\ufffd\ufffdL\ufffd\u001c\ufffd\u001b\u0015\ufffd\ufffd\ufffdD:\ufffdv\ufffd\ufffd\u0005\ufffd\u0006\ufffdW\ufffdK\ufffd\ufffd/ \ufffd\ufffdn\ufffd/\ufffd8\ufffd\ufffd\ufffdNtR\u0001k\ufffdۉ\u001ab\ufffd\u000cĪ\ufffdr\ufffd\u001d\ufffd\ufffd\u0004\u00150\u0015p\ufffd\u0012\ufffd\ufffd\ufffd\\B\ufffdB{\rl\ufffdk:\ufffd\ufffdl\ufffd\ufffd\u0007\ufffd\ufffdc\u0004\ufffd\ufffdf-\ufffd\ufffd\ufffdk\ufffd,\ufffd[\ufffd\ufffd\ufffd\ufffd5ήP\ufffd\ufffd\u0018\ufffd\ufffd{\ufffd\u0008\ufffd\u001dH\ufffd\u000e\u000bYA\ufffd\ufffdz\ufffdi\r\ufffd\ufffd\u000c\ufffd\u0005\ufffd\ufffd\u0018\ufffd\u0004\ufffd\ufffd\ufffd0#Z\ufffd\u001ae\u0003.h2\ufffd\ufffd\ufffd\ufffdZվ\ufffd\ufffd\ufffdRj\ufffd\ufffd̴7\ufffd`\ufffdH\ufffd4 \ufffdFA\ufffd\ufffd\ufffdB\u0010\ufffdR\ufffd\ufffd\u0013\u0015ǜ\ufffd`.80L\ufffd\ufffdi\ufffdÑg\ufffd\ufffdh\u001a\ufffdm\u0015\ufffd~!\r'\ufffdZ\ufffd'#`\ufffdU\ufffd\ufffdI\u000b~?7\ufffd\u0000p%\ufffd\u0001鿕g2G/\u000c6\u0007\ufffd\ufffd9\u0008F\"Jn|\ufffdR©\u0005+7l\ufffd5\ufffd\ufffdb\u0007\ufffd\t\ufffd\ufffd쮅\ufffd\u003c\ufffdP\ufffdq\ufffdK9G𡱺\u0016\ufffdQT\u003c\ufffd\ufffd坿K\ufffd\ufffd$\ufffd\u000f\ufffdNs\ufffd/\u0008\u0008\ufffd\ufffd\ufffdG\ufffd\ufffd50=\ufffd\ufffd\ufffd\ufffd\ufffd+N؆q`\ufffd:\ufffd]\ufffd\ufffdw\ufffd\ufffdK.\u0011\ufffd\ufffd!\ufffdm\ufffd\ufffd\ufffd֔A\ufffd\ufffd\ufffdլ\u0017S\ufffd\u0000\ufffdݗ\ufffdFA\ufffd\ufffdZs\ufffdW4W\ufffd+[R\ufffdćD\ufffd\ufffdw\ufffd~\ufffd7׎!%Xg\u0006Ui\ufffd]\u001a\ufffd\ufffd\ufffd\u000c]\u0026\ufffd\ufffd5\ufffd\u003crd\ufffd6\ufffd\ufffdlV$\ufffd\u0005\ufffd\ufffd\r\u000c\\w\u001dl\ufffd\ufffd\ufffdw\ufffd\u0018\ufffd\ufffd\ufffd\ufffd\u0007\ufffd\u001c+\ufffd-n|{\ufffdg\ufffd\u002625G\u0011\ufffd#̹x\ufffd\ufffd)\ufffdǍc\ufffd\ufffdMN,\u0016Ϸ\u0006\ufffd\ufffdY\ufffd\ufffd\ufffdC\ufffd\ufffd\ufffd qy\ufffd*\u001c\ufffdG\u0015$Y\ufffdp\ufffd9º\ufffd\ufffdy\ufffdIw\ufffd\u00153\ufffd`\ufffd\u001d\u001c\r \ufffd63\ufffd\ufffdX\ufffd\ufffd!\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd洐$\u0012p\ufffd\ufffd\ufffd\ufffd\ufffdN\ufffd;\n\ufffd\rav\ufffd\u000f\ufffd\ufffd\ufffdx\ufffd\u0014a\ufffda\ufffd\ufffd\ufffd\u0019\ufffd\ufffd\ufffd\ufffdn\ufffd\ufffdt8\ufffd\ufffd]\u001c\ufffd\ufffd\ufffdp\ufffdD\u0017Ao\u0018\ufffd\ufffd\ufffd\ufffd\u0017!\ufffd\ufffdQ\ufffdM\ufffd\ufffdnqJc\ufffd\ufffdY\ufffd\ufffd3\ufffd\ufffd\ufffd%@A\ufffd\ufffdJ\u000c\ufffdLHs\ufffd\u003c\ufffd\ufffdۘ\ufffd\ufffd\ufffd\ufffd\ufffdY\ufffdDlg\ufffd[٦\ufffdM\u001a\u001a\ufffd\ufffd\ufffdMD\ufffd\u0026o\ufffd=\u0016]\ufffd\ufffd\ufffd^\ufffd\ufffd\u0014\u0013 \ufffd\ufffd\ufffd\ufffdE\ufffdsz\ufffd\ufffd\ufffd\ufffd?|\u0000\ufffd\ufffd^\u001c\ufffd\ufffd${\ufffd\ufffdV\u000bzD\ufffd\ufffdu\u001f\ufffd/~L3\ufffd\ufffd\u0007\ufffd/ǹ\ufffd?]\ufffd\ufffdw\ufffd/\ufffd:]\ufffd\u001b\u001d\ufffdI\u0017\ufffdZ\ufffd\u001f\ufffd\ufffd${\ufffd\ufffdV\u000bzD\ufffd\ufffdx4\ufffd\ufffd8f\ufffd\ufffd\ufffd\u000fz%ɍ\ufffdT\ufffd\ufffd\ufffd\ufffd\u0007`\ufffd\ufffd\u0017\ufffd\ufffd\ufffd\ufffdg\ufffd\ufffd\ufffd\u00030G\ufffd\ufffd\ufffd\u001c%\ufffd#s\ufffdZ\ufffd\ufffd^R\u001e\ufffd\ufffd\ufffd\ufffd\ufffd\u000fN\u001f\ufffd.\ufffd/\ufffdtQ\ufffd=\ufffdEmw\ufffd\u001b\ufffd\u0003\ufffd\ufffd}\ufffdm\ufffdKƚI\ufffd\ufffdZ\ufffd\u0014'I\ufffd\ufffd\ufffd[\ufffd\ufffd\ufffd\u0001u\ufffd3\ufffd\"s:3\ufffd8\ufffd\ufffdh\ufffd\u001dClA\ufffd\u001e[\ufffd\ufffd\ufffd\ufffd=\ufffd]A1S\ufffd$\u0005s\ufffd#\ufffdMMi˔B\ufffd.\ufffd|\ufffd{ \ufffd\ufffd;\ufffd\u00042\ufffd\u000eK\ufffd\ufffdp\ufffdB\ufffd\ufffd\ufffd\ufffdL0\ufffd#9Cs\ufffd\ufffd\"Dז\ufffdfX`͈zn\ufffdY\ufffd-\ufffd\ufffd\u0002\ufffd@\ufffd.\u0015\ufffd\u000e\ufffdcS+a\ufffd\u00019\ufffd15T\u0014C\ufffd\ufffd\u001c\ufffd9\ufffdS\ufffdگ\ufffd\ufffd\ufffd \ufffd2\ufffd\ufffd\ufffd\n\ufffdM\ufffdߕ6\ufffdl\u0016\u001f\ufffd\u0017\ufffd^.;\ufffdꤢ\ufffd\ufffd\ufffdֶhh5ֻ3}\u001a{\ufffd\ufffd\ufffd\u001fS\u001dI\u0010J\ufffd\ufffdE\u0013\ufffd\u003ePzsu\u0007p\ufffdՙH2\rf\ufffdg\ufffd \ufffd\\m\u0015\ufffdMJi\u000b\ufffd\ufffdDŽcB#\ufffdCS\ufffd\ufffd\ufffd6\ufffd0\ufffd\ufffd\u001e\n\ufffdJx/baH\ufffdW\ufffd\ufffd\u0011\ufffd\ufffd\u003c\ufffd\ufffd\u003c\ufffd\u0007\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdw\ufffd\ufffdkF\ufffdO݋˫\ufffdf\ufffd\ufffd\ufffd\ufffdL\ufffd\ufffd\ufffdp\ufffd\ufffd\ufffd\ufffd|\ufffd5\ufffd\ufffdKM;-\ufffd$fzr\ufffd\ufffd\u000c\ufffdZ\ufffd{_\ufffd\ufffd灐\u0019[\ufffd\r#2K\ufffd\ufffd]K\ufffdW'Ÿ\ufffd\ufffd\u003e\ufffdB¥\ufffd\ufffdj\ufffdR\u0010\ufffd\ufffdM\ufffd\ufffd\ufffdb\ufffd`Uz\n\ufffd(\ufffd3\u0013\ufffd\ufffdV\ufffdG\ufffdV\ufffdFo\ufffd\ufffd\ufffd\u0003\ufffdt\ufffd\u000c\u0013\ufffd:bb^\ufffd\ufffd\ufffd\ufffd\ufffdI\ufffd\ufffdZo\ufffd\n\ufffdbo\ufffd\ufffd\u0014\ufffd\ufffd\u000eǛ8\ufffd\u0000\u0007i\ufffd\ufffdL\ufffd\ufffd\ufffd \u000b[\ufffd\ufffd\n\ufffd\ufffd]o\ufffdZi\ufffd\ufffd\ufffd\u0018hu\ufffd\ufffd\ufffdlئ\ufffdu\u0000\n\ufffd\ufffd\u0003\ufffd\ufffdx[6+u\ufffd\"\ufffd\ufffd\ufffd\ufffd\ufffdL-D\ufffd\ufffdwI\ufffds\ufffdM!\ufffd\ufffd\ufffdT\ufffdي5S@\ufffd\ufffd\ufffd\ufffdW8\ufffdhJ\ufffdͨe\u0015\ufffdf%lV\ufffd\ufffdk[(u\ufffd\u0004w\ufffdz\ufffd\ufffd\ufffdN=غ\ufffdͣC.X\u001ejJo\ufffdҾy\ufffd\u0015m\ufffd\ufffdo\u0000\u0026\ufffd\ufffd\u0003\ufffdd\u0014}\u0004\ufffd+\ufffd\ufffd.u\ufffdi\ufffd\ufffdv\ufffd\ufffdkV[\ufffd\ufffd6\ufffd\ufffdM\ufffd\ufffd\u0013\u0012X\ufffd\ufffdEZ\ufffd~] \ufffd/\ufffd(\rS\ufffd\ufffd\ufffd^\u001e\ufffd\ufffd:d\ufffdc\u0019\ufffd\ufffdkw,\u0003,\ufffd\ufffd/\ufffd\ufffd\ufffd\"lU\ufffdl=\ufffd$\ufffd.\ufffdln\ufffd\ufffd\ufffd`7+\ufffd\ufffdH\ufffd\ufffd\ufffd\u001b8\u0012\ufffd\u001f;\ufffd\u001fq\u0007%\ufffd\u00041\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2092\r\nCache-Control: no-cache\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:34 GMT\r\nSet-Cookie: AWSALB=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta; Expires=Thu, 14 Sep 2023 15:34:34 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta; Expires=Thu, 14 Sep 2023 15:34:34 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:04:39+05:30","url":"https://ginandjuice.shop/catalog/cart","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; AWSALB=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta; AWSALBCORS=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta","Referer":"https://ginandjuice.shop/my-account","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/catalog/cart","scheme":"https"},"raw":"GET /catalog/cart HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; AWSALB=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta; AWSALBCORS=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta\r\nReferer: https://ginandjuice.shop/my-account\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"1806","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:39 GMT","Set-Cookie":"AWSALB=+OTzt/cHdN9mzp1pbOgaURPkES7i2qq1lhilq3fOQUe1jfh6+KYbRfxvolgT0DjiZvlhhdhoctO4ecMahMhz5hagBN8ye+HbQJnxFvNdj2yozEGUDHaWbKKErer+; Expires=Thu, 14 Sep 2023 15:34:39 GMT; Path=/, AWSALBCORS=+OTzt/cHdN9mzp1pbOgaURPkES7i2qq1lhilq3fOQUe1jfh6+KYbRfxvolgT0DjiZvlhhdhoctO4ecMahMhz5hagBN8ye+HbQJnxFvNdj2yozEGUDHaWbKKErer+; Expires=Thu, 14 Sep 2023 15:34:39 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffdZ\ufffdS\ufffd6\u0018\ufffd޿B\ufffdvM{7ǘ@\ufffd%\ufffd\u000e\u0018e\ufffdx[\ufffd֮_8\ufffdVbQ\ufffdrm9\ufffd\ufffd\ufffd{$9\ufffd\t6\u0011`v\ufffd\ufffd\u001cG\ufffd\ufffd\ufffd\u001e=\ufffd\u000f\ufffd\ufffd\ufffd\ufffd\ufffd}v\ufffd\"\u00113\ufffd\ufffdP!\ufffd\u000c#\ufffdC\ufffd\ufffd\ufffd\ufffd\u0026_Q\ufffd\ufffd\ufffd\ufffdd$\ufffdE\u0016\ufffd\ufffdax$\ufffd\ufffd\ufffd\t\ufffd\ufffd\ufffd\u0003\ufffd\u001c\ufffd\ufffdTG\u001f:PF\ufffd\ufffd\ufffd\u0019#yD\ufffdX\u0005\u0026!\u00000\ufffd\u000801\ufffd\u001b \u0026\u0002\ufffd\u0004\ufffdij\ufffd\ufffd\\\ufffd\u003c\u0013\u0016\nx\"H\"\u003c늆\"\ufffdB2\ufffd\u0001\ufffdU\ufffdgT\ufffd$\ufffd\ufffdB؁\u0011/\ufffdV\r-\u000f2\ufffd\n\ufffdg\ufffdg\ufffd\u0008\ufffd̡\ufffd\u0003\ufffd\u0007$\ufffdx\u001a\u0003x\ufffd2\ufffd\ufffd\ufffd\ufffdW\ufffdC\ufffd!\ufffd\ufffd\u0001#f)\ufffdL\ufffdk\ufffd\\\ufffd)ֽV\u0013:N\u0026\u0005\ufffdمko\ufffd[m\ufffd\ufffd\nF\ufffd}\ufffd\td\ufffdC\ufffd\ufffd\ufffd8Nw\ufffd\ufffd\u0002\ufffd\ufffd\u003eF\u003c\u001d:z\ufffd\ufffd\ufffds#\ufffdሇ3\ufffdLl\ufffd\ufffd5\ufffd\ufffdN\u0011\r=\ufffd.\ufffd\u001aC\ufffdQH (OP\ufffdp\ufffd{\ufffd\ufffd\u0013;$\ufffd:\u0018YZ\ufffd\u0016\ufffd\ufffd\ufffd\ufffd\ufffd\u003c\ufffd9\ufffd?\ufffdB\ufffd\ufffd\ufffddX\u00106Cӂ%\ufffd\u000c\u0002EWd\ufffd\ufffdFF\u0003\ufffdv\ufffd}\ufffd$!!\u001a\ufffd\u000c\t\ufffd\u000b\ufffdL\ufffd\ufffd\ufffd\ufffd(Ow\ufffd\u000b)\ufffdb\ufffd\ufffd\ufffdQ\ufffd/\ufffd7\u00120\ufffdS\\\u001dEj\ufffd\ufffdx\u0012sP*\u0010\u0026ɤ\ufffd%!\ufffd\ufffd_\\\\\ufffd\ufffdCn5̧\ufffd\ufffd\ufffd\u000b\ufffd1\u000c\ufffdG\u0007zt\u003e\u000c\ufffdI}xcaq\ufffdz\u0016\u001d\ufffd\u0017\ufffd\ufffd\ufffd?G\ufffd\ufffd\u0016\ufffd\ufffd\ufffd\ufffd\ufffdK\ufffdx\ufffd-\u000fa\ufffdG/zG\ufffd/\ufffd\ufffdp\ufffd\u0005\ufffd[\ufffd?]\ufffd\ufffd!c\ufffd\ufffd\ufffd\ufffd^V\ufffd2\ufffd%\ufffd \u0007\"H\r\ufffd\ufffdA\ufffd\ufffd\ufffd\u001b\ufffd\u0006\r}\u001b\ufffdV\u00084\u0003\ufffdF\ufffd\u001c\ufffd\ufffdHDD\ufffd\ufffd\u0007Pi`\ufffd\ufffd8K\ufffd\u0002\u0013\ufffd\ufffdݩ\ufffd=\ufffd\ufffd{j\ufffd^\u0003\ufffdTo=U\ufffd\u0014LaZ\ufffd^\u000e\ufffd\ufffdb=\ufffd\u0001\ufffd\u0001.W\ufffd\ufffdf|\ufffd\ufffdN\ufffd\ufffd\u0005\ufffd\ufffd-\u00037P\ufffd`\ufffd\u0015\ufffd\ufffd5\tn٦\ufffdQ\ufffd\ufffdj\ufffd\\:Q:a\ufffd\u0006+\ufffd=GiFc\ufffd\ufffdt\ufffd\ufffdpoA2\ufffdz\ufffd\ufffd\ufffd\ufffd[\ufffd\n!\ufffd)h\ufffdZ\u000eh\u0026\u0006\u003e\ufffdfd\u003c,\u0002\ufffd\ufffd2e\u0001\ufffd1\ufffd\ufffd\ufffdԍ\u0014i{\ufffd\ufffdE\u0016\u001e\ufffdBX\ufffd\ufffd\ufffd#\ufffd\ufffdYG\ufffd\r\ufffd\ufffd=Z\ufffd\ufffd\u0006y\u0012\u003e\ufffd\u0012\ufffd \ufffdE\"l\n{A\ufffd\ufffd\ufffd6!\ufffdMoX\u0014\ufffd\ufffdr\u0012\ufffd\ufffd|:\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd7\ufffd\u0015\ufffd}\"\ufffd\u0000g\u003e\ufffd\u000f\ufffd\ufffdo c-,\ufffd\u0011W\ufffd\ufffd\u0004\u000b\"\u0012|\ufffd\u0003\ufffd\u0005\ufffd(\ufffdz\ufffd\u0012\ufffd\ufffd\ufffdJƛM\ufffd\u0017Co3vIѤ\u0007\ufffd\ufffdv\ufffd'\u0008\u001e\ufffd\ufffdU\ufffdF\ufffd\ufffd=\ufffdu\ufffd\u001f\ufffdP\ufffd\ufffdD\ufffd\ufffd6\u0019s\ufffd\u0007\ufffdA\u0000\ufffd\ufffd6\u0002\u0019\ufffd\u0003A\ufffde\ufffd\ufffd\ufffd)\ufffd\u0004ddu\ufffd\u0008kh\u0010r\ufffdV\ufffd\ufffd6\u000f\ufffd^\ufffd\ufffd\ufffd\u001c\ufffd-\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdތ\ufffd\ufffdg\ufffd\ufffdT\ufffdim\u0000}\ufffd\ufffdzC\ufffd5\ufffd\ufffdD\ufffd\ufffd\ufffd\ufffd|ei\ufffd\ufffd\ufffd\ufffd\u0008\ufffdJGK\ufffd\ufffd,;\ufffdd\ufffd\ufffd\t\ufffd\u0012\ufffd\ufffd\u0004ª\ufffdIG\ufffd\ufffdt[\\\ufffdP\ufffd\ufffd\ufffd\ufffd\\\ufffdq\ufffdf\ufffd\u001e\ufffdm\ufffd\ufffd\ufffd[FS\u0010\ufffd\ufffdx2Q\ufffd\t\ufffdB7n%u\ufffd\ufffdKŻ\ufffd \ufffdo\ufffd\ufffd\ufffd%6#\ufffdeV\ufffd\\K\u0005\ufffdM\ufffdV\ufffdT\ufffdkle:\u0006+\ufffdj\u001aO\ufffd:O-st\ufffd\ufffd\ufffd:\u000b\ufffd\u001bo\ufffd̝\u0014\u0004(\ufffd\u001erAB*E\ufffdO\ufffd\ufffd\t\ufffd-)\ufffd\ufffdӄ@ƒؠ2VS6\ufffd\ufffd\ufffd\ufffdt\ufffd_\ufffd\ufffdw\ufffd\ufffd\ufffdg\ufffd\ufffd\ufffd\u0003}v\ufffdσ\ufffd\ufffd\ufffd\ufffd\ufffd\u0001=\ufffd\u0005\ufffd\r\u0007p\ufffd\ufffd\u0006k\ufffd\ufffd5S\ufffdf\ufffdo\u0005N\ufffd1\ufffd\ufffd\ufffd\ufffdC\ufffd\ufffd\u0018SV\ufffdB\ufffd\u001bC@\ufffd\u003c\ufffd\u0016\u001c\u003c\ufffd\ufffd\ufffdx蝝~\u003cG\ufffd\u0026\u0005J7\ufffd\ufffd\u000c\ufffd64\ufffdL\ufffdT\"\ufffd^4I\ufffdV\ufffdȷ\ufffdfP_\ufffd\ufffd\ufffd\ufffd\ufffd!I\ufffd\ufffdL%!4Ŭ \ufffd\ufffd\ufffd\ufffd\ufffd5/\ufffdJ\ufffd\ufffdO\ufffd\ufffd\ufffd\ufffdn\ufffd~(O\ufffd\u0008'\u0013ym\u0012Ѽ/9\ufffdϋQLŋ\ufffd;\ufffd\u003c\ufffd\ufffd\ufffd\ufffdK\ufffd\ufffd\u0010֚UER\u0019-\ufffd\ufffd\ufffdQ]\ufffd_\ufffd\u000cl\ufffd\ufffd\u0007\ufffd\ufffd\r,\ufffd30\ufffd\ufffd7;\u0003۴\ufffdW\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd,\ufffd3\ufffdm\ufffd\ufffd\u0019\ufffdk\ufffdw׺\ufffd\ufffd5\ufffd\ufffd\ufffd\u0008\ufffd\n\ufffd\ufffd\ufffd\ufffd\u0005;p\ufffd3\u0004\u0017,\ufffd\ufffd\ufffd\u0014\\\ufffd\u0005\ufffd\ufffd\ufffd \ufffdE\ufffd\ufffd:v\ufffd\ufffdI\ufffd\ufffd\ufffd\ufffd\ufffd\u000f\ufffd\ufffd)\ufffd\ufffd\ufffd5-\ufffd\u000c\ufffd\ufffdC\ufffdr\ufffdnR\ufffdJČ\ufffd\ufffd(d\u003ee\ufffd\\\ufffd]\u0005G\rmw\ufffd\ufffd\u0008\ufffd\ufffdu\ufffd\ufffd\ufffd!\ufffd\ufffds/\ufffd\ufffd~_\ufffd\u0006\ufffdB\ufffd\ufffd\ufffdA\tv\ufffd\ufffd%\ufffd\ufffdf\ufffd\ufffd\u0026\ufffdv\ufffd%\ufffd\ufffdp[\ufffd\u0001\ufffdjt\ufffd\ufffd\ufffdg\ufffd2\ufffd,\ufffdL2\ufffdh\ufffd\ufffdsP\ufffd7P\ufffd\rL,J\u0017`UZ\ufffd\ufffd\ufffd\ufffd)SҔ\ufffd\u0011\ufffd\u0008U\u0007\r\ufffd\ufffd\u0017)/\ufffdM\ufffd\ufffd\ufffd\t\u000b\u0016g(c\ufffdG\ufffd\ufffd\ufffdj=\ufffdO7\r+\ufffd\u0006Ͷ\ufffdj[\ufffd;\ufffd \ufffd\ufffd\ufffd\ufffdev\ufffd^\ufffd\ufffd\ufffd\ufffd]0\ufffd\ufffd߲\ufffd\ufffd\ufffd\ufffd\ufffdߏ\ufffd9\ufffd\ufffd\ufffd =\ufffdd\\\ufffd-\ufffd\u000b\u000e% \u0011g\ufffd|A\ufffd\u001b\ufffdH3\ufffd\ufffd\u0004\ufffdw\ufffd\u0018\ufffdm\ufffdGiZZ\u0018\ufffd\ufffd\ufffd߅rifnNƦԬ\u0002\ufffd2\ufffd\ufffd\ufffd\ufffdX\ufffd\ufffdO$\ufffd\ufffd\ufffd\ufffd\ufffdɫ\ufffd\ufffd\ufffd\ufffd\ufffdG\ufffd\ufffd?\ufffd\u001b\ufffd\ufffd\ufffdͽ\u003c99\u003e\u0008x\ufffd\ufffd5\ufffd\ufffd*\ufffd\ufffdIQ\"e\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdQwP\ufffd\ufffd\ufffd~Ɯ\u000bp=\ufffd\ufffd-\ufffd\ufffdP\ufffd\u0002Ư\ufffd/\ufffdn\ufffd\ufffdT/\u000e\u000cotr\u001eP̎V\ufffdMX\ufffd\ufffd\ufffd\ufffdǺ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd/\ufffd\ufffd-r\ufffdfx\ufffdY\ufffd\u0005J\ufffdvo\ufffdmK\ufffdTD\ufffd$\ufffd/\ufffd\ufffd\ufffdoD\ufffdg\ufffdp\ufffd?\ufffd4A\ufffd\ufffd\ufffd_\ufffd\u0017\ufffd\ufffd\u0016$\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 1806\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:39 GMT\r\nSet-Cookie: AWSALB=+OTzt/cHdN9mzp1pbOgaURPkES7i2qq1lhilq3fOQUe1jfh6+KYbRfxvolgT0DjiZvlhhdhoctO4ecMahMhz5hagBN8ye+HbQJnxFvNdj2yozEGUDHaWbKKErer+; Expires=Thu, 14 Sep 2023 15:34:39 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=+OTzt/cHdN9mzp1pbOgaURPkES7i2qq1lhilq3fOQUe1jfh6+KYbRfxvolgT0DjiZvlhhdhoctO4ecMahMhz5hagBN8ye+HbQJnxFvNdj2yozEGUDHaWbKKErer+; Expires=Thu, 14 Sep 2023 15:34:39 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:04:41+05:30","url":"https://ginandjuice.shop/my-account","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; AWSALB=+n84438yZsDA5AHP8fkJy0HrFaX/8Yq0pknF0+7HT5H3HB2hEd3G3yvICmv5HTZkzoDUZB/b7S77KFxPM4mi7pHuQYLCafFCtH2zJrM/l9i9JIn+2wcdTRh/fxDy; AWSALBCORS=+n84438yZsDA5AHP8fkJy0HrFaX/8Yq0pknF0+7HT5H3HB2hEd3G3yvICmv5HTZkzoDUZB/b7S77KFxPM4mi7pHuQYLCafFCtH2zJrM/l9i9JIn+2wcdTRh/fxDy","Referer":"https://ginandjuice.shop/catalog/cart","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/my-account","scheme":"https"},"raw":"GET /my-account HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; AWSALB=+n84438yZsDA5AHP8fkJy0HrFaX/8Yq0pknF0+7HT5H3HB2hEd3G3yvICmv5HTZkzoDUZB/b7S77KFxPM4mi7pHuQYLCafFCtH2zJrM/l9i9JIn+2wcdTRh/fxDy; AWSALBCORS=+n84438yZsDA5AHP8fkJy0HrFaX/8Yq0pknF0+7HT5H3HB2hEd3G3yvICmv5HTZkzoDUZB/b7S77KFxPM4mi7pHuQYLCafFCtH2zJrM/l9i9JIn+2wcdTRh/fxDy\r\nReferer: https://ginandjuice.shop/catalog/cart\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Cache-Control":"no-cache","Content-Encoding":"gzip","Content-Length":"2094","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:41 GMT","Set-Cookie":"AWSALB=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq; Expires=Thu, 14 Sep 2023 15:34:41 GMT; Path=/, AWSALBCORS=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq; Expires=Thu, 14 Sep 2023 15:34:41 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffd[\ufffdr۸\u0011\ufffd~O\ufffdc\ufffd8\ufffd\tEQr\ufffdg\"i\u0026\ufffd\ufffd|s\ufffd\ufffd\ufffd\ufffd\ufffdn\ufffd/\u0019\u0008\ufffd$\ufffd \ufffd\u0012\ufffddާ\ufffdF\u001f\ufffd\ufffd\ufffdG\ufffd\ufffdt\u0001P2%\ufffd\"\u0015\ufffd\ufffd\ufffd O\u0026\u0016\ufffd\ufffd\u000f\ufffd\ufffd\ufffd\u000f\u000bq=\ufffd\ufffd\ufffd닟\ufffd\ufffd\ufffd\u0003\ufffd阏\ufffd\u0019\ufffd_\u0008~\u00063\ufffd#\ufffd\ufffd\u003er\u0026\ufffd\ufffd,\ufffd\ufffda\ufffdR%\ufffd\ufffdP\u0015p\u003c6b4\r\ufffdR\ufffd\"X\ufffdD\ufffd\r\u001dh@)\ufffdC\ufffdsNՌR\ufffd\u0004f \u0000P\ufffd\u0000LLw\u0003\ufffdTc$pL\ufffdޜ\ufffdE\"S\ufffd!\"\ufffd\ufffdB\u000f\ufffd\u0005\ufffd\ufffdl\u0018\ufffd9#Է\u000f\ufffdQ\ufffdhꃆ0\u0003\ufffdC!\ufffd\u0012\ufffd\")K4R)\u0019z%\ufffdn\u0015\u003c`\ufffd;\ufffdD\ufffdLb\u0000\ufffd\ufffd*o4\u0008܈\ufffd\u0010~$\ufffd=`t\ufffd\ufffd\ufffd4\ufffd\ufffd\ufffd-\ufffdc\ufffd\ufffdU\ufffdc1\ufffd8N?\ufffd\ufffd\ufffdZ\ufffd\ufffd\ufffd\ufffdt\ufffdS\ufffd0!2\u0013\u001a\ufffd\ufffd\ufffd\t\ufffd\u0002\ufffd\ufffd[\ufffdc\u0006VB73\ufffd\u000c\u0002'\ufffd\ufffd?xp\ufffd\ufffdXF9\u0012S\u001f'I\t6bsĢ\ufffdW\ufffd\ufffd\ufffdY݂(\ufffdL\nD8Vj\ufffd9o\ufffd#\ufffdt\ufffd\ufffd\ufffd\u0001vP\ufffd\ufffdf~~\ufffd1\ufffd\ufffd\u001fF\u0011\ufffdlLS\ufffd)\ufffd\ufffd\u003c\ufffd\u0002\u003eö\ufffd\u0005\u001d#Б3\ufffd\ufffd\ufffd0\u000f\ufffd\n\u001a\ufffd\ufffdL\ufffd\ufffdJ315B/\ufffdX%o\ufffd\u0003\u0019g:wMf)Ц:\ufffd\n\u000cT\ufffdWK1\u003e\ufffd!)b\t\ufffd\u0005[JS\ufffdx\"\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd!\u001a\ufffdp\ufffd-\ufffd뽽\ufffd\ufffd\u0018\ufffd˽}׻\ufffd\ufffdVQ\ufffd\u003e^\u001b\ufffd%C\ufffdM\ufffd\ufffd\ufffd\ufffd/^\ufffd\ufffd\\k\ufffd\ufffd\ufffd\ufffd\ufffdW\ufffd\ufffd\ufffdZC\ufffd\ufffd\u001c\ufffd\u003c\ufffd\ufffd\ufffdG8\ufffd|\ufffdI\ufffd=ÿ\ufffd'\ufffd%\ufffdd1\u001c\u001e\ufffdZ\ufffd\ufffd)6:\ufffd\u003ePMKp\ufffd[\ufffd\ufffd֫h\ufffdW\ufffd\u001d{\ufffdO\ufffdF987*\ufffd\u0001i\ufffd\ufffd\ufffdڭ\ufffd\u0016\u001c\u001b\ufffd_\ufffd.\ufffd\ufffd\ufffd\ufffd\ufffd󺝾x\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u0002ָ\ufffd\u00135Ă\u0019\ufffdU\ufffd\ufffd\u0000;.;\n*`*\ufffd\ufffd%8\ufffd˩\ufffdh\ufffd\ufffd\u001a\ufffd\u0000\ufffdt\u003c@ـ\ufffd\u001b\ufffd\ufffdc\u0004\ufffd\ufffdf%\ufffd\ufffd\ufffdk\ufffd,\ufffdZ\ufffd\ufffd\ufffd\ufffd5ήP\ufffd\ufffd\u0018\ufffd\ufffd{\ufffd\u0008\ufffd-HΚ\ufffd\ufffd ^N=δ\u0006Rp\u0006\ufffd\u0002\ufffdL\u000cv\u0002\ufffdHe\ufffd\u0011\ufffdj\ufffd\ufffd\u0006\u0017\ufffd\ufffd\ufffd\ufffdڍ\ufffdj\ufffd\ufffd\ufffd?\ufffdZx,3퍮M\u001ci\ufffd\ufffdO\ufffd\ufffd \ufffd\ufffd\ufffd]\u0008bP\ufffd\ufffdwv\ufffd\ufffd\ufffd\ufffd\u0019\ufffd\u0005\u0007\ufffd\ufffd\ufffd)\ufffd|8\ufffd\u000cs\u0011M\ufffd\ufffd\ufffd\ufffd\ufffd/\ufffd\ufffd\ufffdT\ufffd\ufffdh\u0000\ufffd\ufffd\ufffd|8i\ufffd\ufffd\ufffd\ufffd\u0017\u0000.\ufffd; \ufffd\ufffd\ufffdL\ufffd\ufffd\ufffd\ufffd\ufffd\u000068\ufffd`dFɝOXJ8\ufffd`\ufffd\ufffd\r\ufffd6;X\ufffd@;\ufffd\ufffd,\ufffdm\ufffd\r\ufffd3Tf\\\"\ufffd\ufffdZ]\u000b\ufffd**\ufffd^\ufffd\ufffd\ufffd?\ufffdF\ufffd\ufffd\ufffdͱ\ufffd\u001e\ufffd\u000b\u0002\ufffd\ufffd\ufffd\ufffd\ufffd\rb\rL\ufffd\ufffd|s\ufffd\ufffd\ufffd\t\ufffd2\u000e\ufffdP\ufffd\ufffd+\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdK\ufffd\ufffdk\ufffdg\u001b\"x\ufffd5e\u0010\ufffd\ufffdr5\ufffd\ufffd\ufffd\u003e\ufffd~\ufffde\ufffdAP{\ufffd֜\ufffd\u0015\ufffd\u0015\ufffdʆ\ufffd=\ufffd!\u00112\ufffd\ufffd\ufffd\ufffd\ufffd̵cH\tV\ufffdAU\ufffd\ufffd\ufffdFx+9C\ufffd\t\ufffdq\r1\u000f\u001c\ufffd\ufffd\ufffd.5\ufffd\u0014\ufffdv\ufffd\ufffdu\u0003\u0003\ufffd]\u0007\u001b\ufffd\u0018|\ufffd\ufffdAx\ufffd\ufffd|\ufffdʱ\ufffd\ufffd\ufffdƷc6l\"Ss\u0014\ufffd=œ\ufffd\r\ufffdO\ufffd\u003en\u001c\ufffd4nsb\ufffdx\ufffd1Є\ufffd\ufffd\ufffdf\u001f\u0002\ufffd\u0004\u0006\ufffd\ufffdk\ufffd\ufffdX\u003c\ufffd \ufffd҄\ufffd\ufffd3\ufffd;\ufffd\ufffd\ufffd\ufffdt[[1\ufffd-\u0006\ufffd\ufffd\ufffd\u003e\ufffd\ufffdfF\ufffd\u0003\ufffd\ufffdr\u0008{\ufffdo9v\ufffd\u003e-$\ufffd\u0004\ufffd\ufffd\ufffda\ufffd\ufffd\ufffdv\u0007\ufffd\ufffd%̖\ufffd\u0011\ufffd\ufffd\u0019o\ufffd\"\ufffd0l\\_\ufffd\ufffdH\ufffd\u000e\ufffd\ufffd\ufffd\u001c\ufffd\ufffd\ufffdu\u0017\ufffd60\u0011\ufffd1\ufffdY\u0010\ufffd\u0006\ufffdn\ufffd\ufffdEH\ufffdx\ufffdpӤ\ufffd[\ufffd\ufffdXg\ufffd\ufffd\ufffd\ufffd\u0019\ufffd\ufffd\ufffd\u0012\ufffd\ufffdds%\ufffd[\u0026\ufffd9n\u001e\ufffd\ufffd\ufffd\ufffdn\ufffd\ufffd\ufffd\ufffd\ufffd|\ufffd\ufffd\ufffd\ufffd\ufffde\ufffdV6ii\ufffd\u0016\u001a\ufffd\u0011\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd}m\ufffd\ufffd^\ufffd\ufffd\u0000\ufffd\u003e\ufffd/\ufffd\ufffd\ufffd\ufffd\ufffdק\ufffd\ufffd\ufffd\u0013\ufffdE\ufffd\ufffd@\u0017%\ufffd\u0003]쵠g\ufffd\ufffd\ufffd\ufffd\\|\ufffd}\ufffd1\ufffd\ufffd\ufffd\ufffd\u001c\ufffd\ufffd\ufffdtq\ufffd뜞u\ufffd81:\u003c\ufffd. \ufffd89\ufffdEI\ufffd@\u0017{-\ufffd\u0019\ufffd\ufffd\ufffd\ufffd؂㘉\ufffd\ufffd\ufffd\ufffd\ufffdB\ufffd;\u0013\ufffd\ufffd\ufffd\ufffd\ufffd'`\ufffd\ufffd\ufffd\ufffdY\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\t\ufffd#\u003c\u003e0GI\ufffd\ufffd\u001c{-\ufffdp/)\u000f\ufffdC\ufffdKN\ufffd\ufffdO@\u0017\ufffd\ufffd\u0003]\ufffdd\u000ftQ\ufffd]\ufffd\u0006\ufffdat\ufffd\ufffd\ufffdms\ufffdX\u0013)5Xk\ufffd\ufffd$\ufffd\ufffd\ufffdz\ufffd;\\7\ufffd\ufffd{\ufffdmdN'\ufffd\u0015ǎ\u0017\ufffd\ufffdc\ufffd-\ufffd\ufffda\ufffd\ufffd\ufffd7\ufffd\ufffd\ufffd+(fʕ\ufffd`\ufffd|\ufffd\ufffd\ufffd)m\u0019S\ufffd\ufffd\u0005\ufffdOo\u0007D2\ufffdQ2\ufffdL\ufffd\ufffdB\ufffd\u003cZ\ufffd\ufffd\"\"\ufffd8\u0013L\ufffdHN\ufffd\u0014\ufffd\ufffd\ufffdЭ%\ufffd\t\u0016X3\ufffd^\ufffdr\u0016zOx\ufffd\ufffd1\ufffd\ufffd\u000b\ufffd\ufffd\u0003\ufffd\ufffd\ufffdJXm@\u000ezL\r\u0015Ő8\ufffd\u0006h\ufffd\ufffdX\ufffd\ufffd\ufffdzx6ȱLig\ufffd\ufffdb\ufffd\ufffd\u000f\ufffdMG\ufffd\ufffdG\ufffd\ufffd^\ufffdE\ufffdV\u001dU\ufffd\u001eU\ufffd\ufffd\u0016\r-\ufffdz\u000f\ufffdOc\ufffd0\u0015\ufffdc\ufffdg\u0012\ufffd\u0012\ufffdtф\ufffd\u000f\ufffd\ufffd\\=\u00004\ufffd:\u0013I\ufffd\ufffd\u000c\ufffd\ufffd\u0018Đ\ufffd\ufffd\ufffd\ufffdI)m\u0001\ufffd\ufffd\ufffdpL\ufffdL\ufffd\ufffd\u0014\ufffd|\ufffdM8\ufffd`\ufffdM\u0001\\\t\ufffd\ufffdX\u0014Q\ufffd\u0015%jD\ufffd\u0013\u000f\ufffd1\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdn\u003c\u0017\ufffd/\ufffd\ufffd]\ufffdO~\ufffd=\ufffd\ufffd}\ufffdk\ufffd\ufffd\ufffd\ufffd~k:\ufffd\u0006\ufffd5\ufffd\ufffdKM;-\ufffd$fzt\ufffd\ufffd\u000c\ufffdZ\ufffd{W\ufffd\ufffd恈\u0019[\ufffd\r#2K\ufffd\ufffd]\ufffd\u001e\ufffdN\ufffdq\ufffd\ufffdݰB¥\ufffd\ufffdr\ufffdR\u0010\ufffd\ufffd]\ufffd|a\ufffd\ufffd\ufffd*\ufffd\ufffdh\u0014\ufffd\ufffd\ufffd\ufffd\ufffdU\ufffdPs\ufffdl#\u001c\ufffd\ufffd\ufffd8\ufffd \ufffd\u0004\ufffd\ufffd11\ufffdy\ufffdj\ufffd$\ufffd|\ufffd\ufffd[\ufffd~\ufffd7In\n\ufffd|\ufffd㍜a\ufffd\ufffd4pE\u0026\ufffd\ufffdi\ufffd\ufffd-v`\t]\ufffd\ufffd6a\ufffd4tmo\u000c\ufffd\ufffd\ufffd\ufffd`G\ufffdlS\ufffd:\u0000e\ufffd\u0013\u0000\ufffd\ufffdy\u001b6+u\ufffd\"\ufffdv\ufffd\ufffd\ufffdL{\ufffd\u0026\ufffdwI\ufffds\ufffdM!\ufffd\ufffd\ufffdT\ufffdي5S@\ufffd\ufffd\ufffd\ufffd\u000b\ufffdj4\ufffd\ufffdfԲ\ufffdi\ufffd\u00126+\ufffd͵{(\ufffdx\ufffd\ufffd@m`\ufffd\ufffdS\u000f\ufffd.e\ufffdY\ufffd\u000b\ufffd\ufffd\ufffd\ufffd[\ufffd\ufffdo\ufffdmE[+\ufffd\u001b\ufffd\ufffd\ufffd\ufffdL2\ufffd\u003e\u0001C@\u001fu\ufffdi\ufffd\ufffd\ufffd^\ufffd׬\ufffdXim2S\ufffd\u0002U'$\ufffd\ufffd\ufffd\u001ei\ufffdժ@jW\ufffdQ\u001a\ufffd$\ufffd\u001d\ufffd\ufffdP5\ufffdd\ufffdC\u0019\ufffd\ufffdkw(\u0003,\ufffd\ufffd_\u0010S}Eب\ufffd\ufffdx\ufffdIJ]l\ufffd\ufffd\ufffd\ufffd\u0015\ufffdvV\n\ufffd\ufffd\ufffd\ufffd\ufffd7p$\ufffd?v\ufffd?\ufffd\ufffd;\ufffd\u00041\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2094\r\nCache-Control: no-cache\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:41 GMT\r\nSet-Cookie: AWSALB=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq; Expires=Thu, 14 Sep 2023 15:34:41 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq; Expires=Thu, 14 Sep 2023 15:34:41 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:04:46+05:30","url":"https://ginandjuice.shop/logout","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; AWSALB=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq; AWSALBCORS=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq","Referer":"https://ginandjuice.shop/my-account","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/logout","scheme":"https"},"raw":"GET /logout HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; AWSALB=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq; AWSALBCORS=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq\r\nReferer: https://ginandjuice.shop/my-account\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"0","Date":"Thu, 07 Sep 2023 15:34:47 GMT","Location":"/","Set-Cookie":"AWSALB=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/, AWSALBCORS=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/; SameSite=None; Secure, session=UeLc4S4hyi19aLba4fvsmba8ZhjwmwH2; Secure; HttpOnly; SameSite=None","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"raw":"HTTP/1.1 302 Found\r\nConnection: close\r\nContent-Encoding: gzip\r\nDate: Thu, 07 Sep 2023 15:34:47 GMT\r\nLocation: /\r\nSet-Cookie: AWSALB=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/; SameSite=None; Secure\r\nSet-Cookie: session=UeLc4S4hyi19aLba4fvsmba8ZhjwmwH2; Secure; HttpOnly; SameSite=None\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\nContent-Length: 0\r\n\r\n"}} {"timestamp":"2023-09-07T21:04:47+05:30","url":"https://ginandjuice.shop/","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; AWSALB=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; AWSALBCORS=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; session=UeLc4S4hyi19aLba4fvsmba8ZhjwmwH2","Referer":"https://ginandjuice.shop/my-account","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/","scheme":"https"},"raw":"GET / HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; AWSALB=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; AWSALBCORS=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; session=UeLc4S4hyi19aLba4fvsmba8ZhjwmwH2\r\nReferer: https://ginandjuice.shop/my-account\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"2128","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:47 GMT","Set-Cookie":"AWSALB=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/, AWSALBCORS=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffd\u001a\ufffdr\ufffd6\ufffd\ufffdb˻\ufffd\ufffd\ufffdP\ufffd$\ufffd\ufffdL$u\u0012;\u001f\ufffd\ufffd\ufffd[{\ufffd\ufffd\ufffdd \u0012\ufffd\ufffd\ufffd\u0000K\ufffd\ufffd\ufffdH}\ufffd{\ufffd[\u0000\ufffdLI\ufffdH\ufffd\ufffdLn\u0026\u001e\ufffd-\u0000\ufffd\ufffd\ufffd~/\ufffd\ufffd\ufffdg\ufffdO\ufffd\u003e\\\ufffd\ufffd\ufffdN\ufffd仑\ufffd\u0007\ufffd3\ufffdS\u0012\ufffd\ufffdvș\ufffd\u0004\ufffd\ufffd\ufffd\ufffdAF\ufffd̳\ufffd\ufffd\ufffd\ufffd\ufffd\u0001\ufffdY\u0010*\u0015\ufffd\ufffd\ufffd_\ufffd[;\ufffd\ufffd\t\ufffd(\u001f+]p\ufffd\ufffd\ufffd\ufffd6b\ufffd\u0004\u0012T\ufffdH\u0026\ufffd\ufffd\t$T\u0013\u0010$\ufffdco\ufffd\ufffd2\ufffd\ufffd\ufffd \ufffdBS\ufffd\ufffdޒEz\u003e\ufffd肅Է\ufffdǐ+\ufffd\ufffd\ufffd!\ufffd\ufffd\ufffdXH\ufffdBM\ufffd\u0019K5\ufffd,\u001c{\u0015\ufffd\ufffd\u0015\u000eH\ufffd{H\ufffdr\ufffd\u0026H\ufffdw\ufffd\ufffd\ufffd(p\u0018\ufffdI\ufffd\ufffdL\u000e \ufffd\ufffd\u0014O\ufffd\ufffd\ufffd\u000e\ufffdɂ\ufffdY\ufffd\ufffd:\u0011q\ufffdI\ufffd\ufffd\ufffd?\ufffd\ufffd6Q\ufffdLs:y+\u0013\n\u003e\ufffda\u0002\u001e\ufffd$}\u000e?\ufffd(\u001f\ufffd\ufffd\ufffdt\u00148\u0010\ufffd\ufffd\ufffdV\ufffd\ufffd\ufffd\ufffd\n\u0010\ufffdOҴB0b\u000b`\ufffdثj\ufffd\"Pw\u0014\u001aj\u0026\u0005\ufffd\ufffd(5\ufffd\ufffd\ufffd\ufffd\u0011u\ufffd\ufffd\ufffd\u0016\ufffdEJw\ufffd\ufffd\ufffd՜)\ufffd_\u0002\u0011\ufffdlJ3\ufffd)/`\ufffds\ufffd\ufffdQ\ufffd\ufffd\ufffdS@\u001e9\u000b\ufffd\ufffd\u0015\ufffda\ufffd\ufffd\u0011\ufffdd\u0006\ufffd*\ufffdDl\ufffd\u001e\ufffd\ufffdJ\ufffd\ufffd\u0010\u0019g\ufffdpS\ufffd(8\ufffdz\ufffd\u000c\ufffdTJ\ufffdG1\ufffd\ufffd\ufffd\u0014\ufffdD\ufffdBe\ufffd̘\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdM\u001f\ufffd\ufffd_/\ufffd\ufffd\ufffd\ufffd`c5\ufffd\ufffd\ufffd\ufffdЭ\ufffd\ufffdqVT\ufffdO6\ufffd\ufffdt\ufffd\ufffd\u0019\u003c\ufffdn\ufffd\ufffd\u0001T\ufffd\ufffd\u0018\u000e7\ufffd'\ufffd\ufffd\ufffdRZc ZN\u001f\u001e\ufffd\u000f\ufffdӟ\ufffdy\ufffd\ufffd\ufffd5#ܤ\ufffd\u001b\ufffd\ufffd\ufffdx|\ufffdh\ufffd+\ufffd\ufffd\ufffd\ufffdz\ufffd\ufffdV\ufffd\ufffd\ufffd\ufffd\ufffdܠfnX3w\ufffdM.r\r\u0005\ufffd5\ufffdz\u0000-AϩU\ufffd\ufffdh\ufffd(\ufffd\u001as\t\ufffd\ufffd\u0005\u0001\ufffd\ufffd\ufffd\ufffd\ufffd#g\ufffd/\ufffdFG5d\ufffdy;P\u0013R\u0008C\ufffdz\ufffd\u001c\u0011\u0017Ŏ\ufffd\u001a25\ufffd\ufffd\rm\u003e\ufffd\ufffdD?\ufffd\ufffd\u0006\ufffd\u0001iX\ufffd%e\u001d\ufffdo\ufffd\ufffd\ufffd\u0008Ұ\ufffd\u001a\"\ufffd+\ufffdFX\u0016[\ufffd\ufffdK\ufffd5Ʈ \ufffdXB\ufffdj\u001cw\ufffd$g\ufffd@\u0016\ufffd\ufffd\ufffd\ufffd\ufffdZcPp\u0002\ufffd\u0002\ufffdL\ufffdrB\ufffd\ufffdd\ufffd\ufffdZ5\ne\ufffd\\\ufffde\ufffd\ufffds7\ufffd\ufffd\ufffdĿ_\u0015[d*s\ufffdM\ufffd\u001b?\ufffd2+\ufffd\u0010s\ufffd \ufffdw6!\ufffdA)\ufffd{6\"\u0012\ufffd2\u0017\ufffdgaE*I\ufffd\ufffd\ufffd\ufffd\u001d\ufffd\"\ufffd\ufffd0\ufffd\ufffd\u0016o\ufffd)Zxl\ufffd!\ufffd\n\ufffd\ufffd\ufffd\ufffdؠ\ufffdn\ufffd\ufffd\u0026\ufffd\u001a:]\ufffd[\ufffd\ufffd\u001b\ufffdA\ufffd\ufffd=\ufffd\ufffd9T\u000f~.c`\ufffd3ǖn'\ufffd\ufffd_\ufffd)\ufffd\ufffd|O\ufffd\ufffd\ufffdvwz\ufffda\ufffd!ɶ\ufffd\ufffd\u000c|\ufffdY1\ufffdj2_\ufffd̀\u001d\ufffdנZӽ\ufffd~\ufffd\ufffdv\u003er\ufffd\ufffd}-\ufffd\ufffdS܍l\ufffdf,\ufffdM\ufffd\ufffdN\ufffd\u003eL\ufffd\u0000\ufffd\ufffdkT\u001a\u0005\ufffdy\ufffd!\ufffd\ufffdL\ufffd\ufffd\u0013[P6#c\ufffdb:\ufffd9\u0016\ufffd-Up\ufffd\ufffdz\ufffd\ufffd\ufffd\n\ufffd\ufffd\u0004\ufffd탳\ufffd.\ufffd\ufffd\u0005-5\ufffd\ufffd\ufffdo\u0019y\ufffd\u0010\u0003\ufffdܰ\ufffdŲ-4\\\ufffd\ufffd'f\ufffd*\ufffd\ufffdc\ufffd\ufffd\ufffd\ufffd!\ufffd\ufffd\ufffd0@8Ǻ\ufffd%\ufffd7Tt;̚ndE\ufffd\"\ufffd\ufffd\ufffd}\ufffdVļ\ufffd:\ufffdۙl`\ufffd\ufffdS\ufffd\ufffd4v\u0017\ufffdK\r\ufffd\u0007\u0003\ufffd\u0016\ufffdZM\ufffdI8c\ufffdlp\ufffd\ufffd\ufffd\ufffd?d\ufffd#\ufffd\u0013\u0005\ufffdxr\ufffd\ufffd\u000c\u0014r\ufffd!\ufffd\ufffd\ufffd\n\ufffd\u000cb\u0026\ufffdǮ-\ufffd\ufffdd\ufffdݑ)E\ufffd'\ufffdSS\r+Ԭ\ufffd\u0015Vl\ufffde\ufffd\ufffdom\ufffd=\ufffd%\u001d:F7\ufffd\u000b\ufffds\ufffdu\u0019\ufffd@P\u001a\ufffd\ufffdN\ufffd\ufffdi܍-@\ufffd\ufffd\ufffd\ufffd}\ufffdF\ufffdf\ufffd[~\ufffd\u000e'X)\ufffd\ufffd\ufffd\ufffdNE\u0018\ufffdNL%+?\ufffd\ufffd\ufffd\ufffd\ufffd\u000et,-\ufffd\ufffd\ufffd%\ufffdMW\ufffd\ufffd\ufffdV\u0014\ufffd6PA\ufffd욦\ufffd~\ufffd\u00113g\ufffd\ufffd\"\ufffd\ufffd\ufffd|8\ufffdX\ufffd\ufffd+\ufffd\u000f\ufffd2\ufffd\ufffdR\ufffd\u0018\u0002\ufffd\u0007\ufffd\ufffd\ufffdl3b\ufffd\u0015\ufffd\ufffdpUuXl\ufffdBL\u0018\ufffd\u001c\u001e\ufffd\ufffd\u001c7\ufffd\ufffd\ufffdT\ufffd\ufffd\ufffd\ufffdԈ\ufffdө\ufffdĺ\u0015\u0002\u001d,`pO\u0016\ufffd\ufffd\u001f\ufffdr\u0017\ufffd჉\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd'wU\ufffd\ufffdOz\ufffd~\ufffd\ufffdV\ufffd\ufffd\ufffd\u0014~|\ufffd\ufffd_g9\ufffd\ufffd\u001e\ufffd\u0019'\ufffd\ufffd\ufffd\ufffd\ufffd٠\ufffd\ufffd\ufffdש斲ʁ\ufffdON\ufffdFr_U\ufffd\ufffdj9\ufffd\ufffd\ufffd\u0018R\ufffd.Ir\u000b\ufffdOe;\u000e\ufffd\ufffd\u0002\u0005\u0006\ufffdG\ufffd\ufffdyE\ufffd\ufffd\ufffdaU\ufffd\ufffdw\ufffd\ufffd^{\u00176\ufffd\u000f\u0026/\ufffd-aY\u0001g\ufffd@\ufffd\u001e\ufffdr\ufffda\u003cXC\ufffd*A\ufffdx\ufffd8\ufffdj3\ufffd\u000eV\ufffd\ufffd$,ȗ\u0015\ufffdI'\ufffd\u000e\u000f\u0010\ufffd\u0015\u0016\ufffd\ufffd2I9\ufffdB\ufffd^Dz\ufffd\u0005E\ufffd\ufffd\t\ufffd\ufffd\u001eh\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdn\ufffd\ufffd\ufffd\ufffd\ufffd{\u0026%\ufffd\u001c\ufffd2Ò\ufffd\ufffd\ufffdݪH\u001dBS\ufffd\ufffdK\ufffdә\ufffd\u0011\ufffdso\ufffd\ufffd\u0013\ufffdFh_\ufffd@\u000bxG1}@”{a\"\u001c|P\ufffd\ufffd\ufffd\ufffdS\nB.\ufffd\ufffd\t\ufffd\ufffd?Kl\ufffdLͱ4\u001dߒE\u0014B\ufffd$\ufffd\ufffdn\ufffd\ufffd\ufffd\ufffd\ufffd\u0008\ufffdm7#\u00023L\ufffd\u001e\ufffd\ufffd)z\u0013\ufffd\\\ufffd\u0005\ufffdD\ufffd\ufffd\ufffd9B\ufffd\ufffd\ufffd\u000f\ufffd\r\ufffd\ufffd\ufffdy\u000c\ufffdDQ\u001c\ufffd\ufffds2\ufffd\ufffd\ufffd7pl('2\ufffd-\ufffdF\ufffd\ufffd\ufffdh\ufffd\u0015q}\ufffdw\ufffd\ufffd\\\ufffd\ufffd\ufffdG\ufffdZY\ufffd7\ufffd\u0015\ufffdw+\ufffd,\ufffd ´\ufffd'Tϥi\ufffdM\u0014pS\ufffd\ufffd@\ufffd\ufffd\ufffd%\ufffd\ufffd*L\ufffd\ufffdF1\ufffd\ufffd\ufffd\u000c{i\ufffdHJ\u0013̋\ufffd%\ufffd}D\ufffd\u000e\ufffd\\\ufffd\ufffd4\ufffd\ufffd\ufffd\u0014\ufffd\"\u003cm[\ufffdSKޛ\ufffd(\ufffd\ufffd+ߚC\ufffd\ufffd\u003c0\ufffd\u001e\u000e\ufffd͟\ufffdIt\u001a\ufffd.^\u003cS\ufffd\ufffd)5\ufffd\ufffd\ufffd\ufffd\u001f\ufffd_/\ufffd^\ufffd\ufffd\ufffdж_\u0019P\ufffd\ufffd@\ufffd\ufffd(\ufffd\ufffd\ufffd\ufffd\ufffdJ2\ufffd\ufffd-ߥ\ufffd4\ufffd\ufffd\n\u000be\ufffdJộ\u0003\ufffdc\ufffd\ufffd\ufffd\ufffd-'\u000c\ufffdT\ufffd_\ufffdS\ufffd\ufffd\ufffd\ufffdS9}ji\ufffdY\ufffd\u001e\ufffd7\n\ufffd\ufffdxz۩\ufffd\ufffd\ufffd\ufffdLv.#\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdOg`\"A\ufffd\ufffdX\ufffd\ufffd\ufffd\ufffd;\ufffd\ufffd\ufffd\ufffd\ufffdmio\ufffd\ufffda\ufffd\ufffd\ufffd0\ufffdپ\ufffd\ufffdM\ufffd`0\u0006i\ufffd\u0015\ufffd\ufffd\ufffd\u0017\ufffd\ufffd\ufffd*i\ufffd\ufffdK\ufffdk%\ufffd\ufffdƥ]\ufffd\ufffd\ufffd\ufffd\ufffd34\ufffd!\ufffd\ufffd\ufffd!QF#\u001f\ufffd\ufffd'oKf\ufffd%(\ufffdn_\ufffd\ufffd\ufffd\ufffd\u0000\ufffdt\ufffd\"M\ufffd\ufffd\ufffdc\n\ufffd\ufffd\ufffd\ufffd\ufffdީ\ufffd˷\ufffd|[\ufffd\ufffdd\u001a\ufffd\u0014\ufffd\u0019\ufffdQ\ufffd\ufffdY\u0008\ufffd\ufffd/\ufffd썵\u00070ա\u00020^ѭ\ufffd]e=T]\ufffd\ufffd\ufffd!\ufffd\ufffd\ufffd\u000e\ufffdZڋz\ufffd@\ufffdI\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdK\ufffd!\\ \ufffdKG\u0001\ufffdu\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\u001c_\ufffd\ufffd\ufffdZ\ufffd\ufffdΩ-H\ufffd\ufffd\ufffd\u0003ʒw\ufffd\ufffdΎ퉒!j\ufffd\ufffd\ufffd\u0011\ufffdMx\ufffd^\ufffd\ufffd\u003cw\ufffd^\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdշ\u0008[\u000ff[Æ\ufffd\ufffd\ufffd\ufffd\ufffdM\ufffd\ufffd\u0001w\ufffdRLF2*\u0026\ufffdaJ\ufffd\ufffdZ\ufffd\u001f\u000blT\ufffd(\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2128\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:47 GMT\r\nSet-Cookie: AWSALB=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} {"timestamp":"2023-09-07T21:04:52+05:30","url":"https://ginandjuice.shop/catalog","request":{"header":{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"en-US,en;q=0.9,hi;q=0.8","Connection":"close","Cookie":"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=UeLc4S4hyi19aLba4fvsmba8ZhjwmwH2; AWSALB=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom; AWSALBCORS=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom","Referer":"https://ginandjuice.shop/","Sec-Ch-Ua":"\"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"","Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Platform":"\"macOS\"","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"same-origin","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36","host":"ginandjuice.shop","method":"GET","path":"/catalog","scheme":"https"},"raw":"GET /catalog HTTP/1.1\r\nHost: ginandjuice.shop\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\r\nConnection: close\r\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=UeLc4S4hyi19aLba4fvsmba8ZhjwmwH2; AWSALB=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom; AWSALBCORS=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom\r\nReferer: https://ginandjuice.shop/\r\nSec-Ch-Ua: \"Chromium\";v=\"116\", \"Not)A;Brand\";v=\"24\", \"Google Chrome\";v=\"116\"\r\nSec-Ch-Ua-Mobile: ?0\r\nSec-Ch-Ua-Platform: \"macOS\"\r\nSec-Fetch-Dest: document\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-Site: same-origin\r\nSec-Fetch-User: ?1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\r\n\r\n"},"response":{"header":{"Content-Encoding":"gzip","Content-Length":"2876","Content-Type":"text/html; charset=utf-8","Date":"Thu, 07 Sep 2023 15:34:52 GMT","Set-Cookie":"AWSALB=AymL6NoUb7IQ+yLeqn8Nx2AnsAEQE7J/dECy9Mv2IjZxSjlYL3ClOPOJQywrvrB2uDIqCLT8bh+PpOR56TWHYLLnsfn2bXbh+12VUozjFNV+c3PYsBusyKnEB1Ut; Expires=Thu, 14 Sep 2023 15:34:52 GMT; Path=/, AWSALBCORS=AymL6NoUb7IQ+yLeqn8Nx2AnsAEQE7J/dECy9Mv2IjZxSjlYL3ClOPOJQywrvrB2uDIqCLT8bh+PpOR56TWHYLLnsfn2bXbh+12VUozjFNV+c3PYsBusyKnEB1Ut; Expires=Thu, 14 Sep 2023 15:34:52 GMT; Path=/; SameSite=None; Secure","X-Backend":"ecfca00b-5a9e-4a89-91bd-de41ecbc4611","X-Frame-Options":"SAMEORIGIN"},"body":"\u001f\ufffd\u0008\u0000\u0000\u0000\u0000\u0000\u0000\ufffd\ufffd\\}r\ufffd6\u0016\ufffd\ufffd\ufffd@\ufffd\ufffd\ufffd=#Q\ufffd\ufffd\ufffdĒ:\ufffd\ufffd\ufffdi\ufffd8\ufffd\ufffd\ufffdvwv\u003c\u0010\tI\ufffdA\ufffd\u0005@)\ufffdLf\ufffd\u001a{\ufffd\ufffd\ufffd\u001eeO\ufffd\u000f\u0000EQ\u0012)\ufffd.;^\ufffd֓\ufffdE\u0000\ufffd\ufffd\ufffd\ufffd\ufffd\u0017(\ufffd\ufffdg\ufffdn.\ufffd\ufffd\ufffd\ufffd\u0012\ufffdU\ufffd\ufffd_t\ufffd/\u0004?\ufffd1\ufffd\ufffd\ufffdh\u001e\u0019\r\ufffd\ufffdX\ufffda\ufffd)\ufffd\ufffd\ufffd\ufffd\ufffdl2\u003c\ufffdÈhzR6\ufffd\ufffd\ufffd\ufffd\ufffdצ\ufffd\ufffd\u0006$\u0008\ufffdI5cD\ufffd\tQۘi\u0016\ufffdP^z\u003c\u0008\u00084\ufffd\n\u001e\ufffd J\ufffd\ufffdA@\u0014F!\u000eHϙP2\ufffd\ufffdP\u000e\ufffdx\ufffdH\ufffdzΔ\ufffdj\ufffd\ufffdɄz\ufffda\u001e\ufffd(\ufffdD4`\ufffd0\u0003#\ufffd\ufffd;\u0019n\ufffd\u00134RH\n\ufffd\ufffdd\u0004\ufffd \ufffd\u0001{\ufffd\u0005N\ufffd\ufffd(\u0000\ufffd\ufffd\u0007\ufffd\ufffd\ufffdMKQ\ufffdE\ufffd\ufffd\ufffd\u0003بY\u0004+S\ufffd\ufffdj~\ufffd\u0013l[\ufffd\u003c\ufffd8\u001c\ufffd\u000c\ufffd\ufffdV\ufffd\ufffdqR\ufffdUQ\ufffdH\ufffd\ufffd\ufffd~\ufffd)\ufffd\u001a芆\ufffd9\u000e\ufffd3\ufffdM\u000c:B\ufffdc\u001eu\ufffdv\ufffd\ufffd\ufffd\ufffd„\ufffd\u0003\ufffd\ufffdP8j\ufffd(\ufffd0\ufffd\ufffd\u0004Q\ufffd\ufffdd-'\ufffdT\ufffd\u001c\ufffd)\ufffdC\ufffd1,eϱ\ufffd\ufffd\ufffd\ufffd\ufffd\u0010zV\u0008\u000cQ\ufffdަޏ\ufffdD\ufffd\u000f#\ufffd0: \u0002+\ufffdfh\u0012\ufffd\u0010\u003e\u0003\ufffdhJ\u0006\u0008dd\ufffd\ufffdfV\ufffd\ufffd\ufffdB\ufffd\ufffd!\u0017H\u0011\ufffdh8҃\ufffd\ufffd\u0003\u0019\ufffd\ufffd\t)\ufffdjf\ufffd\ufffdR\ufffdM\ufffd\ufffd\u0002te\ufffdӥh\u000bv\u0010\u000f\u0003\u000e\ufffd\u0005\ufffd\u0012\ufffd\ufffd.\ufffd\ufffdԽ\ufffd\ufffd\ufffdB=\ufffdJ\ufffd\ufffdd\ufffd\ufffd\ufffd\ufffd\u001b@w\ufffd\ufffdc{\ufffd\ufffd\ufffd\u001af\ufffd\u000f\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd!\ufffd\ufffdN\ufffd\ufffd9\ufffdε\ufffd\ufffdY~\u003c\ufffdG\ufffd'\ufffd\ufffd!\ufffd\ufffd`\ufffdv\ufffd\ufffdkkpuļ\ufffd5\ufffd\ufffd\u0018\ufffdW\ufffdy\ufffd^\ufffd\ufffd\ufffd\ufffd\ufffd\u0008\ufffd2\u0003\u000eD\ufffd\u000c\ufffd\ufffd\u0019Zkk\ufffd\ufffdur\ufffd\u000e\ufffd\ufffd\ufffdX\ufffd\u0019\ufffd6Jp@\ufffd#5\u0026\u0006\ufffdg`֠\ufffd\u001csi\ufffd\ufffd\u000b\u000c\ufffdV\ufffd\ufffd\u0016k\ufffdl\ufffd\ufffdD\ufffd\u001c\ufffdڼ\ufffdP\u001dV0\ufffda\ufffdv\ufffd\ufffd6\ufffd՚9lr\ufffdI\u0013\ufffd\u001a\ufffd\ufffd8\ufffd*\ufffd\u0017\ufffdm₎\u0005+㰍-\ufffd\u0019\ufffd\u0010\u0017L\ufffd\ufffd\ufffd\ufffdܴa,\u001d\u0019\ufffdh$\u000e\ufffd\ufffd]\ufffdH\ufffd\u0000\ufffd\ufffd}\ufffdq\ufffd5\ufffd\ufffdn\u001fd\u0006\ufffd\ufffdԃX)\ufffdH[\ufffd\ufffd\ufffd\ufffdX\ufffd:M0Q\u000c\ns\ufffd\u0018V\ufffd\ufffd%\ufffd\ufffd2\u0012\u003cZ\ufffdT\ufffd\ufffd\u0011\ufffd\u001c\ufffd\ufffd\ufffd\u0012\u000b\u000fx\ufffd\ufffd\ufffd\ufffdv(\ufffdŬ\"\ufffd\ufffd͘\ufffdj[\u0002g\ufffd\ufffd\ufffd\u001b[\u0013\ufffd\u003c\u001e\ufffd\ufffdA\ufffd\ufffdV\ufffdY#i\ufffdT)'\ufffd~\u0017\u0002ڼs\ufffdZ\ufffd\ufffdG\ufffd\u0017\ufffd\ufffdǻ0\ufffd\u000fK\u003c\ufffd\ufffdXg\\ͧ\ufffdz\u0013\ufffd\ufffd\u001b\ufffd\ufffde\ufffd/\ufffd\u001c\ufffd\u000b\ufffd\ufffd#D\ufffd\ufffd\u0012\u001b\ufffd\ufffd\ufffd\ufffd\ufffd\u0015\ufffd\ufffd\u0019J\u003e\ufffdF\ufffdo\ufffd\ufffd\ufffd\ufffd\u001ea\ufffd\u001e\u0016+֚\u0004\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdS`+I\ufffd%\ufffdW\ufffd\u001a\ufffd]p\ufffdݖ^r\ufffd\ufffd\u001b\ufffd\ufffdF\ufffd\ufffdlx\ufffdU\ufffd\ufffdH\ufffd\ufffdv5\ufffdb\n\u001f@\ufffd_\u001b\ufffd\ufffd\ufffd\ufffd\u0004X\ufffd\ufffds\ufffds\n\ufffd\ufffdQ\u00265CŢ\ufffd%d\ufffd\u0013\ufffdR\u0013\u0007\ufffd\ufffd\ufffd\u000c\ufffdW\u000e/ҽ\ufffd\ufffd\ufffd:\ufffd\u0011\u0001\ufffd\u0005q\ufffdkcm\ufffdu\ufffd\ufffd0)\ufffd\ufffd0\\Dش\ufffdEl[\ufffd\u0004\u000c\u000f\u0005e\ufffd\ufffd\u0002%\ufffd\ufffd\u001bo\ufffd\u0006\ufffd\ufffd\u0000\ufffdZ\ufffdd2=\ufffd\u001dޘ\ufffd\ufffd\ufffd\ufffd\ufffd[\ufffd\ufffda\u0014';\u0026\ufffda\ufffd{\u00123\ufffd9\u0016\u000e\ufffd\u0018\ufffdȘ3_\ufffd෦\u001d*\u0019\ufffd\u0008'\ufffdB\ufffd\ufffd\ufffd\ufffd\u0008\ufffd$\ufffd\ufffd\ufffd֪\ufffdg\ufffd!\ufffd\u0026\\A\ufffd\u001e\ufffd\ufffdζ\u0012\ufffd܋\ufffd\ufffdpD\ufffd%#\ufffd\ufffd\ufffd썿WK\ufffdS\ufffdw'\ufffd\ufffd\u0004\u0018.\ufffdof\ufffd\ufffd\u001d\ufffd\u001d\ufffdT^F\ufffd2\u001e\u0004T%\ufffdَ\ufffd\ufffd\\\ufffd\ufffd\u003cn\ufffd4\ufffdg\ufffdu\ufffdV\ufffdi\ufffd\ufffdp4\ufffdv׼p\ufffd\ufffd-\ufffde7\ufffd\ufffd\ufffd\ufffdkS\ufffdЅ\ufffd\u003ea\ufffd\u001f\ufffd\u0010\ufffd\ufffdm\ufffd\ufffd}\ufffdmڮb\ufffdͻpK\u0008\ufffdI\ufffd\ufffd\ufffd\t\ufffd\ufffd\ufffd\ufffd)\ufffd@nE\u0012\ufffd\ufffd2\u0003\ufffd\ufffdɈ\u000bJ$\ufffd\ufffd\ufffd|Ř\ufffdra\ufffdu\ufffd+\u000f\ufffd\ufffd4\u00032\u001d_\u0026d\ufffd^\ufffdߌ\ufffd\u0001\u0004G\ufffd\ufffd)$\ufffd\u000e\ufffd;\ufffd\ufffd\ufffd\ufffd\u000f\ufffd=u犆\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd7\u0005\ufffd\ufffd\ufffd\ufffdsv\ufffd\ufffd\n\ufffd\"\u0019\u0003\ufffd\u000ecƲC\u0004\ufffd\ufffd\ufffd^\ufffd|\ufffd\ufffd\ufffd\u0007h\ufffd+t\ufffd\u0015\ufffd\ufffd\ufffd\ufffd\ufffd}\ufffd\ufffd\ufffd\ufffdm2;\ufffd\ufffd\ufffd\u001e^O\ufffd\ufffdG\ufffd\u003ez\ufffdl\ufffd\ufffd\ufffd4\u0011\u0000\ufffdz\ufffdt\u0008z\ufffdh38\ufffdNqM\ufffd\ufffd\ufffdf\ufffd\u0001\ufffd\\\u0010P#\ufffd\ufffd@y\ufffd\rp\ufffd7o\ufffd\ufffd\u00115SJ\ufffdβ\ufffd3\ufffd\\\ufffdv\ufffd\ufffd\ufffd\ufffd\u0011:\ufffdgG\ufffd\ufffdF\ufffd\ufffdjW\u0015\ufffd\ufffd\ufffd9\u001a\ufffd\ufffd,\ufffds\ufffd\ufffd\ufffddtS\ufffdn֘\ufffdM\ufffdj\ufffd\ufffd\ufffd\ufffdL\ufffd\u0013D\ufffd\"\ufffd;\ufffd^\r\ufffd\ufffd\ufffd\u0013\ufffd'\ufffd\ufffd\ufffd\ufffd\u0017\ufffd\ufffd\ufffd\ufffd\u003e׍\u001a\u0012\ufffd\u003e/Cj'\ufffd\ufffd\u0007\ufffd\ufffd\ufffd %\u0002GmP\ufffd%@\u0012\u0016ڲ\\AB\u0008\ufffd{)\ufffdf\ufffd9\u0016\ufffd\rO\ufffd\ufffd\u0018\ufffd0R5\u0014edc\u003c«5]\ufffd\ufffd\ufffdL~\ufffd\ufffd{\ufffdm\ufffd\ufffd\u0006\ufffd\ufffd%\ufffd\ufffd\ufffd\ufffd\ufffd]œ\ufffd\ncٌ@\u003c\ufffdB\ufffd\ufffd\u0011\ufffdj\ufffd\ufffd(\u001cm\ufffdc\ufffd鿛ӡKK\ufffd.\ufffdw\u000f\ufffde\ufffd\ufffd;\ufffde\\\ufffdG\u0005֯\ufffd:e\ufffdȾk\ufffd\ufffd\ufffd/\ufffd?v\u000eܣ\ufffd\ufffd\ufffd@\ufffd\ufffd\ufffd~\ufffd\ufffd#%S\ufffd\u0013\ufffd\n\ufffd\ufffd\ufffdƚ\ufffd\u000c\ufffd튑l\ufffdJBwa\ufffd(\ufffdY绛i\ufffd\ufffd\u001d=\u0016\ufffd\ufffdC\ufffd\ufffd\ufffdn\u0000ש\u001a\ufffd\ufffd\ufffd\ufffd\ufffd\u00161\ufffd\\7\u0013\"\u0018\ufffd\ufffd'\ufffd\ufffd\u0017m\ufffd\ufffd\ufffdn\ufffduX1\\\ufffdL\u001c\ufffd\ufffd_\ufffd\\~T\u0002\n\u0012.\ufffdֻZ\ufffd\ufffd\ufffdx\ufffdQ\ufffdp\ufffd\ufffdu.\ufffd\u0003\u001a\ufffd\ufffd\u001f\ufffdL\ufffd!\ufffd\ufffdj \ufffd8Z\ufffd؜\ufffd\ufffd\ufffd\u000b\ufffd\ufffd\ufffd\u001b\ufffd\u001dW\ufffd\ufffd\u0000+o|\ufffd\ufffdtNJBx\ufffd\t\ufffd\u0026\ufffd4\ufffd\u001d\ufffd\u001f\u0014#'\u0015cW*\ufffd\ufffd)햁\ufffd\u0004ڻ\ufffd\u0001\ufffdۊ\ufffd\ufffd\ufffdR\ufffd\ufffd-\ufffd\ufffd#1\ufffd\ufffd\ufffd\ufffd\ufffd\u0001)\ufffd\ufffd\ufffdq\ufffd\ufffd\u0019:\u0007\ufffd\ufffd?mQr춏v\u0003\ufffd\u0017\u0015#V6D\ufffdx\ufffd\"\ufffdݎ\ufffd=\ufffd\ufffdHi?\u0016\u003e\ufffd\ufffd\ufffdڻ\u0001_\ufffd\ufffdb\ufffdJ)\r\ufffd\ufffde\ufffd\ufffd\ufffd9\ufffdi\u0000\u0011\ufffd[\ufffd\ufffd\ufffd\ufffdN\ufffdܓ\ufffd\u001d\ufffd\ufffd\ufffd7'\ufffd\ufffd\ufffd]\ufffdR\ufffd\ufffdJp\ufffd\ufffd\ufffd\ufffdx\ufffdI}\ufffd\ufffd\ufffd\u001e\ufffd\ufffd\u0008pU\ufffd(y\ufffd\ufffd\ufffd\ufffd\"\u0016\ufffdN\ufffdՄ\ufffdG\ufffd\u0004\ufffd\u0007\ufffd\ufffd\ufffd\ufffd#\ufffd\ufffd_\ufffd\u001c\ufffd\ufffd\ufffdF\ufffdpd\u0003\ufffdk\u001a\u0012YIt|\ufffd\ufffd\u001d\ufffdLJ;\ufffdX\ufffdoI\ufffd\ufffdR\ufffd\ufffdIE\u0019+\t޷\ufffd\u0004\ufffdj\ufffd\ufffd@\ufffd\ufffdgP\ufffd\ufffd\ufffd\ufffdс{\ufffd#{\ufffdVկN\ufffd\ufffd#\u001abv\ufffd\ufffdٝ\ufffd\ufffd\ufffd\ufffd^h\ufffd\ufffd+1C\ufffd\ufffd_\ufffd߷\ufffd\ufffd\ufffd2PV\ufffd.%\ufffd\ufffd\ufffd\ufffdg\ufffdU\ufffd\ufffdw\u000c\u0001\ufffd\u001a\ufffd'w\ufffd\ufffd\ufffd\u000e\ufffd`\ufffd/R\u0018\ufffdꎆw\ufffdnLfD\ufffd\ufffd\ufffd\u0001\u0015\ufffd$\ufffd\ufffdך\ufffd\ufffd!ܥ(Z\ufffd˔R\ufffd\n\ufffd1E\u000b\ufffd\u0013P\ufffd\ufffd\ufffdpE\ufffd\n\ufffd\ufffd\ufffdO\ufffd\ufffd;\u003euO\ufffd\u0018\ufffd\r_\ufffd\ufffd;\ufffdg\ufffd3'䆜\ufffd3\tS\ufffd\ufffd(\ufffd8\ufffdʷ\ufffd\ufffd\ufffd\ufffd8\ufffd:gF\ufffd\ufffd \ufffd\ufffds\ufffd\ufffd4\ufffd\ufffd\u001f\ufffd\ufffdl\ufffd\ufffd\ufffdK\u0026\ufffd燝\ufffd\ufffd\ufffd\u0002\t\ufffd\ufffdd\u003c\ufffd\u0007\u0016\u0006\u0004\ufffd|\n\ufffd\ufffd\ufffd\ufffd\"\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdS.\ufffd?\ufffd\u003eA\ufffd\u000cb\u001cR5C|\ufffdF:f\ufffd\u003e\ufffd`\ufffd\ufffd\u001a\ufffd\u0010\ufffdǓus;\ufffd|\ufffdX,\ufffd\u0004f\"S\ufffd\ufffdeİ\ufffdza\ufffd\ufffdqУ/d\u0011,\t\u003cy\ufffd1\u003c\ufffdœ*\ufffdg\ufffd9\ufffd\ufffd\ufffdk\u00172\ufffdտ\ufffd'U[\ufffdɔ.\ufffd-\ufffd\ufffd\u001fd-\ufffd\"S\ufffd\ufffd\ufffdy\ufffd9\ufffd\ufffdP\ufffd\u0008\u001c䃿6\ufffd\ufffd\ufffdN\u0004q3iZ=n\ufffd\ufffd|k|0\ufffd\r\u0005\ufffd%\ufffd\u0002\ufffd\ufffd\ufffd\ufffd\u0018\t\ufffd\ufffd\ufffd\u001c%\ufffd\u001f\ufffd\ufffd\u001c^\ufffd\u0026\ufffd\ufffd\ufffd\ufffdm\u0007\ufffds\ufffd;c\ufffd\ufffd$\ufffd\u001fV\ufffd\ufffd\u0018:\ufffd\u001c\u0003\ufffd9\ufffdG\ufffdO\ufffd㛟n\ufffd\ufffd\ufffd{\ufffd\ufffdtz|=\ufffd.\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdk\ufffdӟ\ufffd͗\u001c\ufffd[9z\ufffd9\u0005ؿ\ufffdk\ufffd\ufffdٿB\ufffdH\ufffdҺ6\ufffdy\u003c\ufffdxذ-ۄ\\:\u0001k\ufffd\ufffd\ufffd{\ufffd\n=H\ufffd\ufffd1_'\u000f=F\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd~eD\ufffd\u0003o\u000cվ\ufffd\ufffdm\ufffd\ufffd \u0026\r\ufffd\ufffd\ufffd\ufffd\ufffdz\ufffd\ufffd\ufffd\u000f\ufffd\u0004~:D:\u0012\ufffd\ufffd\u0018b{\ufffd)\ufffdR\ufffdG\ufffd\ufffdG)r+B'\ufffd\u0026\ufffd\ufffd\ufffdt\r\ufffd\ufffd\ufffd[\ufffd@\u000cR\u0010+\ufffd\ufffdߞ\ufffd\ufffd\ufffd\u0026\u0008\ufffdY'lS\u0010R\ufffd\ufffdk\u001d\u0018h\ufffd\ufffd?\u0002\ufffd%\ufffdd\ufffd\u000e\ufffdR\ufffd7 \ufffd\ufffd;+:\ufffdt\ufffd\ufffd\ufffd6\u0005\ufffd\ufffd\ufffd\u001e04\ufffd\u0015El\ufffd\ufffd\ufffd\ufffdh\ufffd\ufffd\u0017\ufffd\ufffdIU}\u001f3҅\ufffd\u0005\u0016\n\r\u0008D3b\ufffd\ufffdn3#\ufffd\ufffd\u0017u7\ufffd\ufffd\u0007\u0008\ufffd\ufffds\ufffd\ufffdQ\ufffdD╬\u0007\ufffd\ufffd^q\ufffd\ufffd\u0004\ufffd\ufffd\ufffd\u001e\ufffd\ufffd\ufffd\ufffd\u0001\ufffd\ufffd\\)\ufffdk\u0006\ufffd\ufffd\u000b\ufffd\u000f\ufffd\u001d\ufffd\u000e8\ufffdZ\u000e\ufffdZ\ufffdn\ufffd\ufffd=\ufffdb@\ufffdj\ufffd\ufffd\u0016\ufffd\u0026(\ufffdsr\u000b\u0012X\ufffd\ufffd\u0001e\ufffd\ufffd\ufffd\ufffdզ*#C\u0026\ufffd\u0007\ufffd^o\ufffd{\ufffdMy\ufffd\ufffd*\ufffd^\ufffd\ufffdo\u0015\ufffd\ufffd\u003e\ufffd\ufffdN\ufffd^a\ufffd\ufffd\ufffd\ufffdcAuj\ufffd\ufffd\u0014\ufffd\ufffdo\u0013\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd?\ufffd\u0001\ufffd\ufffd\ufffd\u0019\ufffd\ufffd\u0002\ufffdYQ\ufffd\ufffdA\u0000\u0000","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 2876\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\nDate: Thu, 07 Sep 2023 15:34:52 GMT\r\nSet-Cookie: AWSALB=AymL6NoUb7IQ+yLeqn8Nx2AnsAEQE7J/dECy9Mv2IjZxSjlYL3ClOPOJQywrvrB2uDIqCLT8bh+PpOR56TWHYLLnsfn2bXbh+12VUozjFNV+c3PYsBusyKnEB1Ut; Expires=Thu, 14 Sep 2023 15:34:52 GMT; Path=/\r\nSet-Cookie: AWSALBCORS=AymL6NoUb7IQ+yLeqn8Nx2AnsAEQE7J/dECy9Mv2IjZxSjlYL3ClOPOJQywrvrB2uDIqCLT8bh+PpOR56TWHYLLnsfn2bXbh+12VUozjFNV+c3PYsBusyKnEB1Ut; Expires=Thu, 14 Sep 2023 15:34:52 GMT; Path=/; SameSite=None; Secure\r\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\r\nX-Frame-Options: SAMEORIGIN\r\n\r\n"}} ================================================ FILE: pkg/input/formats/testdata/ginandjuice.proxify.yaml ================================================ timestamp: 2024-02-20T19:24:13+05:30 url: https://ginandjuice.shop/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: ginandjuice.shop method: GET path: /blog/post scheme: https raw: |+ GET /blog/post?postId=3&source=proxify HTTP/1.1 Host: ginandjuice.shop 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:32 url: https://ginandjuice.shop/users/3 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: ginandjuice.shop method: POST path: /catalog/product scheme: https raw: |+ POST /catalog/product?productId=3 HTTP/1.1 Host: ginandjuice.shop 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

Fruit Overlays

$92.79

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.

View cart
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.

More information...

` 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.

More information...

` body2 := ` Example Domain

It's test time!

` // 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|