diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index a39e441..d0b098d 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -6,38 +6,40 @@
version: 2
updates:
- # Maintain dependencies for GitHub Actions
- - package-ecosystem: "github-actions"
- directory: "/"
- schedule:
- interval: "weekly"
- target-branch: "dev"
- commit-message:
- prefix: "chore"
- include: "scope"
- labels:
- - "Type: Maintenance"
-
# Maintain dependencies for go modules
- package-ecosystem: "gomod"
directory: "/"
schedule:
- interval: "daily"
+ interval: "weekly"
target-branch: "dev"
commit-message:
prefix: "chore"
include: "scope"
labels:
- "Type: Maintenance"
+ allow:
+ - dependency-name: "github.com/projectdiscovery/*"
- # Maintain dependencies for docker
- - package-ecosystem: "docker"
- directory: "/"
- schedule:
- interval: "weekly"
- target-branch: "dev"
- commit-message:
- prefix: "chore"
- include: "scope"
- labels:
- - "Type: Maintenance"
\ No newline at end of file
+# # Maintain dependencies for GitHub Actions
+# - package-ecosystem: "github-actions"
+# directory: "/"
+# schedule:
+# interval: "weekly"
+# target-branch: "dev"
+# commit-message:
+# prefix: "chore"
+# include: "scope"
+# 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"
\ No newline at end of file
diff --git a/.github/release.yml b/.github/release.yml
new file mode 100644
index 0000000..1073bdc
--- /dev/null
+++ b/.github/release.yml
@@ -0,0 +1,17 @@
+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:
+ - "*"
\ No newline at end of file
diff --git a/.github/stale.yml b/.github/stale.yml
new file mode 100644
index 0000000..381a372
--- /dev/null
+++ b/.github/stale.yml
@@ -0,0 +1,26 @@
+# Number of days of inactivity before an issue becomes stale
+daysUntilStale: 14
+
+# Number of days of inactivity before a stale issue is closed
+daysUntilClose: 30
+
+# Issues with these labels will never be considered stale
+# exemptLabels:
+# - pinned
+# - security
+
+# Only issues or pull requests with all of these labels are check if stale.
+onlyLabels:
+ - "Status: Abandoned"
+ - "Type: Question"
+
+# Label to use when marking as stale
+staleLabel: stale
+
+# Comment to post when marking an issue as stale. Set to `false` to disable
+markComment: >
+ This issue has been automatically marked as stale because it has not had
+ activity in the past 2 weeks. It will be closed if no further activity occurs. Thank you
+ for your contributions.
+# Comment to post when closing a stale issue. Set to `false` to disable
+closeComment: This issue is being closed after 30 days of inactivity. Thank you.
\ No newline at end of file
diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index 0dc2651..c4c8bd0 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -13,12 +13,12 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
- os: [ubuntu-latest-16-cores, windows-latest-8-cores, macOS-latest]
+ os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- name: Set up Go
- uses: actions/setup-go@v3
+ uses: actions/setup-go@v4
with:
- go-version: 1.19
+ go-version: 1.21.x
- name: Check out code
uses: actions/checkout@v3
@@ -31,6 +31,10 @@ jobs:
run: go test ./...
working-directory: .
+ - name: Running example
+ run: go run .
+ working-directory: examples/
+
- name: Integration Tests Linux, macOS
if: runner.os == 'Linux' || runner.os == 'macOS'
env:
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 0cff8f6..3080047 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -11,7 +11,7 @@ on:
jobs:
analyze:
name: Analyze
- runs-on: ubuntu-latest-16-cores
+ runs-on: ubuntu-latest
permissions:
actions: read
contents: read
diff --git a/.github/workflows/dep-auto-merge.yml b/.github/workflows/dep-auto-merge.yml
new file mode 100644
index 0000000..84b26e1
--- /dev/null
+++ b/.github/workflows/dep-auto-merge.yml
@@ -0,0 +1,26 @@
+name: 🤖 dep auto merge
+
+on:
+ pull_request:
+ branches:
+ - dev
+ workflow_dispatch:
+
+permissions:
+ pull-requests: write
+ issues: write
+ repository-projects: write
+
+jobs:
+ automerge:
+ runs-on: ubuntu-latest
+ if: github.actor == 'dependabot[bot]'
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ token: ${{ secrets.DEPENDABOT_PAT }}
+
+ - uses: ahmadnassri/action-dependabot-auto-merge@v2
+ with:
+ github-token: ${{ secrets.DEPENDABOT_PAT }}
+ target: all
\ No newline at end of file
diff --git a/.github/workflows/functional-test.yml b/.github/workflows/functional-test.yml
index 7d2e49b..e6787a3 100644
--- a/.github/workflows/functional-test.yml
+++ b/.github/workflows/functional-test.yml
@@ -13,12 +13,12 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
- os: [ubuntu-latest-16-cores, windows-latest-8-cores, macOS-latest]
+ os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- name: Set up Go
- uses: actions/setup-go@v3
+ uses: actions/setup-go@v4
with:
- go-version: 1.19
+ go-version: 1.21.x
- name: Check out code
uses: actions/checkout@v3
diff --git a/.github/workflows/lint-test.yml b/.github/workflows/lint-test.yml
index 0fdf3ea..d8fee0d 100644
--- a/.github/workflows/lint-test.yml
+++ b/.github/workflows/lint-test.yml
@@ -10,16 +10,16 @@ on:
jobs:
lint:
name: Lint Test
- runs-on: ubuntu-latest-16-cores
+ runs-on: ubuntu-latest
steps:
- name: Set up Go
- uses: actions/setup-go@v3
+ uses: actions/setup-go@v4
with:
- go-version: 1.19
+ go-version: 1.21.x
- name: Checkout code
uses: actions/checkout@v3
- name: Run golangci-lint
- uses: golangci/golangci-lint-action@v3.4.0
+ uses: golangci/golangci-lint-action@v3.6.0
with:
version: latest
args: --timeout 5m
diff --git a/.github/workflows/release-binary.yml b/.github/workflows/release-binary.yml
index 2dc5a16..884e0f4 100644
--- a/.github/workflows/release-binary.yml
+++ b/.github/workflows/release-binary.yml
@@ -16,14 +16,14 @@ jobs:
fetch-depth: 0
- name: "Set up Go"
- uses: actions/setup-go@v3
+ uses: actions/setup-go@v4
with:
- go-version: 1.19
+ go-version: 1.21.x
- name: "Create release on GitHub"
uses: goreleaser/goreleaser-action@v4
with:
- args: "release --rm-dist"
+ args: "release --clean"
version: latest
workdir: .
env:
diff --git a/.github/workflows/release-test.yml b/.github/workflows/release-test.yml
new file mode 100644
index 0000000..c915185
--- /dev/null
+++ b/.github/workflows/release-test.yml
@@ -0,0 +1,29 @@
+name: 🔨 Release Test
+
+on:
+ pull_request:
+ paths:
+ - '**.go'
+ - '**.mod'
+ workflow_dispatch:
+
+jobs:
+ release-test:
+ runs-on: ubuntu-latest-16-cores
+ steps:
+ - name: "Check out code"
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - name: Set up Go
+ uses: actions/setup-go@v4
+ with:
+ go-version: 1.21.x
+
+ - name: release test
+ uses: goreleaser/goreleaser-action@v4
+ with:
+ args: "release --clean --snapshot"
+ version: latest
+ workdir: .
diff --git a/.gitignore b/.gitignore
index 028e9b1..064196c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
.idea/
.vscode/
+dist
cmd/httpx/httpx
integration_tests/httpx
integration_tests/integration-test
@@ -7,3 +8,5 @@ cmd/functional-test/httpx_dev
cmd/functional-test/functional-test
cmd/functional-test/httpx
cmd/functional-test/*.cfg
+
+.devcontainer
\ No newline at end of file
diff --git a/.goreleaser.yml b/.goreleaser.yml
index 78f2618..852f6cb 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -28,8 +28,7 @@ builds:
archives:
- format: zip
- replacements:
- darwin: macOS
+ name_template: '{{ .ProjectName }}_{{ .Version }}_{{ if eq .Os "darwin" }}macOS{{ else }}{{ .Os }}{{ end }}_{{ .Arch }}'
checksum:
algorithm: sha256
diff --git a/Dockerfile b/Dockerfile
index 101d761..33474c3 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# Base
-FROM golang:1.20.2-alpine AS builder
+FROM golang:1.21.4-alpine AS builder
RUN apk add --no-cache git build-base gcc musl-dev
WORKDIR /app
@@ -7,9 +7,9 @@ COPY . /app
RUN go mod download
RUN go build ./cmd/httpx
-FROM alpine:3.17.2
-RUN apk -U upgrade --no-cache \
- && apk add --no-cache bind-tools ca-certificates
+FROM alpine:3.18.2
+RUN apk upgrade --no-cache \
+ && apk add --no-cache bind-tools ca-certificates chromium
COPY --from=builder /app/httpx /usr/local/bin/
-ENTRYPOINT ["httpx"]
\ No newline at end of file
+ENTRYPOINT ["httpx"]
diff --git a/README.md b/README.md
index 0c47187..a462253 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
Features •
Installation •
Usage •
- Running httpx •
+ Documentation •
Notes •
Join Discord
@@ -62,12 +62,19 @@
# Installation Instructions
-`httpx` requires **go1.19** to install successfully. Run the following command to get the repo:
+`httpx` requires **go1.21** to install successfully. Run the following command to get the repo:
```sh
go install -v github.com/projectdiscovery/httpx/cmd/httpx@latest
```
+To learn more about installing httpx, see https://docs.projectdiscovery.io/tools/httpx/install.
+
+| :exclamation: **Disclaimer** |
+|---------------------------------|
+| **This project is in active development**. Expect breaking changes with releases. Review the changelog before updating. |
+| This project was primarily built to be used as a standalone CLI tool. **Running it as a service may pose security risks.** It's recommended to use with caution and additional security measures. |
+
# Usage
```sh
@@ -99,6 +106,7 @@ PROBES:
-lc, -line-count display response body line count
-wc, -word-count display response body word count
-title display page title
+ -bp, -body-preview display first N characters of response body (default 100)
-server, -web-server display server name
-td, -tech-detect display technology in use based on wappalyzer dataset
-method display http request method
@@ -106,18 +114,26 @@ PROBES:
-ip display host ip
-cname display host cname
-asn display host asn information
- -cdn display cdn in use
+ -cdn display cdn/waf in use
-probe display probe status
+HEADLESS:
+ -ss, -screenshot enable saving screenshot of the page using headless browser
+ -system-chrome enable using local installed chrome for screenshot
+ -ho, -headless-options string[] start headless chrome with additional options
+ -esb, -exclude-screenshot-bytes enable excluding screenshot bytes from json output
+ -ehb, -exclude-headless-body enable excluding headless header from json output
+ -st, -screenshot-timeout int set timeout for screenshot in seconds (default 10)
+
MATCHERS:
-mc, -match-code string match response with specified status code (-mc 200,302)
-ml, -match-length string match response with specified content length (-ml 100,102)
-mlc, -match-line-count string match response body with specified line count (-mlc 423,532)
-mwc, -match-word-count string match response body with specified word count (-mwc 43,55)
-mfc, -match-favicon string[] match response with specified favicon hash (-mfc 1494302000)
- -ms, -match-string string match response with specified string (case insensitive) (-ms admin)
+ -ms, -match-string string match response with specified string (-ms admin)
-mr, -match-regex string match response with specified regex (-mr admin)
- -mcdn, -match-cdn string[] match host with specified cdn provider (oracle, google, azure, cloudflare, cloudfront, fastly, incapsula, leaseweb, akamai, sucuri)
+ -mcdn, -match-cdn string[] match host with specified cdn provider (cloudfront, fastly, google, leaseweb, stackpath)
-mrt, -match-response-time string match response with specified response time in seconds (-mrt '< 1')
-mdc, -match-condition string match response with dsl expression condition
@@ -127,15 +143,17 @@ EXTRACTOR:
FILTERS:
-fc, -filter-code string filter response with specified status code (-fc 403,401)
+ -fep, -filter-error-page filter response with ML based error page detection
-fl, -filter-length string filter response with specified content length (-fl 23,33)
-flc, -filter-line-count string filter response body with specified line count (-flc 423,532)
-fwc, -filter-word-count string filter response body with specified word count (-fwc 423,532)
- -ffc, -filter-favicon string[] filter response with specified favicon hash (-mfc 1494302000)
+ -ffc, -filter-favicon string[] filter response with specified favicon hash (-ffc 1494302000)
-fs, -filter-string string filter response with specified string (-fs admin)
-fe, -filter-regex string filter response with specified regex (-fe admin)
- -fcdn, -filter-cdn string[] filter host with specified cdn provider (oracle, google, azure, cloudflare, cloudfront, fastly, incapsula, leaseweb, akamai, sucuri)
+ -fcdn, -filter-cdn string[] filter host with specified cdn provider (cloudfront, fastly, google, leaseweb, stackpath)
-frt, -filter-response-time string filter response with specified response time in seconds (-frt '> 1')
-fdc, -filter-condition string filter response with dsl expression condition
+ -strip strips all tags in response. supported formats: html,xml (default html)
RATE-LIMIT:
-t, -threads int number of threads to use (default 50)
@@ -154,19 +172,27 @@ MISCELLANEOUS:
-vhost probe and display server supporting VHOST
-ldv, -list-dsl-variables list json output field keys name that support dsl matcher/filter
+UPDATE:
+ -up, -update update httpx to latest version
+ -duc, -disable-update-check disable automatic httpx update check
+
OUTPUT:
-o, -output string file to write output results
+ -oa, -output-all filename to write output results in all formats
-sr, -store-response store http response to output directory
-srd, -store-response-dir string store http response to custom directory
-csv store output in csv format
-csvo, -csv-output-encoding string define output encoding
- -json store output in JSONL(ines) format
- -irr, -include-response include http request/response in JSON output (-json only)
+ -j, -json store output in JSONL(ines) format
+ -irh, -include-response-header include http response (headers) in JSON output (-json only)
+ -irr, -include-response include http request/response (headers + body) in JSON output (-json only)
-irrb, -include-response-base64 include base64 encoded http request/response in JSON output (-json only)
-include-chain include redirect http chain in JSON output (-json only)
-store-chain include http redirect chain in responses (-sr only)
+ -svrc, -store-vision-recon-cluster include visual recon clusters (-ss and -sr only)
CONFIGURATIONS:
+ -config string path to the httpx configuration file (default $HOME/.config/httpx/config.yaml)
-r, -resolvers string[] list of custom resolver (file or comma separated)
-allow string[] allowed list of IP/CIDR's to process (file or comma separated)
-deny string[] denied list of IP/CIDR's to process (file or comma separated)
@@ -179,13 +205,17 @@ CONFIGURATIONS:
-fr, -follow-redirects follow http redirects
-maxr, -max-redirects int max number of redirects to follow per host (default 10)
-fhr, -follow-host-redirects follow redirects on the same host
+ -rhsts, -respect-hsts respect HSTS response headers for redirect requests
-vhost-input get a list of vhosts as input
-x string request methods to probe, use 'all' to probe all HTTP methods
-body string post body to include in http request
-s, -stream stream mode - start elaborating input targets without sorting
-sd, -skip-dedupe disable dedupe input items (only used with stream mode)
- -ldp, -leave-default-ports leave default http/https ports in host header (eg. http://host:80 - https//host:443
+ -ldp, -leave-default-ports leave default http/https ports in host header (eg. http://host:80 - https://host:443
-ztls use ztls library with autofallback to standard one for tls13
+ -no-decode avoid decoding body
+ -tlsi, -tls-impersonate enable experimental client hello (ja3) tls randomization
+ -no-stdin Disable Stdin processing
DEBUG:
-health-check, -hc run diagnostic check up
@@ -204,322 +234,39 @@ OPTIMIZATIONS:
-nf, -no-fallback display both probed protocol (HTTPS and HTTP)
-nfs, -no-fallback-scheme probe with protocol scheme specified in input
-maxhr, -max-host-error int max error count per host before skipping remaining path/s (default 30)
- -ec, -exclude-cdn skip full port scans for CDNs (only checks for 80,443)
+ -e, -exclude string[] exclude host matching specified filter ('cdn', 'private-ips', cidr, ip, regex)
-retries int number of retries
- -timeout int timeout in seconds (default 5)
- -delay duration duration between each http request (eg: 200ms, 1s) (default -1ns)
+ -timeout int timeout in seconds (default 10)
+ -delay value duration between each http request (eg: 200ms, 1s) (default -1ns)
-rsts, -response-size-to-save int max response size to save in bytes (default 2147483647)
-rstr, -response-size-to-read int max response size to read in bytes (default 2147483647)
```
-# Running httpX
-
-### URL Probe
-
-This will run the tool against all the hosts and subdomains in `hosts.txt` and returns URLs running HTTP webserver.
-
-```console
-cat hosts.txt | httpx
-
- __ __ __ _ __
- / /_ / /_/ /_____ | |/ /
- / __ \/ __/ __/ __ \| /
- / / / / /_/ /_/ /_/ / |
-/_/ /_/\__/\__/ .___/_/|_| v1.1.1
- /_/
-
- projectdiscovery.io
-
-[WRN] Use with caution. You are responsible for your actions
-[WRN] Developers assume no liability and are not responsible for any misuse or damage.
-
-https://mta-sts.managed.hackerone.com
-https://mta-sts.hackerone.com
-https://mta-sts.forwarding.hackerone.com
-https://docs.hackerone.com
-https://www.hackerone.com
-https://resources.hackerone.com
-https://api.hackerone.com
-https://support.hackerone.com
-```
-
-### File Input
-
-This will run the tool with the `-probe` flag against all the hosts in **hosts.txt** and return URLs with probed status.
-
-```console
-httpx -list hosts.txt -silent -probe
-
-http://ns.hackerone.com [FAILED]
-https://docs.hackerone.com [SUCCESS]
-https://mta-sts.hackerone.com [SUCCESS]
-https://mta-sts.managed.hackerone.com [SUCCESS]
-http://email.hackerone.com [FAILED]
-https://mta-sts.forwarding.hackerone.com [SUCCESS]
-http://links.hackerone.com [FAILED]
-https://api.hackerone.com [SUCCESS]
-https://www.hackerone.com [SUCCESS]
-http://events.hackerone.com [FAILED]
-https://support.hackerone.com [SUCCESS]
-https://gslink.hackerone.com [SUCCESS]
-http://o1.email.hackerone.com [FAILED]
-http://info.hackerone.com [FAILED]
-https://resources.hackerone.com [SUCCESS]
-http://o2.email.hackerone.com [FAILED]
-http://o3.email.hackerone.com [FAILED]
-http://go.hackerone.com [FAILED]
-http://a.ns.hackerone.com [FAILED]
-http://b.ns.hackerone.com [FAILED]
-```
-
-### CIDR Input
-
-```console
-echo 173.0.84.0/24 | httpx -silent
-
-https://173.0.84.29
-https://173.0.84.43
-https://173.0.84.31
-https://173.0.84.44
-https://173.0.84.12
-https://173.0.84.4
-https://173.0.84.36
-https://173.0.84.45
-https://173.0.84.14
-https://173.0.84.25
-https://173.0.84.46
-https://173.0.84.24
-https://173.0.84.32
-https://173.0.84.9
-https://173.0.84.13
-https://173.0.84.6
-https://173.0.84.16
-https://173.0.84.34
-```
-### AS Number Input
-```console
-echo AS14421 | httpx -silent
-
-https://216.101.17.248
-https://216.101.17.249
-https://216.101.17.250
-https://216.101.17.251
-https://216.101.17.252
-```
-
-### Tool Chain
-
-
-```console
-subfinder -d hackerone.com -silent| httpx -title -tech-detect -status-code
-
- __ __ __ _ __
- / /_ / /_/ /_____ | |/ /
- / __ \/ __/ __/ __ \| /
- / / / / /_/ /_/ /_/ / |
-/_/ /_/\__/\__/ .___/_/|_|
- /_/ v1.1.1
-
- projectdiscovery.io
-
-Use with caution. You are responsible for your actions
-Developers assume no liability and are not responsible for any misuse or damage.
-https://mta-sts.managed.hackerone.com [404] [Page not found · GitHub Pages] [Varnish,GitHub Pages,Ruby on Rails]
-https://mta-sts.hackerone.com [404] [Page not found · GitHub Pages] [Varnish,GitHub Pages,Ruby on Rails]
-https://mta-sts.forwarding.hackerone.com [404] [Page not found · GitHub Pages] [GitHub Pages,Ruby on Rails,Varnish]
-https://docs.hackerone.com [200] [HackerOne Platform Documentation] [Ruby on Rails,jsDelivr,Gatsby,React,webpack,Varnish,GitHub Pages]
-https://support.hackerone.com [301,302,301,200] [HackerOne] [Cloudflare,Ruby on Rails,Ruby]
-https://resources.hackerone.com [301,301,404] [Sorry, no Folders found.]
-```
-
-### Favicon Hash
-
-
-```console
-subfinder -d hackerone.com -silent | httpx -favicon
-
- __ __ __ _ __
- / /_ / /_/ /_____ | |/ /
- / __ \/ __/ __/ __ \| /
- / / / / /_/ /_/ /_/ / |
-/_/ /_/\__/\__/ .___/_/|_|
- /_/ v1.1.5
-
- projectdiscovery.io
-
-Use with caution. You are responsible for your actions.
-Developers assume no liability and are not responsible for any misuse or damage.
-https://docs.hackerone.com/favicon.ico [595148549]
-https://hackerone.com/favicon.ico [595148549]
-https://mta-sts.managed.hackerone.com/favicon.ico [-1700323260]
-https://mta-sts.forwarding.hackerone.com/favicon.ico [-1700323260]
-https://support.hackerone.com/favicon.ico [-1279294674]
-https://gslink.hackerone.com/favicon.ico [1506877856]
-https://resources.hackerone.com/favicon.ico [-1840324437]
-https://api.hackerone.com/favicon.ico [566218143]
-https://mta-sts.hackerone.com/favicon.ico [-1700323260]
-https://www.hackerone.com/favicon.ico [778073381]
-```
-
-### [JARM Fingerprint](https://github.com/salesforce/jarm)
+# Running httpx
-
-```console
-subfinder -d hackerone.com -silent | httpx -jarm
- __ __ __ _ __
- / /_ / /_/ /_____ | |/ /
- / __ \/ __/ __/ __ \| /
- / / / / /_/ /_/ /_/ / |
-/_/ /_/\__/\__/ .___/_/|_|
- /_/ v1.2.1
-
- projectdiscovery.io
-
-Use with caution. You are responsible for your actions.
-Developers assume no liability and are not responsible for any misuse or damage.
-https://www.hackerone.com [29d3dd00029d29d00042d43d00041d5de67cc9954cc85372523050f20b5007]
-https://mta-sts.hackerone.com [29d29d00029d29d00042d43d00041d2aa5ce6a70de7ba95aef77a77b00a0af]
-https://mta-sts.managed.hackerone.com [29d29d00029d29d00042d43d00041d2aa5ce6a70de7ba95aef77a77b00a0af]
-https://docs.hackerone.com [29d29d00029d29d00042d43d00041d2aa5ce6a70de7ba95aef77a77b00a0af]
-https://support.hackerone.com [29d3dd00029d29d00029d3dd29d29d5a74e95248e58a6162e37847a24849f7]
-https://api.hackerone.com [29d3dd00029d29d00042d43d00041d5de67cc9954cc85372523050f20b5007]
-https://mta-sts.forwarding.hackerone.com [29d29d00029d29d00042d43d00041d2aa5ce6a70de7ba95aef77a77b00a0af]
-https://resources.hackerone.com [2ad2ad0002ad2ad0002ad2ad2ad2ad043bfbd87c13813505a1b60adf4f6ff5]
-```
-
-### ASN Fingerprint
-
-
-```console
-subfinder -d hackerone.com -silent | httpx -asn
- __ __ __ _ __
- / /_ / /_/ /_____ | |/ /
- / __ \/ __/ __/ __ \| /
- / / / / /_/ /_/ /_/ / |
-/_/ /_/\__/\__/ .___/_/|_|
- /_/ v1.2.1
-
- projectdiscovery.io
-
-Use with caution. You are responsible for your actions.
-Developers assume no liability and are not responsible for any misuse or damage.
-https://mta-sts.managed.hackerone.com [AS54113, FASTLY, US]
-https://gslink.hackerone.com [AS16509, AMAZON-02, US]
-https://www.hackerone.com [AS13335, CLOUDFLARENET, US]
-https://mta-sts.forwarding.hackerone.com [AS54113, FASTLY, US]
-https://resources.hackerone.com [AS16509, AMAZON-02, US]
-https://support.hackerone.com [AS13335, CLOUDFLARENET, US]
-https://mta-sts.hackerone.com [AS54113, FASTLY, US]
-https://docs.hackerone.com [AS54113, FASTLY, US]
-https://api.hackerone.com [AS13335, CLOUDFLARENET, US]
-```
-
-
-### File/Path Bruteforce
-
-
-```console
-httpx -l urls.txt -path /v1/api -sc
-
- __ __ __ _ __
- / /_ / /_/ /_____ | |/ /
- / __ \/ __/ __/ __ \| /
- / / / / /_/ /_/ /_/ / |
-/_/ /_/\__/\__/ .___/_/|_|
- /_/ v1.1.5
-
- projectdiscovery.io
-
-Use with caution. You are responsible for your actions.
-Developers assume no liability and are not responsible for any misuse or damage.
-https://mta-sts.managed.hackerone.com/v1/api [404]
-https://mta-sts.hackerone.com/v1/api [404]
-https://mta-sts.forwarding.hackerone.com/v1/api [404]
-https://docs.hackerone.com/v1/api [404]
-https://api.hackerone.com/v1/api [401]
-https://hackerone.com/v1/api [302]
-https://support.hackerone.com/v1/api [404]
-https://resources.hackerone.com/v1/api [301]
-https://gslink.hackerone.com/v1/api [404]
-http://www.hackerone.com/v1/api [301]
-```
-
-### Docker Run
-
-```console
-cat sub_domains.txt | docker run -i projectdiscovery/httpx
-
- __ __ __ _ __
- / /_ / /_/ /_____ | |/ /
- / __ \/ __/ __/ __ \| /
- / / / / /_/ /_/ /_/ / |
-/_/ /_/\__/\__/ .___/_/|_|
- /_/ v1.1.2
-
- projectdiscovery.io
-
-Use with caution. You are responsible for your actions
-Developers assume no liability and are not responsible for any misuse or damage.
-https://mta-sts.forwarding.hackerone.com
-https://mta-sts.hackerone.com
-https://mta-sts.managed.hackerone.com
-https://www.hackerone.com
-https://api.hackerone.com
-https://gslink.hackerone.com
-https://resources.hackerone.com
-https://docs.hackerone.com
-https://support.hackerone.com
-```
+For details about running httpx, see https://docs.projectdiscovery.io/tools/httpx/running.
### Using `httpx` as a library
-`httpx` can be used as a library by creating an instance of the `Option` struct and populating it with the same options that would be specified via CLI. Once validated, the struct should be passed to a runner instance (to be closed at the end of the program) and the `RunEnumeration` method should be called. Here follows a minimal example of how to do it:
-
-```go
-package main
-
-import (
- "log"
-
- "github.com/projectdiscovery/goflags"
- "github.com/projectdiscovery/gologger"
- "github.com/projectdiscovery/gologger/levels"
- "github.com/projectdiscovery/httpx/runner"
-)
-
-func main() {
- gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose) // increase the verbosity (optional)
-
- options := runner.Options{
- Methods: "GET",
- InputTargetHost: goflags.StringSlice{"scanme.sh", "projectdiscovery.io"},
- //InputFile: "./targetDomains.txt", // path to file containing the target domains list
- }
-
- if err := options.ValidateOptions(); err != nil {
- log.Fatal(err)
- }
-
- httpxRunner, err := runner.New(&options)
- if err != nil {
- log.Fatal(err)
- }
- defer httpxRunner.Close()
-
- httpxRunner.RunEnumeration()
-}
-```
-
+`httpx` can be used as a library by creating an instance of the `Option` struct and populating it with the same options that would be specified via CLI. Once validated, the struct should be passed to a runner instance (to be closed at the end of the program) and the `RunEnumeration` method should be called. A minimal example of how to do it is in the [examples](examples/) folder
# Notes
-- As default, `httpx` checks for **HTTPS** probe and fall-back to **HTTP** only if **HTTPS** is not reachable.
-- The `-no-fallback` flag can be used to display both **HTTP** and **HTTPS** results
+- As default, `httpx` probe with **HTTPS** scheme and fall-back to **HTTP** only if **HTTPS** is not reachable.
+- The `-no-fallback` flag can be used to probe and display both **HTTP** and **HTTPS** result.
- Custom scheme for ports can be defined, for example `-ports http:443,http:80,https:8443`
-- The following flags should be used for specific use cases instead of running them as default with other probes:
- * `-favicon`,`-vhost`, `-http2`, `-pipeline`, `-ports`, `-csp-probe`, `-tls-probe`, `-path`
-- When using the `-json` flag, all the default probe results are included in the JSON output.
- Custom resolver supports multiple protocol (**doh|tcp|udp**) in form of `protocol:resolver:port` (e.g. `udp:127.0.0.1:53`)
-- Invalid custom resolvers/files are ignored.
+- The following flags should be used for specific use cases instead of running them as default with other probes:
+ - `-ports`
+ - `-path`
+ - `-vhost`
+ - `-screenshot`
+ - `-csp-probe`
+ - `-tls-probe`
+ - `-favicon`
+ - `-http2`
+ - `-pipeline`
+ - `-tls-impersonate`
+
# Acknowledgement
diff --git a/cmd/functional-test/testcases.txt b/cmd/functional-test/testcases.txt
index 7a525c9..34a53b4 100644
--- a/cmd/functional-test/testcases.txt
+++ b/cmd/functional-test/testcases.txt
@@ -13,8 +13,11 @@ scanme.sh {{binary}} -silent -tls-grab
scanme.sh {{binary}} -silent -unsafe
scanme.sh {{binary}} -silent -x all
scanme.sh {{binary}} -silent -body 'a=b'
-scanme.sh {{binary}} -silent -exclude-cdn
+scanme.sh {{binary}} -silent -exclude cdn
scanme.sh {{binary}} -silent -ports https:443
scanme.sh {{binary}} -silent -ztls
+scanme.sh {{binary}} -silent -jarm
https://scanme.sh?a=1*1 {{binary}} -silent
-https://scanme.sh:443 {{binary}} -asn
\ No newline at end of file
+https://scanme.sh:443 {{binary}} -asn
+scanme.sh {{binary}} -silent -tls-impersonate
+example.com {{binary}} -silent -bp -strip
\ No newline at end of file
diff --git a/cmd/integration-test/http.go b/cmd/integration-test/http.go
index 654d50c..caa1448 100644
--- a/cmd/integration-test/http.go
+++ b/cmd/integration-test/http.go
@@ -5,10 +5,12 @@ import (
"io"
"net/http"
"net/http/httptest"
+ "os"
"strings"
"github.com/julienschmidt/httprouter"
"github.com/projectdiscovery/httpx/internal/testutils"
+ fileutil "github.com/projectdiscovery/utils/file"
)
var httpTestcases = map[string]testutils.TestCase{
@@ -17,19 +19,20 @@ var httpTestcases = map[string]testutils.TestCase{
"Raw HTTP GET Request": &standardHttpGet{unsafe: true},
"Raw request with non standard rfc path via stdin": &standardHttpGet{unsafe: true, stdinPath: "/%invalid"},
"Raw request with non standard rfc path via cli flag": &standardHttpGet{unsafe: true, path: "/%invalid"},
- "Regression test for: https://github.com/projectdiscovery/httpx/issues/363": &issue363{}, // infinite redirect
- "Regression test for: https://github.com/projectdiscovery/httpx/issues/276": &issue276{}, // full path with port in output
- "Regression test for: https://github.com/projectdiscovery/httpx/issues/277": &issue277{}, // scheme://host:port via stdin
- "Regression test for: https://github.com/projectdiscovery/httpx/issues/303": &issue303{}, // misconfigured gzip header with uncompressed body
- "Regression test for: https://github.com/projectdiscovery/httpx/issues/400": &issue400{}, // post operation with body
- "Regression test for: https://github.com/projectdiscovery/httpx/issues/414": &issue414{}, // stream mode with path
- "Regression test for: https://github.com/projectdiscovery/httpx/issues/433": &issue433{}, // new line scanning with title flag
- "Request URI to existing file - https://github.com/projectdiscovery/httpx/issues/480": &issue480{}, // request uri pointing to existing file
+ "Regression test for: https://github.com/projectdiscovery/httpx/issues/363": &issue363{}, // infinite redirect
+ "Regression test for: https://github.com/projectdiscovery/httpx/issues/276": &issue276{}, // full path with port in output
+ "Regression test for: https://github.com/projectdiscovery/httpx/issues/277": &issue277{}, // scheme://host:port via stdin
+ "Regression test for: https://github.com/projectdiscovery/httpx/issues/303": &issue303{}, // misconfigured gzip header with uncompressed body
+ "Regression test for: https://github.com/projectdiscovery/httpx/issues/400": &issue400{}, // post operation with body
+ "Regression test for: https://github.com/projectdiscovery/httpx/issues/414": &issue414{}, // stream mode with path
+ "Regression test for unwanted chars": &titleUnwantedChars{}, // new line scanning with title flag, Regression test for: https://github.com/projectdiscovery/httpx/issues/433
+ "Request URI to existing file - https://github.com/projectdiscovery/httpx/issues/480": &issue480{}, // request uri pointing to existing file
"Standard HTTP GET Request with match response time": &standardHttpGet{mrt: true, inputValue: "\"<10s\""},
"Standard HTTP GET Request with filter response time": &standardHttpGet{frt: true, inputValue: "\">3ms\""},
"Multiple Custom Header": &customHeader{inputData: []string{"-debug-req", "-H", "'user-agent: test'", "-H", "'foo: bar'"}, expectedOutput: []string{"User-Agent: test", "Foo: bar"}},
"Output Match Condition": &outputMatchCondition{inputData: []string{"-silent", "-mdc", "\"status_code == 200\""}},
"Output Filter Condition": &outputFilterCondition{inputData: []string{"-silent", "-fdc", "\"status_code == 400\""}},
+ "Output All": &outputAll{},
}
type standardHttpGet struct {
@@ -254,14 +257,14 @@ func (h *issue414) Execute() error {
return nil
}
-type issue433 struct{}
+type titleUnwantedChars struct{}
-func (h *issue433) Execute() error {
+func (h *titleUnwantedChars) Execute() error {
var ts *httptest.Server
router := httprouter.New()
uriPath := "/index"
router.GET(uriPath, httprouter.Handle(func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
- htmlResponse := "Project\n\r Discovery\n - Httpx>test data"
+ htmlResponse := "\v\fProject\n\r Discovery\n - Httpx\t>test data"
fmt.Fprint(w, htmlResponse)
}))
ts = httptest.NewServer(router)
@@ -377,3 +380,42 @@ func (h *outputFilterCondition) Execute() error {
}
return nil
}
+
+type outputAll struct {
+}
+
+func (h *outputAll) Execute() error {
+ var ts *httptest.Server
+ router := httprouter.New()
+ router.GET("/", httprouter.Handle(func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(200)
+ fmt.Fprint(w, `{"status": "ok"}`)
+ }))
+ ts = httptest.NewServer(router)
+ defer ts.Close()
+
+ fileName := "test_output_all"
+ _, hErr := testutils.RunHttpxAndGetResults(ts.URL, false, []string{"-o", fileName, "-oa"}...)
+ if hErr != nil {
+ return hErr
+ }
+
+ expectedFiles := []string{fileName, fileName + ".json", fileName + ".csv"}
+ var actualFiles []string
+
+ for _, file := range expectedFiles {
+ if fileutil.FileExists(file) {
+ actualFiles = append(actualFiles, file)
+ }
+ }
+ if len(actualFiles) != 3 {
+ return errIncorrectResultsCount(actualFiles)
+ }
+
+ for _, file := range actualFiles {
+ _ = os.Remove(file)
+ }
+
+ return nil
+}
diff --git a/common/errorpageclassifier/clf.gob b/common/errorpageclassifier/clf.gob
new file mode 100644
index 0000000..f697862
Binary files /dev/null and b/common/errorpageclassifier/clf.gob differ
diff --git a/common/errorpageclassifier/dataset.txt b/common/errorpageclassifier/dataset.txt
new file mode 100644
index 0000000..1f9ee4e
--- /dev/null
+++ b/common/errorpageclassifier/dataset.txt
@@ -0,0 +1,201 @@
+The Forum page seems to have a glitch. Our technicians are on it.||error
+There was a problem with the Product Details page. Try reloading.||error
+Error 500: The E-books page is experiencing a problem.||error
+Unfortunately, the Video Tutorials page is down for maintenance.||error
+Our Archive page is currently unavailable. We apologize for the inconvenience.||error
+We're having trouble loading the Membership Details page.||error
+An error occurred while trying to access the Profile Settings page.||error
+Error 404: The Team page could not be found.||error
+Our Project Highlights page seems to be having some technical issues.||error
+We're sorry, but we can't seem to find the Donations page.||error
+You've landed on our Forum page. Engage in interesting discussions.||nonerror
+Welcome to the Product Details page. Learn more about our products here.||nonerror
+You are now on our E-books page. Enjoy a wealth of knowledge.||nonerror
+This is the Video Tutorials page. Learn with our easy-to-follow videos.||nonerror
+Welcome to our Archive. Dive into our rich history.||nonerror
+You're now on the Membership Details page. See the benefits of joining us.||nonerror
+This is your Profile Settings page. Update your personal details as needed.||nonerror
+You're on the Team page. Meet the people behind our organization.||nonerror
+Welcome to our Project Highlights page. See what we've been up to.||nonerror
+You've landed on the Donations page. Every contribution helps us do more.||nonerror
+500 - Server Error This is highly unusual! Our tech team have been notified and are working on it.||error
+Sorry this page is currently under maintenance.||error
+Access Denied - You don't have permission to access this page.||error
+This page seems to be missing 404 Error!||error
+Sorry something went wrong. Please try again later.||error
+We're sorry this page could not be found 404.||error
+The page you requested could not be found on our site.||error
+500 - Internal server error. There is a problem with the resource you are looking for and it cannot be displayed.||error
+Error 401 Unauthorized: Access is denied due to invalid credentials.||error
+Bad request 400. Your browser sent a request that this server could not understand.||error
+This is a 404 error page||error
+Sorry this page does not exist||error
+Error 500: Internal Server Error||error
+Oops! That page can’t be found.Try searching from the field above or go to the home page.||error
+An error has occurred while processing your request. It happens to the best of us! Don't worry! There are no bugs without a fix! Let's try again! What were you looking for? If you are an adventurer search this site! If difficulties persist please contact the website administrator and report the error below. 404 Page not found||error
+Whoops our bad... The page you requested was not found and we have a fine guess why. If you typed the URL directly please make sure the spelling is correct. If you clicked on a link to get here the link is outdated. What can you do? Have no fear help is near! There are many ways you can get back on track with Magento Store. Go back to the previous page. Use the search bar at the top of the page to search for your products. Follow these links to get you back on track! Store Home My Account||error
+404 - Page not found Unfortunately the requested page could not be found.||error
+PAGE NOT FOUND The page you're looking for doesn't seem to exist anymore… Return to the homepage||error
+Who moved my... lemon? Oh no - looks like we can't find the page you are looking for. But you know the saying; when life gives you lemons... okay we can't find a clever way to end that sentence but we do have 2 suggestions to help you find what you were looking for: Go to the front page Or Search for a specific topic If something you need really is missing we would love it if you would let us know ❤️️||error
+404—page not found||error
+Apologies but there's a 503 Service Unavailable error. The server cannot handle the request.||error
+Sorry you don't have access rights to this page. Error 403: Forbidden.||error
+404 - Oops! The page you are looking for has been misplaced.||error
+Sorry the server encountered an unexpected condition that prevented it from fulfilling the request. Error 500: Internal Server Error.||error
+Whoa! The page you're looking for seems to have vanished. Error 404.||error
+Sorry this page has moved or doesn't exist anymore. Error 404.||error
+Sorry but your request timed out. Please try again. Error 504: Gateway Timeout.||error
+We're sorry but an unknown error occurred while processing your request.||error
+Error 502: Bad Gateway. The server encountered a temporary error and could not complete your request.||error
+The requested resource could not be found on this server. Please verify your request and try again. Error 404.||error
+This Help Center page is temporarily unavailable.||error
+Privacy Policy page not found. Please try again later.||error
+There seems to be an error on our Services page. We're working to fix it.||error
+An error occurred while loading the Search Results page.||error
+Category page not found. It might have been removed or relocated.||error
+There was a problem loading the Cart page. Please try again.||error
+Our Terms of Service page is currently down for maintenance.||error
+We're sorry, but the Sitemap is not available at the moment.||error
+We're having trouble loading the Reviews page.||error
+An error occurred while trying to access the Partners page.||error
+Settings page is currently unavailable. We apologize for the inconvenience.||error
+Error 404: Resources page not found.||error
+Our Press Releases page seems to be having some technical issues.||error
+We're sorry, but we can't seem to find the Case Studies page.||error
+There was a problem loading the Community page. Please refresh the page.||error
+Error 503: The Subscriptions page is temporarily unavailable.||error
+There's a problem with our Customer Support page. We're on it.||error
+We're having trouble finding the Notifications page. It may have been moved.||error
+There was a problem with the Feedback page. Try again later.||error
+Our Transactions page is currently experiencing some issues. We appreciate your patience.||error
+Your request has been successfully submitted.||nonerror
+You have successfully logged out.||nonerror
+Congratulations on successfully completing the course!||nonerror
+The payment has been processed successfully.||nonerror
+Thank you for your feedback!||nonerror
+Your download will start shortly.||nonerror
+Profile updated successfully.||nonerror
+Thanks for contacting us! We'll get back to you as soon as possible.||nonerror
+Sign-up successful. Welcome to our community!||nonerror
+Your booking has been confirmed. Check your email for details.||nonerror
+Welcome! Your registration was successful.||nonerror
+Congratulations! You've successfully updated your profile.||nonerror
+Great! Your order was placed successfully. We'll send you an email confirmation soon.||nonerror
+Welcome back! Your login was successful.||nonerror
+Success! You've added the item to your cart.||nonerror
+Your request was sent successfully. We'll get back to you as soon as possible.||nonerror
+Great job! Your settings have been saved.||nonerror
+Your message has been submitted successfully. We appreciate your feedback.||nonerror
+Thank you for subscribing to our newsletter!||nonerror
+Great news! Your transaction was successful.||nonerror
+Welcome to our homepage. Feel free to browse around||nonerror
+Thanks for signing up! You're now a registered user.||nonerror
+Your order has been placed successfully! You'll receive a confirmation email shortly||nonerror
+Congratulations your account has been successfully created||nonerror
+Thank you for your inquiry. We will respond to your message within 24 hours||nonerror
+You've successfully added the item to your cart!||nonerror
+Success! Your password has been updated||nonerror
+Welcome back! You have successfully logged in||nonerror
+Great job! Your profile has been updated||nonerror
+Your message was sent successfully. We'll get back to you shortly||nonerror
+Welcome to our website. Explore and enjoy our services.||nonerror
+Thank you for visiting our About Us page. Learn more about our journey and team.||nonerror
+You are now browsing our Products page. Check out our latest offerings.||nonerror
+This is our Contact Us page. Feel free to reach out with any queries or feedback.||nonerror
+You have reached the end of the page. Scroll up to continue browsing.||nonerror
+Welcome to the News section. Stay updated with our latest announcements.||nonerror
+Now viewing: Image Gallery. Enjoy a visual tour of our activities.||nonerror
+You're on our FAQ page. Get answers to common questions.||nonerror
+Welcome to the Blog section. Engage with our thoughts and insights.||nonerror
+This is the Discussion Forum. Join in, ask questions, or help others.||nonerror
+You're on the Login page. Enter your credentials to access your account.||nonerror
+Welcome to the Sign-Up page. Join our community today.||nonerror
+This is your User Dashboard. Manage your account and settings here.||nonerror
+You've reached the Checkout page. Review your order and proceed to payment.||nonerror
+Welcome to the Download section . Access our digital resources here.||nonerror
+This is the Careers page. Explore job opportunities with us.||nonerror
+You're viewing the Events Calendar. Keep track of upcoming activities.||nonerror
+This is the User Profile page. Update your information as needed.||nonerror
+Welcome to our Testimonials page. Read reviews and stories from our users.||nonerror
+You are now on the Home page. Start exploring from here.||nonerror
+Welcome to home page||nonerror
+You're now on our Help Center page. Find answers to common questions here.||nonerror
+Welcome to our Privacy Policy page. Learn how we protect your personal information.||nonerror
+You've landed on the Services page. Explore what we have to offer.||nonerror
+This is the Search Results page. Did you find what you were looking for?||nonerror
+Now browsing the Category page. View all items in this category.||nonerror
+You're now on the Cart page. Review your selections before proceeding to checkout.||nonerror
+Welcome to our Terms of Service page. Understand our conditions for providing services.||nonerror
+You are currently on our Sitemap. Navigate our website with ease.||nonerror
+You are on the Reviews page. Check out what others have to say about us.||nonerror
+Now viewing the Partners page. Meet the organizations we collaborate with.||nonerror
+You're on the Settings page. Customize your user experience.||nonerror
+This is our Resources page. Access useful documents and guides.||nonerror
+You've landed on the Press Releases page. Stay updated with our latest news.||nonerror
+Welcome to our Case Studies page. Discover our past projects and achievements.||nonerror
+You're now on the Community page. Connect and interact with other members.||nonerror
+You are currently on the Subscriptions page. Manage your preferences here.||nonerror
+Now viewing the Customer Support page. We're here to help.||nonerror
+This is the Notifications page. Keep track of your updates and alerts.||nonerror
+You've landed on the Feedback page. Share your thoughts with us.||nonerror
+Welcome to the Transactions page. Monitor your past and current transactions.||nonerror
+500 - Server Error This is highly unusual! Our tech team have been notified and are working on it.||error
+Sorry this page is currently under maintenance.||error
+Access Denied - You don't have permission to access this page.||error
+This page seems to be missing 404 Error!||error
+Sorry something went wrong. Please try again later.||error
+We're sorry this page could not be found 404.||error
+The page you requested could not be found on our site.||error
+500 - Internal server error. There is a problem with the resource you are looking for and it cannot be displayed.||error
+Error 401 Unauthorized: Access is denied due to invalid credentials.||error
+Bad request 400. Your browser sent a request that this server could not understand.||error
+Your request has been successfully submitted.||nonerror
+You have successfully logged out.||nonerror
+Congratulations on successfully completing the course!||nonerror
+The payment has been processed successfully.||nonerror
+Thank you for your feedback!||nonerror
+Your download will start shortly.||nonerror
+Profile updated successfully.||nonerror
+Thanks for contacting us! We'll get back to you as soon as possible.||nonerror
+Sign-up successful. Welcome to our community!||nonerror
+Your booking has been confirmed. Check your email for details.||nonerror
+This is a 404 error page||error
+Sorry this page does not exist||error
+Error 500: Internal Server Error||error
+Oops! That page can’t be found.Try searching from the field above or go to the home page.||error
+An error has occurred while processing your request. It happens to the best of us! Don't worry! There are no bugs without a fix! Let's try again! What were you looking for? If you are an adventurer search this site! If difficulties persist please contact the website administrator and report the error below. 404 Page not found||error
+Whoops our bad... The page you requested was not found and we have a fine guess why. If you typed the URL directly please make sure the spelling is correct. If you clicked on a link to get here the link is outdated. What can you do? Have no fear help is near! There are many ways you can get back on track with Magento Store. Go back to the previous page. Use the search bar at the top of the page to search for your products. Follow these links to get you back on track! Store Home | My Account||error
+404 - Page not found Unfortunately the requested page could not be found.||error
+PAGE NOT FOUND The page you're looking for doesn't seem to exist anymore… Return to the homepage||error
+Who moved my... lemon? Oh no - looks like we can't find the page you are looking for. But you know the saying; when life gives you lemons... okay we can't find a clever way to end that sentence but we do have 2 suggestions to help you find what you were looking for: Go to the front page Or Search for a specific topic If something you need really is missing we would love it if you would let us know ❤️️||error
+404—page not found||error
+Apologies but there's a 503 Service Unavailable error. The server cannot handle the request.||error
+Sorry you don't have access rights to this page. Error 403: Forbidden.||error
+404 - Oops! The page you are looking for has been misplaced.||error
+Sorry the server encountered an unexpected condition that prevented it from fulfilling the request. Error 500: Internal Server Error.||error
+Whoa! The page you're looking for seems to have vanished. Error 404.||error
+Sorry this page has moved or doesn't exist anymore. Error 404.||error
+Sorry but your request timed out. Please try again. Error 504: Gateway Timeout.||error
+We're sorry but an unknown error occurred while processing your request.||error
+Error 502: Bad Gateway. The server encountered a temporary error and could not complete your request.||error
+The requested resource could not be found on this server. Please verify your request and try again. Error 404.||error
+Welcome! Your registration was successful.||nonerror
+Congratulations! You've successfully updated your profile.||nonerror
+Great! Your order was placed successfully. We'll send you an email confirmation soon.||nonerror
+Welcome back! Your login was successful.||nonerror
+Success! You've added the item to your cart.||nonerror
+Your request was sent successfully. We'll get back to you as soon as possible.||nonerror
+Great job! Your settings have been saved.||nonerror
+Your message has been submitted successfully. We appreciate your feedback.||nonerror
+Thank you for subscribing to our newsletter!||nonerror
+Great news! Your transaction was successful.||nonerror
+Welcome to our homepage. Feel free to browse around||nonerror
+Thanks for signing up! You're now a registered user.||nonerror
+Your order has been placed successfully! You'll receive a confirmation email shortly||nonerror
+Congratulations your account has been successfully created||nonerror
+Thank you for your inquiry. We will respond to your message within 24 hours||nonerror
+You've successfully added the item to your cart!||nonerror
+Success! Your password has been updated||nonerror
+Welcome back! You have successfully logged in||nonerror
+Great job! Your profile has been updated||nonerror
+Your message was sent successfully. We'll get back to you shortly||nonerror
\ No newline at end of file
diff --git a/common/errorpageclassifier/errorpageclassifier.go b/common/errorpageclassifier/errorpageclassifier.go
new file mode 100644
index 0000000..d916d7c
--- /dev/null
+++ b/common/errorpageclassifier/errorpageclassifier.go
@@ -0,0 +1,39 @@
+package errorpageclassifier
+
+import (
+ _ "embed"
+
+ "github.com/jaytaylor/html2text"
+ "github.com/projectdiscovery/utils/ml/naive_bayes"
+)
+
+//go:embed clf.gob
+var classifierData []byte
+
+type ErrorPageClassifier struct {
+ classifier *naive_bayes.NaiveBayesClassifier
+}
+
+func New() *ErrorPageClassifier {
+ classifier, err := naive_bayes.NewClassifierFromFileData(classifierData)
+ if err != nil {
+ panic(err)
+ }
+ return &ErrorPageClassifier{classifier: classifier}
+}
+
+func (n *ErrorPageClassifier) Classify(html string) string {
+ text := htmlToText(html)
+ if text == "" {
+ return "other"
+ }
+ return n.classifier.Classify(text)
+}
+
+func htmlToText(html string) string {
+ text, err := html2text.FromString(html, html2text.Options{TextOnly: true})
+ if err != nil {
+ panic(err)
+ }
+ return text
+}
diff --git a/common/errorpageclassifier/errorpageclassifier_test.go b/common/errorpageclassifier/errorpageclassifier_test.go
new file mode 100644
index 0000000..35923b2
--- /dev/null
+++ b/common/errorpageclassifier/errorpageclassifier_test.go
@@ -0,0 +1,53 @@
+package errorpageclassifier
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestErrorPageClassifier(t *testing.T) {
+ t.Run("test creation of new ErrorPageClassifier", func(t *testing.T) {
+ epc := New()
+ assert.NotNil(t, epc)
+ })
+
+ t.Run("test classification non error page text", func(t *testing.T) {
+ epc := New()
+ assert.Equal(t, "nonerror", epc.Classify(`
+
+
+
+ Terms of Service
+
+
+ Welcome to our Terms of Service page.
+ Understand our conditions for providing services.
+
+
+ `))
+ })
+
+ t.Run("test classification on error page text", func(t *testing.T) {
+ epc := New()
+ assert.Equal(t, "error", epc.Classify(`
+
+
+ Error 403: Forbidden
+
+
+
+
+
Error 403: Forbidden
+
Sorry you don't have access rights to this page.
+
+
+
+ `))
+ })
+}
diff --git a/common/fileutil/fileutil.go b/common/fileutil/fileutil.go
index 8efe89f..994587a 100644
--- a/common/fileutil/fileutil.go
+++ b/common/fileutil/fileutil.go
@@ -80,3 +80,10 @@ func LoadCidrsFromSliceOrFileWithMaxRecursion(option string, splitchar string, m
return
}
+
+func AbsPathOrDefault(p string) string {
+ if absPath, err := filepath.Abs(p); err == nil {
+ return absPath
+ }
+ return p
+}
diff --git a/common/httputilz/normalize.go b/common/httputilz/normalize.go
new file mode 100644
index 0000000..e266767
--- /dev/null
+++ b/common/httputilz/normalize.go
@@ -0,0 +1,11 @@
+package httputilz
+
+import "regexp"
+
+var (
+ normalizeSpacesRegex = regexp.MustCompile(`\s+`)
+)
+
+func NormalizeSpaces(data string) string {
+ return normalizeSpacesRegex.ReplaceAllString(data, " ")
+}
diff --git a/common/httpx/cdn.go b/common/httpx/cdn.go
index 073a3e6..6d0bb5f 100644
--- a/common/httpx/cdn.go
+++ b/common/httpx/cdn.go
@@ -5,11 +5,17 @@ import (
"net"
)
-// CdnCheck verifies if the given ip is part of Cdn ranges
+// CdnCheck verifies if the given ip is part of Cdn/WAF ranges
func (h *HTTPX) CdnCheck(ip string) (bool, string, error) {
if h.cdn == nil {
return false, "", fmt.Errorf("cdn client not configured")
}
- return h.cdn.Check(net.ParseIP((ip)))
+ // the goal is to check if ip is part of cdn/waf to decide if target should be scanned or not
+ // since 'cloud' itemtype does not fit logic here , we consider target is not part of cdn/waf
+ matched, value, itemType, err := h.cdn.Check(net.ParseIP((ip)))
+ if itemType == "cloud" {
+ return false, "", err
+ }
+ return matched, value, err
}
diff --git a/common/httpx/httpx.go b/common/httpx/httpx.go
index 56224d9..5d723fb 100644
--- a/common/httpx/httpx.go
+++ b/common/httpx/httpx.go
@@ -4,6 +4,7 @@ import (
"crypto/tls"
"fmt"
"io"
+ "net"
"net/http"
"net/url"
"strconv"
@@ -14,9 +15,11 @@ import (
"github.com/microcosm-cc/bluemonday"
"github.com/projectdiscovery/cdncheck"
"github.com/projectdiscovery/fastdialer/fastdialer"
- "github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/fastdialer/fastdialer/ja3/impersonate"
+ "github.com/projectdiscovery/httpx/common/httputilz"
"github.com/projectdiscovery/rawhttp"
retryablehttp "github.com/projectdiscovery/retryablehttp-go"
+ "github.com/projectdiscovery/utils/generic"
pdhttputil "github.com/projectdiscovery/utils/http"
stringsutil "github.com/projectdiscovery/utils/strings"
urlutil "github.com/projectdiscovery/utils/url"
@@ -40,7 +43,13 @@ type HTTPX struct {
func New(options *Options) (*HTTPX, error) {
httpx := &HTTPX{}
fastdialerOpts := fastdialer.DefaultOptions
- fastdialerOpts.EnableFallback = true
+
+ // if the user specified any custom resolver disables system resolvers and syscall lookup fallback
+ if len(options.Resolvers) > 0 {
+ fastdialerOpts.ResolversFile = false
+ fastdialerOpts.EnableFallback = false
+ }
+
fastdialerOpts.Deny = options.Deny
fastdialerOpts.Allow = options.Allow
fastdialerOpts.WithDialerHistory = true
@@ -63,6 +72,14 @@ func New(options *Options) (*HTTPX, error) {
retryablehttpOptions.Timeout = httpx.Options.Timeout
retryablehttpOptions.RetryMax = httpx.Options.RetryMax
+ handleHSTS := func(req *http.Request) {
+ if req.Response.Header.Get("Strict-Transport-Security") == "" {
+ return
+ }
+
+ req.URL.Scheme = "https"
+ }
+
var redirectFunc = func(_ *http.Request, _ []*http.Request) error {
// Tell the http client to not follow redirect
return http.ErrUseLastResponse
@@ -73,10 +90,16 @@ func New(options *Options) (*HTTPX, error) {
redirectFunc = func(redirectedRequest *http.Request, previousRequests []*http.Request) error {
// add custom cookies if necessary
httpx.setCustomCookies(redirectedRequest)
+
if len(previousRequests) >= options.MaxRedirects {
// https://github.com/golang/go/issues/10069
return http.ErrUseLastResponse
}
+
+ if options.RespectHSTS {
+ handleHSTS(redirectedRequest)
+ }
+
return nil
}
}
@@ -88,8 +111,8 @@ func New(options *Options) (*HTTPX, error) {
httpx.setCustomCookies(redirectedRequest)
// Check if we get a redirect to a different host
- var newHost = redirectedRequest.URL.Host
- var oldHost = previousRequests[0].Host
+ var newHost = redirectedRequest.URL.Hostname()
+ var oldHost = previousRequests[0].URL.Hostname()
if oldHost == "" {
oldHost = previousRequests[0].URL.Host
}
@@ -101,12 +124,22 @@ func New(options *Options) (*HTTPX, error) {
// https://github.com/golang/go/issues/10069
return http.ErrUseLastResponse
}
+
+ if options.RespectHSTS {
+ handleHSTS(redirectedRequest)
+ }
+
return nil
}
}
transport := &http.Transport{
- DialContext: httpx.Dialer.Dial,
- DialTLSContext: httpx.Dialer.DialTLS,
+ DialContext: httpx.Dialer.Dial,
+ DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
+ if options.TlsImpersonate {
+ return httpx.Dialer.DialTLSWithConfigImpersonate(ctx, network, addr, &tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS10}, impersonate.Random, nil)
+ }
+ return httpx.Dialer.DialTLS(ctx, network, addr)
+ },
MaxIdleConnsPerHost: -1,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
@@ -151,10 +184,7 @@ func New(options *Options) (*HTTPX, error) {
httpx.htmlPolicy = bluemonday.NewPolicy()
httpx.CustomHeaders = httpx.Options.CustomHeaders
if options.CdnCheck || options.ExcludeCdn {
- httpx.cdn, err = cdncheck.NewWithCache()
- if err != nil {
- gologger.Error().Msgf("could not create cdn check: %v", err)
- }
+ httpx.cdn = cdncheck.New()
}
return httpx, nil
@@ -204,8 +234,10 @@ get_response:
resp.Raw = string(rawResp)
resp.RawHeaders = string(headers)
var respbody []byte
- // websockets don't have a readable body
- if httpresp.StatusCode != http.StatusSwitchingProtocols {
+ // body shouldn't be read with the following status codes
+ // 101 - Switching Protocols => websockets don't have a readable body
+ // 304 - Not Modified => no body the response terminates with latest header newline
+ if !generic.EqualsAny(httpresp.StatusCode, http.StatusSwitchingProtocols, http.StatusNotModified) {
var err error
respbody, err = io.ReadAll(io.LimitReader(httpresp.Body, h.Options.MaxResponseBodySizeToRead))
if err != nil && !shouldIgnoreBodyErrors {
@@ -218,6 +250,10 @@ get_response:
return nil, closeErr
}
+ // Todo: replace with https://github.com/projectdiscovery/utils/issues/110
+ resp.RawData = make([]byte, len(respbody))
+ copy(resp.RawData, respbody)
+
respbody, err = DecodeData(respbody, httpresp.Header)
if err != nil && !shouldIgnoreBodyErrors {
return nil, closeErr
@@ -254,8 +290,12 @@ get_response:
resp.Lines = len(strings.Split(respbodystr, "\n"))
if !h.Options.Unsafe && h.Options.TLSGrab {
- // extracts TLS data if any
- resp.TLSData = h.TLSGrab(httpresp)
+ if h.Options.ZTLS {
+ resp.TLSData = h.ZTLSGrab(httpresp)
+ } else {
+ // extracts TLS data if any
+ resp.TLSData = h.TLSGrab(httpresp)
+ }
}
resp.CSPData = h.CSPGrab(&resp)
@@ -375,3 +415,14 @@ func (httpx *HTTPX) setCustomCookies(req *http.Request) {
}
}
}
+
+func (httpx *HTTPX) Sanitize(respStr string, trimLine, normalizeSpaces bool) string {
+ respStr = httpx.htmlPolicy.Sanitize(respStr)
+ if trimLine {
+ respStr = strings.Replace(respStr, "\n", "", -1)
+ }
+ if normalizeSpaces {
+ respStr = httputilz.NormalizeSpaces(respStr)
+ }
+ return respStr
+}
diff --git a/common/httpx/option.go b/common/httpx/option.go
index bec97ed..f1de106 100644
--- a/common/httpx/option.go
+++ b/common/httpx/option.go
@@ -24,6 +24,7 @@ type Options struct {
VHostSimilarityRatio int
FollowRedirects bool
FollowHostRedirects bool
+ RespectHSTS bool
MaxRedirects int
Unsafe bool
TLSGrab bool
@@ -42,6 +43,7 @@ type Options struct {
Resolvers []string
customCookies []*http.Cookie
SniName string
+ TlsImpersonate bool
}
// DefaultOptions contains the default options
diff --git a/common/httpx/pipeline.go b/common/httpx/pipeline.go
index 84f508b..f4130ef 100644
--- a/common/httpx/pipeline.go
+++ b/common/httpx/pipeline.go
@@ -18,7 +18,7 @@ func (h *HTTPX) SupportPipeline(protocol, method, host string, port int) bool {
port = 443
}
}
- if port > 0 {
+ if _, _, err := net.SplitHostPort(host); err != nil && port > 0 {
addr = fmt.Sprintf("%s:%d", host, port)
}
// dummy method while awaiting for full rawhttp implementation
diff --git a/common/httpx/response.go b/common/httpx/response.go
index bf0e15f..1b1ab47 100644
--- a/common/httpx/response.go
+++ b/common/httpx/response.go
@@ -12,7 +12,8 @@ import (
type Response struct {
StatusCode int
Headers map[string][]string
- Data []byte
+ RawData []byte // undecoded data
+ Data []byte // decoded data
ContentLength int
Raw string
RawHeaders string
@@ -68,9 +69,13 @@ func (r *Response) GetChainStatusCodes() []int {
// GetChain dump the whole redirect chain as string
func (r *Response) GetChain() string {
var respchain strings.Builder
- for _, chainItem := range r.Chain {
- respchain.Write(chainItem.Request)
- respchain.Write(chainItem.Response)
+ for counter, chainItem := range r.Chain {
+ if counter != 0 {
+ respchain.Write(chainItem.Request)
+ }
+ if counter < len(r.Chain)-1 {
+ respchain.Write(chainItem.Response)
+ }
}
return respchain.String()
}
diff --git a/common/httpx/title.go b/common/httpx/title.go
index efcd477..1b59b1e 100644
--- a/common/httpx/title.go
+++ b/common/httpx/title.go
@@ -35,7 +35,7 @@ func ExtractTitle(r *Response) (title string) {
// remove unwanted chars
title = strings.TrimSpace(strings.Trim(title, cutset))
- title = stringsutil.ReplaceAll(title, "\n", "\r")
+ title = stringsutil.ReplaceAll(title, "", "\n", "\t", "\v", "\f", "\r")
return title
}
diff --git a/common/httpx/tls.go b/common/httpx/tls.go
index cd7fd15..14077b8 100644
--- a/common/httpx/tls.go
+++ b/common/httpx/tls.go
@@ -3,10 +3,13 @@ package httpx
import (
"crypto/tls"
"crypto/x509"
+ "fmt"
"net"
"net/http"
"github.com/projectdiscovery/tlsx/pkg/tlsx/clients"
+ "github.com/projectdiscovery/tlsx/pkg/tlsx/ztls"
+ zmaptls "github.com/zmap/zcrypto/tls"
)
// versionToTLSVersionString converts tls version to version string
@@ -48,6 +51,42 @@ func (h *HTTPX) TLSGrab(r *http.Response) *clients.Response {
return response
}
+func (h *HTTPX) ZTLSGrab(r *http.Response) *clients.Response {
+ host := r.Request.URL.Host
+ hostname, port, _ := net.SplitHostPort(host)
+ if hostname == "" {
+ hostname = host
+ }
+ if port == "" {
+ port = "443"
+ }
+ // canonical net concatenation
+ host = net.JoinHostPort(hostname, fmt.Sprint(port))
+ tlsConn, err := h.Dialer.DialTLS(r.Request.Context(), "tcp", host)
+ if err != nil {
+ return nil
+ }
+ ztlsConn, ok := (tlsConn).(*zmaptls.Conn)
+ if !ok {
+ return nil
+ }
+ ztlsState := ztlsConn.ConnectionState()
+ if len(ztlsState.PeerCertificates) == 0 {
+ return nil
+ }
+ response := &clients.Response{
+ Host: hostname,
+ ProbeStatus: true,
+ Port: port,
+ Version: versionToTLSVersionString[ztlsState.Version],
+ Cipher: tls.CipherSuiteName(ztlsState.CipherSuite),
+ TLSConnection: "ztls",
+ CertificateResponse: ztls.ConvertCertificateToResponse(&clients.Options{}, hostname, ztlsState.PeerCertificates[0]),
+ ServerName: ztlsState.ServerName,
+ }
+ return response
+}
+
func convertCertificateToResponse(hostname string, cert *x509.Certificate) *clients.CertificateResponse {
response := &clients.CertificateResponse{
SubjectAN: cert.DNSNames,
diff --git a/examples/example.go b/examples/example.go
new file mode 100644
index 0000000..ae2bb4e
--- /dev/null
+++ b/examples/example.go
@@ -0,0 +1,41 @@
+package main
+
+import (
+ "fmt"
+ "log"
+
+ "github.com/projectdiscovery/goflags"
+ "github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/gologger/levels"
+ "github.com/projectdiscovery/httpx/runner"
+)
+
+func main() {
+ gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose) // increase the verbosity (optional)
+
+ options := runner.Options{
+ Methods: "GET",
+ InputTargetHost: goflags.StringSlice{"scanme.sh", "projectdiscovery.io", "localhost"},
+ //InputFile: "./targetDomains.txt", // path to file containing the target domains list
+ OnResult: func(r runner.Result) {
+ // handle error
+ if r.Err != nil {
+ fmt.Printf("[Err] %s: %s\n", r.Input, r.Err)
+ return
+ }
+ fmt.Printf("%s %s %d\n", r.Input, r.Host, r.StatusCode)
+ },
+ }
+
+ if err := options.ValidateOptions(); err != nil {
+ log.Fatal(err)
+ }
+
+ httpxRunner, err := runner.New(&options)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer httpxRunner.Close()
+
+ httpxRunner.RunEnumeration()
+}
diff --git a/go.mod b/go.mod
index 75ecc8f..bdd0ee9 100644
--- a/go.mod
+++ b/go.mod
@@ -1,105 +1,125 @@
module github.com/projectdiscovery/httpx
-go 1.19
+go 1.21
require (
github.com/akrylysov/pogreb v0.10.1 // indirect
- github.com/bluele/gcache v0.0.2
github.com/corpix/uarand v0.2.0
github.com/golang/snappy v0.0.4 // indirect
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf
github.com/julienschmidt/httprouter v1.3.0
github.com/logrusorgru/aurora v2.0.3+incompatible
- github.com/microcosm-cc/bluemonday v1.0.23
- github.com/miekg/dns v1.1.50 // indirect
+ github.com/microcosm-cc/bluemonday v1.0.25
+ github.com/miekg/dns v1.1.56 // indirect
github.com/pkg/errors v0.9.1
- github.com/projectdiscovery/cdncheck v0.0.4-0.20220413175814-b47bc2d578b1
- github.com/projectdiscovery/clistats v0.0.12
+ github.com/projectdiscovery/cdncheck v1.0.9
+ github.com/projectdiscovery/clistats v0.0.20
github.com/projectdiscovery/fdmax v0.0.4
github.com/projectdiscovery/goconfig v0.0.1
- github.com/projectdiscovery/goflags v0.1.8
- github.com/projectdiscovery/gologger v1.1.8
- github.com/projectdiscovery/hmap v0.0.10
- github.com/projectdiscovery/iputil v0.0.2 // indirect
- github.com/projectdiscovery/mapcidr v1.1.0
- github.com/projectdiscovery/rawhttp v0.1.10
- github.com/projectdiscovery/retryablehttp-go v1.0.13
- github.com/projectdiscovery/stringsutil v0.0.2 // indirect
- github.com/projectdiscovery/wappalyzergo v0.0.86
+ github.com/projectdiscovery/goflags v0.1.36
+ github.com/projectdiscovery/gologger v1.1.12
+ github.com/projectdiscovery/hmap v0.0.35
+ github.com/projectdiscovery/mapcidr v1.1.16
+ github.com/projectdiscovery/rawhttp v0.1.31
+ github.com/projectdiscovery/retryablehttp-go v1.0.44
+ github.com/projectdiscovery/wappalyzergo v0.0.109
github.com/remeh/sizedwaitgroup v1.0.0
- github.com/rs/xid v1.4.0
+ github.com/rs/xid v1.5.0
go.etcd.io/bbolt v1.3.7 // indirect
- golang.org/x/net v0.8.0
- golang.org/x/sys v0.6.0 // indirect
- golang.org/x/text v0.8.0
+ golang.org/x/net v0.17.0
+ golang.org/x/sys v0.16.0 // indirect
+ golang.org/x/text v0.14.0
)
require github.com/spaolacci/murmur3 v1.1.0
require (
+ github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057
github.com/PuerkitoBio/goquery v1.8.1
- github.com/bxcodec/faker/v4 v4.0.0-beta.3
+ github.com/corona10/goimagehash v1.1.0
+ github.com/go-faker/faker/v4 v4.1.1
+ github.com/go-rod/rod v0.114.0
github.com/hdm/jarm-go v0.0.7
+ github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056
github.com/mfonda/simhash v0.0.0-20151007195837-79f94a1100d6
github.com/mitchellh/mapstructure v1.5.0
- github.com/projectdiscovery/asnmap v1.0.2
- github.com/projectdiscovery/dsl v0.0.3
- github.com/projectdiscovery/fastdialer v0.0.24
- github.com/projectdiscovery/ratelimit v0.0.6
- github.com/projectdiscovery/tlsx v1.0.4
- github.com/projectdiscovery/utils v0.0.16
- github.com/stretchr/testify v1.8.2
- go.uber.org/multierr v1.10.0
- golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2
+ github.com/projectdiscovery/asnmap v1.0.6
+ github.com/projectdiscovery/dsl v0.0.40
+ github.com/projectdiscovery/fastdialer v0.0.55
+ github.com/projectdiscovery/networkpolicy v0.0.7
+ github.com/projectdiscovery/ratelimit v0.0.23
+ github.com/projectdiscovery/tlsx v1.1.5
+ github.com/projectdiscovery/utils v0.0.74-0.20240115220656-48fef326de18
+ github.com/stretchr/testify v1.8.4
+ github.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968
+ go.uber.org/multierr v1.11.0
+ golang.org/x/exp v0.0.0-20230810033253-352e893a4cad
)
require (
aead.dev/minisign v0.2.0 // indirect
- github.com/Knetic/govaluate v3.0.0+incompatible // indirect
- github.com/Masterminds/semver/v3 v3.2.0 // indirect
+ github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect
+ github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 // indirect
github.com/VividCortex/ewma v1.2.0 // indirect
github.com/alecthomas/chroma v0.10.0 // indirect
+ github.com/andybalholm/brotli v1.0.6 // indirect
github.com/andybalholm/cascadia v1.3.1 // indirect
- github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
+ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/charmbracelet/glamour v0.6.0 // indirect
- github.com/cheggaaa/pb/v3 v3.1.2 // indirect
+ github.com/cheggaaa/pb/v3 v3.1.4 // indirect
+ github.com/cloudflare/cfssl v1.6.4 // indirect
+ github.com/cloudflare/circl v1.3.7 // indirect
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/denisbrodbeck/machineid v1.0.1 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/dlclark/regexp2 v1.8.1 // indirect
- github.com/dsnet/compress v0.0.1 // indirect
- github.com/fatih/color v1.14.1 // indirect
- github.com/golang/protobuf v1.5.2 // indirect
+ github.com/docker/go-units v0.5.0 // indirect
+ github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
+ github.com/fatih/color v1.15.0 // indirect
+ github.com/gaukas/godicttls v0.0.4 // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/certificate-transparency-go v1.1.4 // indirect
github.com/google/go-github/v30 v30.1.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/gorilla/css v1.0.0 // indirect
+ github.com/hashicorp/go-version v1.6.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
+ github.com/kataras/jwt v0.1.8 // indirect
+ github.com/klauspost/compress v1.16.7 // indirect
+ github.com/klauspost/pgzip v1.2.5 // indirect
+ github.com/kljensen/snowball v0.8.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
- github.com/mattn/go-isatty v0.0.17 // indirect
+ github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
- github.com/mholt/archiver v3.1.1+incompatible // indirect
- github.com/minio/selfupdate v0.6.0 // indirect
+ github.com/mholt/archiver/v3 v3.5.1 // indirect
+ github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.1 // indirect
- github.com/nwaples/rardecode v1.1.0 // indirect
+ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
+ github.com/nwaples/rardecode v1.1.3 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
- github.com/pierrec/lz4 v2.6.0+incompatible // indirect
+ github.com/pierrec/lz4/v4 v4.1.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/projectdiscovery/blackrock v0.0.0-20221025011524-9e4efe804fb4 // indirect
- github.com/projectdiscovery/freeport v0.0.4 // indirect
- github.com/projectdiscovery/networkpolicy v0.0.4 // indirect
- github.com/projectdiscovery/retryabledns v1.0.21 // indirect
+ github.com/projectdiscovery/blackrock v0.0.1 // indirect
+ github.com/projectdiscovery/freeport v0.0.5 // indirect
+ github.com/projectdiscovery/gostruct v0.0.2 // indirect
+ github.com/projectdiscovery/retryabledns v1.0.52 // indirect
+ github.com/quic-go/quic-go v0.37.7 // indirect
+ github.com/refraction-networking/utls v1.5.4 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
+ github.com/sashabaranov/go-openai v1.14.2 // indirect
+ github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/tidwall/btree v1.6.0 // indirect
- github.com/tidwall/buntdb v1.2.10 // indirect
+ github.com/tidwall/buntdb v1.3.0 // indirect
github.com/tidwall/gjson v1.14.4 // indirect
github.com/tidwall/grect v0.1.4 // indirect
github.com/tidwall/match v1.1.1 // indirect
@@ -108,20 +128,23 @@ require (
github.com/tidwall/tinyqueue v0.1.1 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6 // indirect
- github.com/weppos/publicsuffix-go v0.20.0 // indirect
+ github.com/weppos/publicsuffix-go v0.30.1-0.20230422193905-8fecedd899db // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/yl2chen/cidranger v1.0.2 // indirect
+ github.com/ysmood/fetchup v0.2.3 // indirect
+ github.com/ysmood/goob v0.4.0 // indirect
+ github.com/ysmood/got v0.34.1 // indirect
+ github.com/ysmood/gson v0.7.3 // indirect
+ github.com/ysmood/leakless v0.8.0 // indirect
github.com/yuin/goldmark v1.5.4 // indirect
github.com/yuin/goldmark-emoji v1.0.1 // indirect
github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 // indirect
- github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4 // indirect
- github.com/zmap/zcrypto v0.0.0-20230205235340-d51ce4775101 // indirect
- golang.org/x/crypto v0.7.0 // indirect
- golang.org/x/mod v0.8.0 // indirect
- golang.org/x/oauth2 v0.5.0 // indirect
- golang.org/x/tools v0.6.0 // indirect
+ golang.org/x/crypto v0.17.0 // indirect
+ golang.org/x/mod v0.12.0 // indirect
+ golang.org/x/oauth2 v0.11.0 // indirect
+ golang.org/x/tools v0.13.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
- google.golang.org/protobuf v1.28.1 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/djherbis/times.v1 v1.3.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
diff --git a/go.sum b/go.sum
index 8d7e09a..1f6ad0a 100644
--- a/go.sum
+++ b/go.sum
@@ -1,9 +1,12 @@
aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk=
aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ=
-github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg=
-github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
-github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
-github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
+cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
+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.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
+github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
+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/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
@@ -15,87 +18,126 @@ github.com/akrylysov/pogreb v0.10.1 h1:FqlR8VR7uCbJdfUob916tPM+idpKgeESDXOA1K0DK
github.com/akrylysov/pogreb v0.10.1/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI=
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/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
+github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
+github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
-github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
-github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
+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/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
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/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/bits-and-blooms/bitset v1.3.1 h1:y+qrlmq3XsWi+xZqSaueaE8ry8Y127iMxlMfqcK8p0g=
-github.com/bits-and-blooms/bloom/v3 v3.3.1 h1:K2+A19bXT8gJR5mU7y+1yW6hsKfNCjcP2uNfLFKncjQ=
-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/bxcodec/faker/v4 v4.0.0-beta.3 h1:gqYNBvN72QtzKkYohNDKQlm+pg+uwBDVMN28nWHS18k=
-github.com/bxcodec/faker/v4 v4.0.0-beta.3/go.mod h1:m6+Ch1Lj3fqW/unZmvkXIdxWS5+XQWPWxcbbQW2X+Ho=
+github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c=
+github.com/bits-and-blooms/bitset v1.8.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/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc=
github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc=
-github.com/cheggaaa/pb/v3 v3.1.2 h1:FIxT3ZjOj9XJl0U4o2XbEhjFfZl7jCVCDOGq1ZAB7wQ=
-github.com/cheggaaa/pb/v3 v3.1.2/go.mod h1:SNjnd0yKcW+kw0brSusraeDd5Bf1zBfxAzTL2ss3yQ4=
+github.com/cheggaaa/pb/v3 v3.1.4 h1:DN8j4TVVdKu3WxVwcRKu0sG00IIU6FewoABZzXbRQeo=
+github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA=
+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.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
+github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ=
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4=
+github.com/corona10/goimagehash v1.1.0 h1:teNMX/1e+Wn/AYSbLHX8mj+mF9r60R1kBeqE9MkoYwI=
+github.com/corona10/goimagehash v1.1.0/go.mod h1:VkvE0mLn84L4aF8vCb6mafVajEb6QYMHl2ZJLn0mOGI=
github.com/corpix/uarand v0.2.0 h1:U98xXwud/AVuCpkpgfPF7J5TQgr7R5tqT8VZP5KWbzE=
github.com/corpix/uarand v0.2.0/go.mod h1:/3Z1QIqWkDIhf6XWn/08/uMHoQ8JUoTIKc2iPchBOmM=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
+github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
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/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0=
github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
-github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
-github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
+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.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
+github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
-github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
-github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
-github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
-github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
+github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
+github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
+github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
+github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
+github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
+github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
+github.com/go-faker/faker/v4 v4.1.1 h1:zkxj/JH/aezB4R6cTEMKU7qcVScGhlB3qRtF3D7K+rI=
+github.com/go-faker/faker/v4 v4.1.1/go.mod h1:uuNc0PSRxF8nMgjGrrrU4Nw5cF30Jc6Kd0/FUTTYbhg=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-rod/rod v0.114.0 h1:P+zLOqsj+vKf4C86SfjP6ymyPl9VXoYKm+ceCeQms6Y=
+github.com/go-rod/rod v0.114.0/go.mod h1:aiedSEFg5DwG/fnNbUOTPMTTWX3MRj6vIs/a684Mthw=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
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.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+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 v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/certificate-transparency-go v1.1.4 h1:hCyXHDbtqlr/lMXU0D4WgbalXL0Zk4dSWWMbPV8VrqY=
+github.com/google/certificate-transparency-go v1.1.4/go.mod h1:D6lvbfwckhNrbM9WVl1EVeMOyzC19mpIjMOI4nxBHtQ=
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 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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.1.0/go.mod h1:Ev4Tre8QoKiolvbpOSG3FIi4Mlon3S2Nt9W5JYqKiwA=
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/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
-github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4=
+github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
+github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/golang-lru/v2 v2.0.6 h1:3xi/Cafd1NaoEnS/yDssIiuVeDVywU0QdFGl3aQaQHM=
+github.com/hashicorp/golang-lru/v2 v2.0.6/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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA=
+github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
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/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/kataras/jwt v0.1.8 h1:u71baOsYD22HWeSOg32tCHbczPjdCk7V4MMeJqTtmGk=
+github.com/kataras/jwt v0.1.8/go.mod h1:Q5j2IkcIHnfwy+oNY3TVWuEBJNw0ADgCcXK9CaZwV4o=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
+github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
+github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
+github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
+github.com/kljensen/snowball v0.8.0 h1:WU4cExxK6sNW33AiGdbn4e8RvloHrhkAssu2mVJ11kg=
+github.com/kljensen/snowball v0.8.0/go.mod h1:OGo5gFWjaeXqCu4iIrMl5OYip9XUJHGOU5eSkPjVg2A=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
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=
@@ -107,24 +149,24 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
-github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
-github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
+github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mfonda/simhash v0.0.0-20151007195837-79f94a1100d6 h1:bjfMeqxWEJ6IRUvGkiTkSwx0a6UdQJsbirRSoXogteY=
github.com/mfonda/simhash v0.0.0-20151007195837-79f94a1100d6/go.mod h1:WVJJvUw/pIOcwu2O8ZzHEhmigq2jzwRNfJVRMJB7bR8=
-github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU=
-github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU=
+github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
+github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM=
-github.com/microcosm-cc/bluemonday v1.0.23 h1:SMZe2IGa0NuHvnVNAZ+6B38gsTbi5e4sViiWJyDDqFY=
-github.com/microcosm-cc/bluemonday v1.0.23/go.mod h1:mN70sk7UkkF8TUr2IGBpNN0jAgStuPzlK76QuruE/z4=
+github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg=
+github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE=
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
-github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
-github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
-github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=
-github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=
+github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
+github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
+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/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -133,74 +175,87 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
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/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.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
-github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
+github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
+github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
-github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc=
+github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
+github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
+github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
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/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
+github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
-github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
+github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
+github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
-github.com/pierrec/lz4 v2.6.0+incompatible h1:Ix9yFKn1nSPBLFl/yZknTp8TU5G4Ps0JDmguYK6iH1A=
-github.com/pierrec/lz4 v2.6.0+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM=
+github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/projectdiscovery/asnmap v1.0.2 h1:2+8tqzJeFVpJS7u27YH7kMK7edDAr7OsmSxs92aWFNc=
-github.com/projectdiscovery/asnmap v1.0.2/go.mod h1:64YfriVxyRQvqc+1iPMHMf+i/of2jr+Qx7geCIm4ZsU=
-github.com/projectdiscovery/blackrock v0.0.0-20221025011524-9e4efe804fb4 h1:EsrQ/zkotVodSJLOch3pV/UYt1vQcwyIs5HX0sm1ljE=
-github.com/projectdiscovery/blackrock v0.0.0-20221025011524-9e4efe804fb4/go.mod h1:5tNGQP9kOfW+X5+40pZP8aqPYLHs45nJkFaSHLxdeH8=
-github.com/projectdiscovery/cdncheck v0.0.4-0.20220413175814-b47bc2d578b1 h1:QtTPPx0uu42AsQJiXT86/wqdHS7/iVcgz1VM38tjv20=
-github.com/projectdiscovery/cdncheck v0.0.4-0.20220413175814-b47bc2d578b1/go.mod h1:EevMeCG1ogBoUJYaa0Mv9R1VUboDm/DiynId7DboKy0=
-github.com/projectdiscovery/clistats v0.0.12 h1:KLYJxpiwEFidduU4PbcwEcCQ2L7c5wrf7DI5IN5fZ+8=
-github.com/projectdiscovery/clistats v0.0.12/go.mod h1:9luKJj+7Hjq3+a7g129sKWRYx4SbTdkUWZQxabn3H5Y=
-github.com/projectdiscovery/dsl v0.0.3 h1:oWlZZaSADqoyfJdUHWqAzpB65NpvLukZQGFv1uTtU3g=
-github.com/projectdiscovery/dsl v0.0.3/go.mod h1:ST66slxtp7fsFqTOq3k6+1TDV7RH1Moz3sPBqUGn4Fg=
-github.com/projectdiscovery/fastdialer v0.0.24 h1:yEyYALCmDQpPYWttZ4uo9AJseqt4mYWcyx3s9WYzqW8=
-github.com/projectdiscovery/fastdialer v0.0.24/go.mod h1:X7zZy3BGdGoprR6CftHKeJyV86a3OjSAlJcNU7FL26E=
+github.com/projectdiscovery/asnmap v1.0.6 h1:NZj1hybBf4KF/hMCgJ6E2GXCe60tg5fIRkexEIU+0og=
+github.com/projectdiscovery/asnmap v1.0.6/go.mod h1:cXQjWMgxkl+8A4861Nms9u+ASxQLTb47imJD+AyX+dU=
+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.0.9 h1:BS15gzj9gb5AVSKqTDzPamfSgStu7nJQOocUvrssFlg=
+github.com/projectdiscovery/cdncheck v1.0.9/go.mod h1:18SSl1w7rMj53CGeRIZTbDoa286a6xZIxGbaiEo4Fxs=
+github.com/projectdiscovery/clistats v0.0.20 h1:5jO5SLiRJ7f0nDV0ndBNmBeesbROouPooH+DGMgoWq4=
+github.com/projectdiscovery/clistats v0.0.20/go.mod h1:GJ2av0KnOvK0AISQnP8hyDclYIji1LVkx2l0pwnzAu4=
+github.com/projectdiscovery/dsl v0.0.40 h1:bY6aOPEIJ+YYYXX2qRZj1y4VsZlV9VU0oaD+GrR/j6I=
+github.com/projectdiscovery/dsl v0.0.40/go.mod h1:wo6lB5vwKWbTo5qDdJ1q7S1bYthm2mwGZ+MVefJmdZo=
+github.com/projectdiscovery/fastdialer v0.0.55 h1:dcD3La9MsImgQMrBnG0/w5Mu8PRJu2TU1STycKSSodc=
+github.com/projectdiscovery/fastdialer v0.0.55/go.mod h1:DNP62sWCLp0YHXwhlo73iyZODpSZE7dVstt2GNAC7+A=
github.com/projectdiscovery/fdmax v0.0.4 h1:K9tIl5MUZrEMzjvwn/G4drsHms2aufTn1xUdeVcmhmc=
github.com/projectdiscovery/fdmax v0.0.4/go.mod h1:oZLqbhMuJ5FmcoaalOm31B1P4Vka/CqP50nWjgtSz+I=
-github.com/projectdiscovery/freeport v0.0.4 h1:H4VrK/7hUcC1zbg46zv9iSMBACBDpUqcHkV+FUyXISw=
-github.com/projectdiscovery/freeport v0.0.4/go.mod h1:PY0bxSJ34HVy67LHIeF3uIutiCSDwOqKD8ruBkdiCwE=
+github.com/projectdiscovery/freeport v0.0.5 h1:jnd3Oqsl4S8n0KuFkE5Hm8WGDP24ITBvmyw5pFTHS8Q=
+github.com/projectdiscovery/freeport v0.0.5/go.mod h1:PY0bxSJ34HVy67LHIeF3uIutiCSDwOqKD8ruBkdiCwE=
github.com/projectdiscovery/goconfig v0.0.1 h1:36m3QjohZvemqh9bkJAakaHsm9iEZ2AcQSS18+0QX/s=
github.com/projectdiscovery/goconfig v0.0.1/go.mod h1:CPO25zR+mzTtyBrsygqsHse0sp/4vB/PjaHi9upXlDw=
-github.com/projectdiscovery/goflags v0.1.8 h1:Urhm2Isq2BdRt8h4h062lHKYXO65RHRjGTDSkUwex/g=
-github.com/projectdiscovery/goflags v0.1.8/go.mod h1:Yxi9tclgwGczzDU65ntrwaIql5cXeTvW5j2WxFuF+Jk=
-github.com/projectdiscovery/gologger v1.1.8 h1:CFlCzGlqAhPqWIrAXBt1OVh5jkMs1qgoR/z4xhdzLNE=
-github.com/projectdiscovery/gologger v1.1.8/go.mod h1:bNyVaC1U/NpJtFkJltcesn01NR3K8Hg6RsLVce6yvrw=
-github.com/projectdiscovery/hmap v0.0.10 h1:O6ALGW3BK+FmknLXW7ENwQevLs+faRJuoRbDtakZZus=
-github.com/projectdiscovery/hmap v0.0.10/go.mod h1:xdtyejCgl5LJW7yz7nf/ut32tWuV/l7FjUzItiCtJIg=
-github.com/projectdiscovery/iputil v0.0.2 h1:f6IGnZF4RImJLysPSPG3D84jyTH34q3lihCFeP+eZzI=
-github.com/projectdiscovery/iputil v0.0.2/go.mod h1:J3Pcz1q51pi4/JL871mQztg0KOzyWDPxnPLOYJm2pVQ=
-github.com/projectdiscovery/mapcidr v1.1.0 h1:Yeb+CGVsRYvHmZ9YSHb9iy4tzY9YuOm3oTFX/xzGhVU=
-github.com/projectdiscovery/mapcidr v1.1.0/go.mod h1:hck0bWXka5ZkUaBG+TWt99bzLy+4hAg9oANhEmm3GNs=
-github.com/projectdiscovery/networkpolicy v0.0.4 h1:zcGjEqZbyECZEdyCy1jVuwOS7Ww1mzgCefQU75XqdJA=
-github.com/projectdiscovery/networkpolicy v0.0.4/go.mod h1:DIXwKs3sQyfCoWHKRLQiRrEorSQW4Zrh4ftu7oDVK6w=
-github.com/projectdiscovery/ratelimit v0.0.6 h1:SAD2ArdT9F8NmbkAIZpl7DjNnbiXdUQLnMZt5dbVmZ0=
-github.com/projectdiscovery/ratelimit v0.0.6/go.mod h1:WFL6gIggPLTwYwDbxqQODuWrz/lcMP2E5ofKSAz3YwI=
-github.com/projectdiscovery/rawhttp v0.1.10 h1:wkQk/lpMVzi4AAELRDaBQEgMqyerpkz3Kks7QgDF274=
-github.com/projectdiscovery/rawhttp v0.1.10/go.mod h1:cIlAWs3Nu8CTBArx/8GU1baimR5T1eO62TJFG2rAnSc=
-github.com/projectdiscovery/retryabledns v1.0.21 h1:vOpPQR1q8Z824uoA8JXCI/RyvDAssPeD68Onz9hP/ds=
-github.com/projectdiscovery/retryabledns v1.0.21/go.mod h1:6oTPKMRlKZ7lIIEzTH723K6RvNRjmm6fe9br4Dom3UI=
-github.com/projectdiscovery/retryablehttp-go v1.0.13 h1:gKxd/J08Dxc8a/LFvTz9+JUedEvivH3PoDnQQEHAY4M=
-github.com/projectdiscovery/retryablehttp-go v1.0.13/go.mod h1:L5HwtGSvc0E3dNVtVqPACWOmr21Bbop2ZhpbCPYEeYU=
+github.com/projectdiscovery/goflags v0.1.36 h1:gElwVU9BJsUbxjyHqDTmlGsB8Br2DDxbfMQMXLYvYhg=
+github.com/projectdiscovery/goflags v0.1.36/go.mod h1:A+MLWJgGKZ2WUED0ZlW5EQ4mmJ/s71VnvY6KF5ThLaM=
+github.com/projectdiscovery/gologger v1.1.12 h1:uX/QkQdip4PubJjjG0+uk5DtyAi1ANPJUvpmimXqv4A=
+github.com/projectdiscovery/gologger v1.1.12/go.mod h1:DI8nywPLERS5mo8QEA9E7gd5HZ3Je14SjJBH3F5/kLw=
+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/hmap v0.0.35 h1:JkadBpuB/GttuS+O72E26y6RrC8Ox90iFunrI2/zvrc=
+github.com/projectdiscovery/hmap v0.0.35/go.mod h1:EXm6Z/e10GS0uK7qNLH2OcT0bIKq+T4ZDWxSzK0ho5U=
+github.com/projectdiscovery/mapcidr v1.1.16 h1:rjj1w5D6hbTsUQXYClLcGdfBEy9bryclgi70t0vBggo=
+github.com/projectdiscovery/mapcidr v1.1.16/go.mod h1:rGqpBhStdwOQ2uS62QM9qPsybwMwIhT7CTd2bxoHs8Q=
+github.com/projectdiscovery/networkpolicy v0.0.7 h1:AwHqBRXBqDQgnWzBMuoJtHBNEYBw+NFp/4qIK688x7o=
+github.com/projectdiscovery/networkpolicy v0.0.7/go.mod h1:CK0CnFoLF1Nou6mY7P4WODSAxhPN8g8g7XpapgEP8tI=
+github.com/projectdiscovery/ratelimit v0.0.23 h1:Fz2A57UW6GK0L0huOGVXd97EhASrJV41SC1NrGImShU=
+github.com/projectdiscovery/ratelimit v0.0.23/go.mod h1:042iuvdggjUnsgAIzyxM3iLFveMaXnGTRwlCpfd03I0=
+github.com/projectdiscovery/rawhttp v0.1.31 h1:ry04GKDuS8bH8UNzWdXV1uB3+5PHdzpdiM8zSwJPSMk=
+github.com/projectdiscovery/rawhttp v0.1.31/go.mod h1:cjqO+O62/9MIxXLctnLNq7muOF7MrDrpWniBLo2m6tg=
+github.com/projectdiscovery/retryabledns v1.0.52 h1:jJRIT5y7KYZvaZAAvlkxvkKkQzst6LvEeLDqRc3LeOM=
+github.com/projectdiscovery/retryabledns v1.0.52/go.mod h1:Ea478e6XNVAmfH4KwqtLNjkwdgkpVH1O3+FL2dKLNb8=
+github.com/projectdiscovery/retryablehttp-go v1.0.44 h1:hicCe2h6daHt4muPovmffZE3YKBqGioreO6EpIGZ87g=
+github.com/projectdiscovery/retryablehttp-go v1.0.44/go.mod h1:7ECXK2cH2/G4sstf8hacyrMdPPJ/3wCAO5tFPZ4iO4s=
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.0.4 h1:aVkoxl1Vq6Uh7Ietpv2X5TfXPRRBj3VGCtoelhj9BE0=
-github.com/projectdiscovery/tlsx v1.0.4/go.mod h1:N50JpF12eeLg+rcDRwIZ8r974MOvJZeV7M0rIxHWV2g=
-github.com/projectdiscovery/utils v0.0.16 h1:7vmi3haCyM3vk0yXSLjoid4p2/7bo042rcmG4Dtk+Sk=
-github.com/projectdiscovery/utils v0.0.16/go.mod h1:Cu216AlQ7rAYa8aDBqB2OgNfu5p24Uj+tG9RxV8Wbfs=
-github.com/projectdiscovery/wappalyzergo v0.0.86 h1:w4UP+F1emlhoDXRHZanSzWQNYm/tv/A0ocGOpeuParo=
-github.com/projectdiscovery/wappalyzergo v0.0.86/go.mod h1:HvYuW0Be4JCjVds/+XAEaMSqRG9yrI97UmZq0TPk6A0=
+github.com/projectdiscovery/tlsx v1.1.5 h1:S8KV2ckcjW3hDBa/REmDdsZfHwYJ9eKoZ7rtgETkwkM=
+github.com/projectdiscovery/tlsx v1.1.5/go.mod h1:0a0TdWb3fYeVpuPsJuf5AGtwZIKwkY0kxdO9lojU6S4=
+github.com/projectdiscovery/utils v0.0.74-0.20240115220656-48fef326de18 h1:hQHfr0YlGGODVMQrN3c41itC477xdFDy/3hJbOfjPqY=
+github.com/projectdiscovery/utils v0.0.74-0.20240115220656-48fef326de18/go.mod h1:SEb3ZoGy1nxdnPNXAGhMZNhRcokRkoMEjC6l9H59t1s=
+github.com/projectdiscovery/wappalyzergo v0.0.109 h1:BERfwTRn1dvB1tbhyc5m67R8VkC9zbVuPsEq4VEm07k=
+github.com/projectdiscovery/wappalyzergo v0.0.109/go.mod h1:4Z3DKhi75zIPMuA+qSDDWxZvnhL4qTLmDx4dxNMu7MA=
+github.com/quic-go/quic-go v0.37.7 h1:AgKsQLZ1+YCwZd2GYhBUsJDYZwEkA5gENtAjb+MxONU=
+github.com/quic-go/quic-go v0.37.7/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU=
+github.com/refraction-networking/utls v1.5.4 h1:9k6EO2b8TaOGsQ7Pl7p9w6PUhx18/ZCeT0WNTZ7Uw4o=
+github.com/refraction-networking/utls v1.5.4/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw=
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=
@@ -208,15 +263,20 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
-github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
-github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
-github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
+github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
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.14.2 h1:5DPTtR9JBjKPJS008/A409I5ntFhUPPGCmaAihcPRyo=
+github.com/sashabaranov/go-openai v1.14.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
+github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
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=
@@ -227,22 +287,23 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
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.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
-github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
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.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg=
github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
-github.com/tidwall/buntdb v1.2.10 h1:U/ebfkmYPBnyiNZIirUiWFcxA/mgzjbKlyPynFsPtyM=
-github.com/tidwall/buntdb v1.2.10/go.mod h1:lZZrZUWzlyDJKlLQ6DKAy53LnG7m5kHyrEHvvcDmBpU=
+github.com/tidwall/buntdb v1.3.0 h1:gdhWO+/YwoB2qZMeAU9JcWWsHSYU3OvcieYgFRS0zwA=
+github.com/tidwall/buntdb v1.3.0/go.mod h1:lZZrZUWzlyDJKlLQ6DKAy53LnG7m5kHyrEHvvcDmBpU=
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/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 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
@@ -252,23 +313,34 @@ 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/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
-github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
+github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
+github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6 h1:TtyC78WMafNW8QFfv3TeP3yWNDG+uxNkk9vOrnDu6JA=
github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6/go.mod h1:h8272+G2omSmi30fBXiZDMkmHuOgonplfKIKjQWzlfs=
-github.com/weppos/publicsuffix-go v0.12.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
-github.com/weppos/publicsuffix-go v0.15.1-0.20220724114530-e087fba66a37/go.mod h1:5ZC/Uv3fIEUE0eP6o9+Yg4+5+W8V0/BieMi05feGXVA=
-github.com/weppos/publicsuffix-go v0.20.0 h1:59ypvSUbW3Dunc6zVm+v+MmXf2Q6cGiNDkxgRIzEnaA=
-github.com/weppos/publicsuffix-go v0.20.0/go.mod h1:5ZC/Uv3fIEUE0eP6o9+Yg4+5+W8V0/BieMi05feGXVA=
-github.com/weppos/publicsuffix-go/publicsuffix/generator v0.0.0-20220704091424-e0182326a282/go.mod h1:GHfoeIdZLdZmLjMlzBftbTDntahTttUMWjxZwQJhULE=
+github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
+github.com/weppos/publicsuffix-go v0.30.1-0.20230422193905-8fecedd899db h1:/WcxBne+5CbtbgWd/sV2wbravmr4sT7y52ifQaCgoLs=
+github.com/weppos/publicsuffix-go v0.30.1-0.20230422193905-8fecedd899db/go.mod h1:aiQaH1XpzIfgrJq3S1iw7w+3EDbRP7mF5fmwUhWyRUs=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU=
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
+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.0.2 h1:VuWweTmXK+zedLqYufJdh3PlxDNBOfFHjIZlPT2T5nw=
+github.com/ysmood/gop v0.0.2/go.mod h1:rr5z2z27oGEbyB787hpEcx4ab8cCiPnKxn0SUHt6xzk=
+github.com/ysmood/got v0.34.1 h1:IrV2uWLs45VXNvZqhJ6g2nIhY+pgIG1CUoOcqfXFl1s=
+github.com/ysmood/got v0.34.1/go.mod h1:yddyjq/PmAf08RMLSwDjPyCvHvYed+WjHnQxpH851LM=
+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.8.0 h1:BzLrVoiwxikpgEQR0Lk8NyBN5Cit2b1z+u0mgL4ZJak=
+github.com/ysmood/leakless v0.8.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU=
@@ -278,60 +350,70 @@ github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGj
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 h1:17HHAgFKlLcZsDOjBOUrd5hDihb1ggf+1a5dTbkgkIY=
github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
-github.com/zmap/zcrypto v0.0.0-20230205235340-d51ce4775101 h1:QuLjRpIBjqene8VvB+VhQ4eTcQGCQ7JDuk0/Fp4sLLw=
-github.com/zmap/zcrypto v0.0.0-20230205235340-d51ce4775101/go.mod h1:bRZdjnJaHWVXKEwrfAZMd0gfRjZGNhTbZwzp07s0Abw=
+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-20230422215203-9a665e1e9968 h1:YOQ1vXEwE4Rnj+uQ/3oCuJk5wgVsvUyW+glsndwYuyA=
+github.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968/go.mod h1:xIuOvYCZX21S5Z9bK1BMrertTGX/F8hgAPw7ERJRNS0=
+github.com/zmap/zlint/v3 v3.0.0/go.mod h1:paGwFySdHIBEMJ61YjoqT4h7Ge+fdYG4sUQhnTb1lJ8=
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
-go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
-go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
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-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-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
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.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
+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/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI=
-golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
+golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
+golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/exp v0.0.0-20230810033253-352e893a4cad h1:g0bG7Z4uG+OgH2QDODnjp6ggkk1bJDsINcuWmJN1iJU=
+golang.org/x/exp v0.0.0-20230810033253-352e893a4cad/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.4.2/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 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/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-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200528225125-3c3fba18258b/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+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 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
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.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
+golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
+golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -342,49 +424,56 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200523222454-059865788121/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-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210510120138-977fb7262007/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-20210630005230-0f9fa26af87c/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-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.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
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.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
+golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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-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/text v0.3.0/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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/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.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
+golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
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/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
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.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
+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.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -398,9 +487,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
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.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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=
diff --git a/internal/testutils/integration.go b/internal/testutils/integration.go
index e5c23e4..2f5b3d0 100644
--- a/internal/testutils/integration.go
+++ b/internal/testutils/integration.go
@@ -7,7 +7,7 @@ import (
"strings"
)
-// RunNucleiAndGetResults returns a list of results for a template
+// RunHttpxAndGetResults returns a list of the results
func RunHttpxAndGetResults(url string, debug bool, extra ...string) ([]string, error) {
cmd := exec.Command("bash", "-c")
cmdLine := `echo "` + url + `" | ./httpx `
@@ -35,7 +35,7 @@ func RunHttpxAndGetResults(url string, debug bool, extra ...string) ([]string, e
return parts, nil
}
-// RunNucleiAndGetResults returns a list of results for a template
+// RunHttpxAndGetCombinedResults returns the results as a single string variable
func RunHttpxAndGetCombinedResults(url string, debug bool, extra ...string) (string, error) {
cmd := exec.Command("bash", "-c")
cmdLine := `echo "` + url + `" | ./httpx `
@@ -53,6 +53,8 @@ func RunHttpxAndGetCombinedResults(url string, debug bool, extra ...string) (str
}
return string(data), nil
}
+
+// RunHttpxBinaryAndGetResults returns a list of the results
func RunHttpxBinaryAndGetResults(target string, httpxBinary string, debug bool, args []string) ([]string, error) {
cmd := exec.Command("bash", "-c")
cmdLine := fmt.Sprintf(`echo %s | %s `, target, httpxBinary)
diff --git a/runner/banner.go b/runner/banner.go
index e81804e..a177f4e 100644
--- a/runner/banner.go
+++ b/runner/banner.go
@@ -1,11 +1,10 @@
package runner
import (
- "github.com/projectdiscovery/gologger"
+ "github.com/projectdiscovery/gologger"
updateutils "github.com/projectdiscovery/utils/update"
)
-
const banner = `
__ __ __ _ __
/ /_ / /_/ /_____ | |/ /
@@ -16,7 +15,7 @@ const banner = `
`
// Version is the current version of httpx
-const version = `v1.2.9`
+const version = `v1.3.9`
// showBanner is used to show the banner to the user
func showBanner() {
@@ -30,4 +29,4 @@ func GetUpdateCallback() func() {
showBanner()
updateutils.GetUpdateToolCallback("httpx", version)()
}
-}
\ No newline at end of file
+}
diff --git a/runner/headless.go b/runner/headless.go
new file mode 100644
index 0000000..fb76e34
--- /dev/null
+++ b/runner/headless.go
@@ -0,0 +1,137 @@
+package runner
+
+import (
+ "fmt"
+ "os"
+ "time"
+
+ "github.com/go-rod/rod"
+ "github.com/go-rod/rod/lib/launcher"
+ "github.com/go-rod/rod/lib/launcher/flags"
+ "github.com/go-rod/rod/lib/proto"
+ "github.com/pkg/errors"
+ fileutil "github.com/projectdiscovery/utils/file"
+ osutils "github.com/projectdiscovery/utils/os"
+)
+
+// MustDisableSandbox determines if the current os and user needs sandbox mode disabled
+func MustDisableSandbox() bool {
+ // linux with root user needs "--no-sandbox" option
+ // https://github.com/chromium/chromium/blob/c4d3c31083a2e1481253ff2d24298a1dfe19c754/chrome/test/chromedriver/client/chromedriver.py#L209
+ return osutils.IsLinux() && os.Geteuid() == 0
+}
+
+type Browser struct {
+ tempDir string
+ engine *rod.Browser
+ // TODO: Remove the Chrome PID kill code in favor of using Leakless(true).
+ // This change will be made if there are no complaints about zombie Chrome processes.
+ // Reference: https://github.com/projectdiscovery/httpx/pull/1426
+ // pids map[int32]struct{}
+}
+
+func NewBrowser(proxy string, useLocal bool, optionalArgs map[string]string) (*Browser, error) {
+ dataStore, err := os.MkdirTemp("", "nuclei-*")
+ if err != nil {
+ return nil, errors.Wrap(err, "could not create temporary directory")
+ }
+
+ // pids := processutil.FindProcesses(processutil.IsChromeProcess)
+
+ chromeLauncher := launcher.New().
+ Leakless(true).
+ Set("disable-gpu", "true").
+ Set("ignore-certificate-errors", "true").
+ Set("ignore-certificate-errors", "1").
+ Set("disable-crash-reporter", "true").
+ Set("disable-notifications", "true").
+ Set("hide-scrollbars", "true").
+ Set("window-size", fmt.Sprintf("%d,%d", 1080, 1920)).
+ Set("mute-audio", "true").
+ Set("incognito", "true").
+ Delete("use-mock-keychain").
+ Headless(true).
+ UserDataDir(dataStore)
+
+ if MustDisableSandbox() {
+ chromeLauncher = chromeLauncher.NoSandbox(true)
+ }
+
+ executablePath, err := os.Executable()
+ if err != nil {
+ return nil, err
+ }
+
+ // if musl is used, most likely we are on alpine linux which is not supported by go-rod, so we fallback to default chrome
+ useMusl, _ := fileutil.UseMusl(executablePath)
+ if useLocal || useMusl {
+ if chromePath, hasChrome := launcher.LookPath(); hasChrome {
+ chromeLauncher.Bin(chromePath)
+ } else {
+ return nil, errors.New("the chrome browser is not installed")
+ }
+ }
+
+ if proxy != "" {
+ chromeLauncher = chromeLauncher.Proxy(proxy)
+ }
+
+ for k, v := range optionalArgs {
+ chromeLauncher.Set(flags.Flag(k), v)
+ }
+
+ launcherURL, err := chromeLauncher.Launch()
+ if err != nil {
+ return nil, err
+ }
+
+ browser := rod.New().ControlURL(launcherURL)
+ if browserErr := browser.Connect(); browserErr != nil {
+ return nil, browserErr
+ }
+
+ engine := &Browser{
+ tempDir: dataStore,
+ engine: browser,
+ // pids: pids,
+ }
+ return engine, nil
+}
+
+func (b *Browser) ScreenshotWithBody(url string, timeout time.Duration) ([]byte, string, error) {
+ page, err := b.engine.Page(proto.TargetCreateTarget{})
+ if err != nil {
+ return nil, "", err
+ }
+ page = page.Timeout(timeout)
+ defer page.Close()
+
+ if err := page.Navigate(url); err != nil {
+ return nil, "", err
+ }
+
+ page.Timeout(5 * time.Second).WaitNavigation(proto.PageLifecycleEventNameFirstMeaningfulPaint)()
+
+ if err := page.WaitLoad(); err != nil {
+ return nil, "", err
+ }
+ _ = page.WaitIdle(1 * time.Second)
+
+ screenshot, err := page.Screenshot(true, &proto.PageCaptureScreenshot{})
+ if err != nil {
+ return nil, "", err
+ }
+
+ body, err := page.HTML()
+ if err != nil {
+ return screenshot, "", err
+ }
+
+ return screenshot, body, nil
+}
+
+func (b *Browser) Close() {
+ b.engine.Close()
+ os.RemoveAll(b.tempDir)
+ // processutil.CloseProcesses(processutil.IsChromeProcess, b.pids)
+}
diff --git a/runner/options.go b/runner/options.go
index d231c5d..f304414 100644
--- a/runner/options.go
+++ b/runner/options.go
@@ -34,12 +34,10 @@ const (
DefaultOutputDirectory = "output"
)
-var defaultProviders = strings.Join(cdncheck.GetDefaultProviders(), ", ")
-
// OnResultCallback (hostResult)
type OnResultCallback func(Result)
-type scanOptions struct {
+type ScanOptions struct {
Methods []string
StoreResponseDirectory string
RequestURI string
@@ -54,6 +52,7 @@ type scanOptions struct {
OutputWebSocket bool
OutputWithNoColor bool
OutputMethod bool
+ ResponseHeadersInStdout bool
ResponseInStdout bool
Base64ResponseInStdout bool
ChainInStdout bool
@@ -73,6 +72,7 @@ type scanOptions struct {
NoFallbackScheme bool
TechDetect bool
StoreChain bool
+ StoreVisionReconClusters bool
MaxResponseBodySizeToSave int
MaxResponseBodySizeToRead int
OutputExtractRegex string
@@ -85,10 +85,16 @@ type scanOptions struct {
OutputLinesCount bool
OutputWordsCount bool
Hashes string
+ Screenshot bool
+ UseInstalledChrome bool
+ DisableStdin bool
+ NoScreenshotBytes bool
+ NoHeadlessBody bool
+ ScreenshotTimeout int
}
-func (s *scanOptions) Clone() *scanOptions {
- return &scanOptions{
+func (s *ScanOptions) Clone() *ScanOptions {
+ return &ScanOptions{
Methods: s.Methods,
StoreResponseDirectory: s.StoreResponseDirectory,
RequestURI: s.RequestURI,
@@ -103,6 +109,7 @@ func (s *scanOptions) Clone() *scanOptions {
OutputWebSocket: s.OutputWebSocket,
OutputWithNoColor: s.OutputWithNoColor,
OutputMethod: s.OutputMethod,
+ ResponseHeadersInStdout: s.ResponseHeadersInStdout,
ResponseInStdout: s.ResponseInStdout,
Base64ResponseInStdout: s.Base64ResponseInStdout,
ChainInStdout: s.ChainInStdout,
@@ -131,6 +138,11 @@ func (s *scanOptions) Clone() *scanOptions {
OutputLinesCount: s.OutputLinesCount,
OutputWordsCount: s.OutputWordsCount,
Hashes: s.Hashes,
+ Screenshot: s.Screenshot,
+ UseInstalledChrome: s.UseInstalledChrome,
+ NoScreenshotBytes: s.NoScreenshotBytes,
+ NoHeadlessBody: s.NoHeadlessBody,
+ ScreenshotTimeout: s.ScreenshotTimeout,
}
}
@@ -143,6 +155,7 @@ type Options struct {
filterStatusCode []int
filterContentLength []int
Output string
+ OutputAll bool
StoreResponseDir string
HTTPProxy string
SocksProxy string
@@ -155,6 +168,7 @@ type Options struct {
OutputMatchStatusCode string
OutputMatchContentLength string
OutputFilterStatusCode string
+ OutputFilterErrorPage bool
OutputFilterContentLength string
InputRawRequest string
rawRequest string
@@ -177,6 +191,7 @@ type Options struct {
Location bool
ContentLength bool
FollowRedirects bool
+ RespectHSTS bool
StoreResponse bool
JSONOutput bool
CSVOutput bool
@@ -187,8 +202,9 @@ type Options struct {
NoColor bool
OutputServerHeader bool
OutputWebSocket bool
- responseInStdout bool
- base64responseInStdout bool
+ ResponseHeadersInStdout bool
+ ResponseInStdout bool
+ Base64ResponseInStdout bool
chainInStdout bool
FollowHostRedirects bool
MaxRedirects int
@@ -215,10 +231,12 @@ type Options struct {
StatsInterval int
RandomAgent bool
StoreChain bool
+ StoreVisionReconClusters bool
Deny customlist.CustomList
Allow customlist.CustomList
MaxResponseBodySizeToSave int
MaxResponseBodySizeToRead int
+ ResponseBodyPreviewSize int
OutputExtractRegexs goflags.StringSlice
OutputExtractPresets goflags.StringSlice
RateLimit int
@@ -226,7 +244,7 @@ type Options struct {
Probe bool
Resume bool
resumeCfg *ResumeCfg
- ExcludeCDN bool
+ Exclude goflags.StringSlice
HostMaxErrors int
Stream bool
SkipDedupe bool
@@ -260,13 +278,26 @@ type Options struct {
ListDSLVariable bool
OutputFilterCondition string
OutputMatchCondition string
- OnResult OnResultCallback
- DisableUpdateCheck bool
+ StripFilter string
+ //The OnResult callback function is invoked for each result. It is important to check for errors in the result before using Result.Err.
+ OnResult OnResultCallback
+ DisableUpdateCheck bool
+ NoDecode bool
+ Screenshot bool
+ UseInstalledChrome bool
+ TlsImpersonate bool
+ DisableStdin bool
+ NoScreenshotBytes bool
+ NoHeadlessBody bool
+ ScreenshotTimeout int
+ // HeadlessOptionalArguments specifies optional arguments to pass to Chrome
+ HeadlessOptionalArguments goflags.StringSlice
}
// ParseOptions parses the command line options for application
func ParseOptions() *Options {
options := &Options{}
+ var cfgFile string
flagSet := goflags.NewFlagSet()
flagSet.SetDescription(`httpx is a fast and multi-purpose HTTP toolkit that allows running multiple probes using the retryablehttp library.`)
@@ -289,6 +320,7 @@ func ParseOptions() *Options {
flagSet.BoolVarP(&options.OutputLinesCount, "line-count", "lc", false, "display response body line count"),
flagSet.BoolVarP(&options.OutputWordsCount, "word-count", "wc", false, "display response body word count"),
flagSet.BoolVar(&options.ExtractTitle, "title", false, "display page title"),
+ flagSet.DynamicVarP(&options.ResponseBodyPreviewSize, "body-preview", "bp", 100, "display first N characters of response body"),
flagSet.BoolVarP(&options.OutputServerHeader, "web-server", "server", false, "display server name"),
flagSet.BoolVarP(&options.TechDetect, "tech-detect", "td", false, "display technology in use based on wappalyzer dataset"),
flagSet.BoolVar(&options.OutputMethod, "method", false, "display http request method"),
@@ -296,10 +328,19 @@ func ParseOptions() *Options {
flagSet.BoolVar(&options.OutputIP, "ip", false, "display host ip"),
flagSet.BoolVar(&options.OutputCName, "cname", false, "display host cname"),
flagSet.BoolVar(&options.Asn, "asn", false, "display host asn information"),
- flagSet.BoolVar(&options.OutputCDN, "cdn", false, "display cdn in use"),
+ flagSet.BoolVar(&options.OutputCDN, "cdn", false, "display cdn/waf in use"),
flagSet.BoolVar(&options.Probe, "probe", false, "display probe status"),
)
+ flagSet.CreateGroup("headless", "Headless",
+ flagSet.BoolVarP(&options.Screenshot, "screenshot", "ss", false, "enable saving screenshot of the page using headless browser"),
+ flagSet.BoolVar(&options.UseInstalledChrome, "system-chrome", false, "enable using local installed chrome for screenshot"),
+ flagSet.StringSliceVarP(&options.HeadlessOptionalArguments, "headless-options", "ho", nil, "start headless chrome with additional options", goflags.FileCommaSeparatedStringSliceOptions),
+ flagSet.BoolVarP(&options.NoScreenshotBytes, "exclude-screenshot-bytes", "esb", false, "enable excluding screenshot bytes from json output"),
+ flagSet.BoolVarP(&options.NoHeadlessBody, "exclude-headless-body", "ehb", false, "enable excluding headless header from json output"),
+ flagSet.IntVarP(&options.ScreenshotTimeout, "screenshot-timeout", "st", 10, "set timeout for screenshot in seconds"),
+ )
+
flagSet.CreateGroup("matchers", "Matchers",
flagSet.StringVarP(&options.OutputMatchStatusCode, "match-code", "mc", "", "match response with specified status code (-mc 200,302)"),
flagSet.StringVarP(&options.OutputMatchContentLength, "match-length", "ml", "", "match response with specified content length (-ml 100,102)"),
@@ -308,7 +349,7 @@ func ParseOptions() *Options {
flagSet.StringSliceVarP(&options.OutputMatchFavicon, "match-favicon", "mfc", nil, "match response with specified favicon hash (-mfc 1494302000)", goflags.NormalizedStringSliceOptions),
flagSet.StringVarP(&options.OutputMatchString, "match-string", "ms", "", "match response with specified string (-ms admin)"),
flagSet.StringVarP(&options.OutputMatchRegex, "match-regex", "mr", "", "match response with specified regex (-mr admin)"),
- flagSet.StringSliceVarP(&options.OutputMatchCdn, "match-cdn", "mcdn", nil, fmt.Sprintf("match host with specified cdn provider (%s)", defaultProviders), goflags.NormalizedStringSliceOptions),
+ flagSet.StringSliceVarP(&options.OutputMatchCdn, "match-cdn", "mcdn", nil, fmt.Sprintf("match host with specified cdn provider (%s)", cdncheck.DefaultCDNProviders), goflags.NormalizedStringSliceOptions),
flagSet.StringVarP(&options.OutputMatchResponseTime, "match-response-time", "mrt", "", "match response with specified response time in seconds (-mrt '< 1')"),
flagSet.StringVarP(&options.OutputMatchCondition, "match-condition", "mdc", "", "match response with dsl expression condition"),
)
@@ -320,15 +361,17 @@ func ParseOptions() *Options {
flagSet.CreateGroup("filters", "Filters",
flagSet.StringVarP(&options.OutputFilterStatusCode, "filter-code", "fc", "", "filter response with specified status code (-fc 403,401)"),
+ flagSet.BoolVarP(&options.OutputFilterErrorPage, "filter-error-page", "fep", false, "filter response with ML based error page detection"),
flagSet.StringVarP(&options.OutputFilterContentLength, "filter-length", "fl", "", "filter response with specified content length (-fl 23,33)"),
flagSet.StringVarP(&options.OutputFilterLinesCount, "filter-line-count", "flc", "", "filter response body with specified line count (-flc 423,532)"),
flagSet.StringVarP(&options.OutputFilterWordsCount, "filter-word-count", "fwc", "", "filter response body with specified word count (-fwc 423,532)"),
- flagSet.StringSliceVarP(&options.OutputFilterFavicon, "filter-favicon", "ffc", nil, "filter response with specified favicon hash (-mfc 1494302000)", goflags.NormalizedStringSliceOptions),
+ flagSet.StringSliceVarP(&options.OutputFilterFavicon, "filter-favicon", "ffc", nil, "filter response with specified favicon hash (-ffc 1494302000)", goflags.NormalizedStringSliceOptions),
flagSet.StringVarP(&options.OutputFilterString, "filter-string", "fs", "", "filter response with specified string (-fs admin)"),
flagSet.StringVarP(&options.OutputFilterRegex, "filter-regex", "fe", "", "filter response with specified regex (-fe admin)"),
- flagSet.StringSliceVarP(&options.OutputFilterCdn, "filter-cdn", "fcdn", nil, fmt.Sprintf("filter host with specified cdn provider (%s)", defaultProviders), goflags.NormalizedStringSliceOptions),
+ flagSet.StringSliceVarP(&options.OutputFilterCdn, "filter-cdn", "fcdn", nil, fmt.Sprintf("filter host with specified cdn provider (%s)", cdncheck.DefaultCDNProviders), goflags.NormalizedStringSliceOptions),
flagSet.StringVarP(&options.OutputFilterResponseTime, "filter-response-time", "frt", "", "filter response with specified response time in seconds (-frt '> 1')"),
flagSet.StringVarP(&options.OutputFilterCondition, "filter-condition", "fdc", "", "filter response with dsl expression condition"),
+ flagSet.DynamicVar(&options.StripFilter, "strip", "html", "strips all tags in response. supported formats: html,xml"),
)
flagSet.CreateGroup("rate-limit", "Rate-Limit",
@@ -357,18 +400,22 @@ func ParseOptions() *Options {
flagSet.CreateGroup("output", "Output",
flagSet.StringVarP(&options.Output, "output", "o", "", "file to write output results"),
+ flagSet.BoolVarP(&options.OutputAll, "output-all", "oa", false, "filename to write output results in all formats"),
flagSet.BoolVarP(&options.StoreResponse, "store-response", "sr", false, "store http response to output directory"),
flagSet.StringVarP(&options.StoreResponseDir, "store-response-dir", "srd", "", "store http response to custom directory"),
flagSet.BoolVar(&options.CSVOutput, "csv", false, "store output in csv format"),
flagSet.StringVarP(&options.CSVOutputEncoding, "csv-output-encoding", "csvo", "", "define output encoding"),
- flagSet.BoolVar(&options.JSONOutput, "json", false, "store output in JSONL(ines) format"),
- flagSet.BoolVarP(&options.responseInStdout, "include-response", "irr", false, "include http request/response in JSON output (-json only)"),
- flagSet.BoolVarP(&options.base64responseInStdout, "include-response-base64", "irrb", false, "include base64 encoded http request/response in JSON output (-json only)"),
+ flagSet.BoolVarP(&options.JSONOutput, "json", "j", false, "store output in JSONL(ines) format"),
+ flagSet.BoolVarP(&options.ResponseHeadersInStdout, "include-response-header", "irh", false, "include http response (headers) in JSON output (-json only)"),
+ flagSet.BoolVarP(&options.ResponseInStdout, "include-response", "irr", false, "include http request/response (headers + body) in JSON output (-json only)"),
+ flagSet.BoolVarP(&options.Base64ResponseInStdout, "include-response-base64", "irrb", false, "include base64 encoded http request/response in JSON output (-json only)"),
flagSet.BoolVar(&options.chainInStdout, "include-chain", false, "include redirect http chain in JSON output (-json only)"),
flagSet.BoolVar(&options.StoreChain, "store-chain", false, "include http redirect chain in responses (-sr only)"),
+ flagSet.BoolVarP(&options.StoreVisionReconClusters, "store-vision-recon-cluster", "svrc", false, "include visual recon clusters (-ss and -sr only)"),
)
flagSet.CreateGroup("configs", "Configurations",
+ flagSet.StringVar(&cfgFile, "config", "", "path to the httpx configuration file (default $HOME/.config/httpx/config.yaml)"),
flagSet.StringSliceVarP(&options.Resolvers, "resolvers", "r", nil, "list of custom resolver (file or comma separated)", goflags.NormalizedStringSliceOptions),
flagSet.Var(&options.Allow, "allow", "allowed list of IP/CIDR's to process (file or comma separated)"),
flagSet.Var(&options.Deny, "deny", "denied list of IP/CIDR's to process (file or comma separated)"),
@@ -381,13 +428,17 @@ func ParseOptions() *Options {
flagSet.BoolVarP(&options.FollowRedirects, "follow-redirects", "fr", false, "follow http redirects"),
flagSet.IntVarP(&options.MaxRedirects, "max-redirects", "maxr", 10, "max number of redirects to follow per host"),
flagSet.BoolVarP(&options.FollowHostRedirects, "follow-host-redirects", "fhr", false, "follow redirects on the same host"),
+ flagSet.BoolVarP(&options.RespectHSTS, "respect-hsts", "rhsts", false, "respect HSTS response headers for redirect requests"),
flagSet.BoolVar(&options.VHostInput, "vhost-input", false, "get a list of vhosts as input"),
flagSet.StringVar(&options.Methods, "x", "", "request methods to probe, use 'all' to probe all HTTP methods"),
flagSet.StringVar(&options.RequestBody, "body", "", "post body to include in http request"),
flagSet.BoolVarP(&options.Stream, "stream", "s", false, "stream mode - start elaborating input targets without sorting"),
flagSet.BoolVarP(&options.SkipDedupe, "skip-dedupe", "sd", false, "disable dedupe input items (only used with stream mode)"),
- flagSet.BoolVarP(&options.LeaveDefaultPorts, "leave-default-ports", "ldp", false, "leave default http/https ports in host header (eg. http://host:80 - https//host:443"),
+ flagSet.BoolVarP(&options.LeaveDefaultPorts, "leave-default-ports", "ldp", false, "leave default http/https ports in host header (eg. http://host:80 - https://host:443"),
flagSet.BoolVar(&options.ZTLS, "ztls", false, "use ztls library with autofallback to standard one for tls13"),
+ flagSet.BoolVar(&options.NoDecode, "no-decode", false, "avoid decoding body"),
+ flagSet.BoolVarP(&options.TlsImpersonate, "tls-impersonate", "tlsi", false, "enable experimental client hello (ja3) tls randomization"),
+ flagSet.BoolVar(&options.DisableStdin, "no-stdin", false, "Disable Stdin processing"),
)
flagSet.CreateGroup("debug", "Debug",
@@ -408,9 +459,9 @@ func ParseOptions() *Options {
flagSet.BoolVarP(&options.NoFallback, "no-fallback", "nf", false, "display both probed protocol (HTTPS and HTTP)"),
flagSet.BoolVarP(&options.NoFallbackScheme, "no-fallback-scheme", "nfs", false, "probe with protocol scheme specified in input "),
flagSet.IntVarP(&options.HostMaxErrors, "max-host-error", "maxhr", 30, "max error count per host before skipping remaining path/s"),
- flagSet.BoolVarP(&options.ExcludeCDN, "exclude-cdn", "ec", false, "skip full port scans for CDNs (only checks for 80,443)"),
+ flagSet.StringSliceVarP(&options.Exclude, "exclude", "e", nil, "exclude host matching specified filter ('cdn', 'private-ips', cidr, ip, regex)", goflags.CommaSeparatedStringSliceOptions),
flagSet.IntVar(&options.Retries, "retries", 0, "number of retries"),
- flagSet.IntVar(&options.Timeout, "timeout", 5, "timeout in seconds"),
+ flagSet.IntVar(&options.Timeout, "timeout", 10, "timeout in seconds"),
flagSet.DurationVar(&options.Delay, "delay", -1, "duration between each http request (eg: 200ms, 1s)"),
flagSet.IntVarP(&options.MaxResponseBodySizeToSave, "response-size-to-save", "rsts", math.MaxInt32, "max response size to save in bytes"),
flagSet.IntVarP(&options.MaxResponseBodySizeToRead, "response-size-to-read", "rstr", math.MaxInt32, "max response size to read in bytes"),
@@ -418,6 +469,25 @@ func ParseOptions() *Options {
_ = flagSet.Parse()
+ if options.OutputAll && options.Output == "" {
+ gologger.Fatal().Msg("Please specify an output file using -o/-output when using -oa/-output-all")
+ }
+
+ if options.OutputAll {
+ options.JSONOutput = true
+ options.CSVOutput = true
+ }
+
+ if cfgFile != "" {
+ if !fileutil.FileExists(cfgFile) {
+ gologger.Fatal().Msgf("given config file '%s' does not exist", cfgFile)
+ }
+ // merge config file with flags
+ if err := flagSet.MergeConfigFile(cfgFile); err != nil {
+ gologger.Fatal().Msgf("Could not read config: %s\n", err)
+ }
+ }
+
if options.HealthCheck {
gologger.Print().Msgf("%s\n", DoHealthCheck(options, flagSet))
os.Exit(0)
@@ -427,6 +497,10 @@ func ParseOptions() *Options {
options.ShowStatistics = true
}
+ if options.ResponseBodyPreviewSize > 0 && options.StripFilter == "" {
+ options.StripFilter = "html"
+ }
+
// Read the inputs and configure the logging
options.configureOutput()
@@ -452,7 +526,7 @@ func ParseOptions() *Options {
}
if !options.DisableUpdateCheck {
- latestVersion, err := updateutils.GetVersionCheckCallback("httpx")()
+ latestVersion, err := updateutils.GetToolVersionCallback("httpx", version)()
if err != nil {
if options.Verbose {
gologger.Error().Msgf("httpx version check failed: %v", err.Error())
@@ -478,11 +552,6 @@ func (options *Options) ValidateOptions() error {
return fmt.Errorf("file '%s' does not exist", options.InputRawRequest)
}
- multiOutput := options.CSVOutput && options.JSONOutput
- if multiOutput {
- return fmt.Errorf("results can only be displayed in one format: 'JSON' or 'CSV'")
- }
-
if options.Silent {
incompatibleFlagsList := flagsIncompatibleWithSilent(options)
if len(incompatibleFlagsList) > 0 {
@@ -555,6 +624,10 @@ func (options *Options) ValidateOptions() error {
gologger.Debug().Msgf("Using resolvers: %s\n", strings.Join(options.Resolvers, ","))
}
+ if options.Screenshot && !options.StoreResponse {
+ gologger.Debug().Msgf("automatically enabling store response")
+ options.StoreResponse = true
+ }
if options.StoreResponse && options.StoreResponseDir == "" {
gologger.Debug().Msgf("Store response directory not specified, using \"%s\"\n", DefaultOutputDirectory)
options.StoreResponseDir = DefaultOutputDirectory
@@ -563,6 +636,7 @@ func (options *Options) ValidateOptions() error {
gologger.Debug().Msgf("Store response directory specified, enabling \"sr\" flag automatically\n")
options.StoreResponse = true
}
+
if options.Hashes != "" {
for _, hashType := range strings.Split(options.Hashes, ",") {
if !slice.StringSliceContains([]string{"md5", "sha1", "sha256", "sha512", "mmh3", "simhash"}, strings.ToLower(hashType)) {
@@ -577,6 +651,32 @@ func (options *Options) ValidateOptions() error {
return nil
}
+// redundant with katana
+func (options *Options) ParseHeadlessOptionalArguments() map[string]string {
+ var (
+ lastKey string
+ optionalArguments = make(map[string]string)
+ )
+ for _, v := range options.HeadlessOptionalArguments {
+ if v == "" {
+ continue
+ }
+ if argParts := strings.SplitN(v, "=", 2); len(argParts) >= 2 {
+ key := strings.TrimSpace(argParts[0])
+ value := strings.TrimSpace(argParts[1])
+ if key != "" && value != "" {
+ optionalArguments[key] = value
+ lastKey = key
+ }
+ } else if !strings.HasPrefix(v, "--") {
+ optionalArguments[lastKey] += "," + v
+ } else {
+ optionalArguments[v] = ""
+ }
+ }
+ return optionalArguments
+}
+
// configureOutput configures the output on the screen
func (options *Options) configureOutput() {
// If the user desires verbose output, show verbose output
diff --git a/runner/runner.go b/runner/runner.go
index 0736cd6..4438008 100644
--- a/runner/runner.go
+++ b/runner/runner.go
@@ -7,6 +7,8 @@ import (
"encoding/csv"
"encoding/json"
"fmt"
+ "html/template"
+ "image"
"io"
"net"
"net/http"
@@ -24,16 +26,19 @@ import (
"golang.org/x/exp/maps"
"github.com/PuerkitoBio/goquery"
+ "github.com/corona10/goimagehash"
asnmap "github.com/projectdiscovery/asnmap/libs"
- dsl "github.com/projectdiscovery/dsl"
"github.com/projectdiscovery/fastdialer/fastdialer"
"github.com/projectdiscovery/httpx/common/customextract"
+ "github.com/projectdiscovery/httpx/common/errorpageclassifier"
"github.com/projectdiscovery/httpx/common/hashes/jarm"
+ "github.com/projectdiscovery/httpx/static"
"github.com/projectdiscovery/mapcidr/asn"
+ "github.com/projectdiscovery/networkpolicy"
errorutil "github.com/projectdiscovery/utils/errors"
- mapsutil "github.com/projectdiscovery/utils/maps"
+ osutil "github.com/projectdiscovery/utils/os"
- "github.com/bluele/gcache"
+ "github.com/Mzack9999/gcache"
"github.com/logrusorgru/aurora"
"github.com/pkg/errors"
@@ -68,15 +73,31 @@ import (
// Runner is a client for running the enumeration process.
type Runner struct {
- options *Options
- hp *httpx.HTTPX
- wappalyzer *wappalyzer.Wappalyze
- fastdialer *fastdialer.Dialer
- scanopts scanOptions
- hm *hybrid.HybridMap
- stats clistats.StatisticsClient
- ratelimiter ratelimit.Limiter
- HostErrorsCache gcache.Cache
+ options *Options
+ hp *httpx.HTTPX
+ wappalyzer *wappalyzer.Wappalyze
+ scanopts ScanOptions
+ hm *hybrid.HybridMap
+ excludePorts map[string]struct{}
+ excludeCdn bool
+ stats clistats.StatisticsClient
+ ratelimiter ratelimit.Limiter
+ HostErrorsCache gcache.Cache[string, int]
+ browser *Browser
+ errorPageClassifier *errorpageclassifier.ErrorPageClassifier
+ pHashClusters []pHashCluster
+}
+
+// picked based on try-fail but it seems to close to one it's used https://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html#c1992
+var hammingDistanceThreshold int = 22
+
+type pHashCluster struct {
+ BasePHash uint64 `json:"base_phash,omitempty" csv:"base_phash"`
+ Hashes []pHashUrl `json:"hashes,omitempty" csv:"hashes"`
+}
+type pHashUrl struct {
+ PHash uint64 `json:"phash,omitempty" csv:"phash"`
+ Url string `json:"url,omitempty" csv:"url"`
}
// New creates a new client for running enumeration process.
@@ -92,20 +113,31 @@ func New(options *Options) (*Runner, error) {
return nil, errors.Wrap(err, "could not create wappalyzer client")
}
if options.StoreResponseDir != "" {
- os.RemoveAll(filepath.Join(options.StoreResponseDir, "index.txt"))
+ os.RemoveAll(filepath.Join(options.StoreResponseDir, "response", "index.txt"))
+ os.RemoveAll(filepath.Join(options.StoreResponseDir, "screenshot", "index_screenshot.txt"))
}
- dialerOpts := fastdialer.DefaultOptions
- dialerOpts.WithDialerHistory = true
- dialerOpts.MaxRetries = 3
- dialerOpts.DialerTimeout = time.Duration(options.Timeout) * time.Second
- if len(options.Resolvers) > 0 {
- dialerOpts.BaseResolvers = options.Resolvers
- }
- fastDialer, err := fastdialer.NewDialer(dialerOpts)
- if err != nil {
- return nil, errors.Wrap(err, "could not create dialer")
+
+ runner.excludePorts = make(map[string]struct{})
+ for _, exclude := range options.Exclude {
+ switch {
+ case exclude == "cdn":
+ runner.excludeCdn = true
+ case exclude == "private-ips":
+ options.Deny = append(options.Deny, networkpolicy.DefaultIPv4Denylist...)
+ options.Deny = append(options.Deny, networkpolicy.DefaultIPv4DenylistRanges...)
+ options.Deny = append(options.Deny, networkpolicy.DefaultIPv6Denylist...)
+ options.Deny = append(options.Deny, networkpolicy.DefaultIPv6DenylistRanges...)
+ case iputil.IsCIDR(exclude):
+ options.Deny = append(options.Deny, exclude)
+ case asn.IsASN(exclude):
+ ips := expandASNInputValue(exclude)
+ options.Deny = append(options.Deny, ips...)
+ case iputil.IsPort(exclude):
+ runner.excludePorts[exclude] = struct{}{}
+ default:
+ options.Deny = append(options.Deny, exclude)
+ }
}
- runner.fastdialer = fastDialer
httpxOptions := httpx.DefaultOptions
// Enables automatically tlsgrab if tlsprobe is requested
@@ -114,12 +146,13 @@ func New(options *Options) (*Runner, error) {
httpxOptions.RetryMax = options.Retries
httpxOptions.FollowRedirects = options.FollowRedirects
httpxOptions.FollowHostRedirects = options.FollowHostRedirects
+ httpxOptions.RespectHSTS = options.RespectHSTS
httpxOptions.MaxRedirects = options.MaxRedirects
httpxOptions.HTTPProxy = options.HTTPProxy
httpxOptions.Unsafe = options.Unsafe
httpxOptions.UnsafeURI = options.RequestURI
httpxOptions.CdnCheck = options.OutputCDN
- httpxOptions.ExcludeCdn = options.ExcludeCDN
+ httpxOptions.ExcludeCdn = runner.excludeCdn
if options.CustomHeaders.Has("User-Agent:") {
httpxOptions.RandomAgent = false
} else {
@@ -135,6 +168,7 @@ func New(options *Options) (*Runner, error) {
httpxOptions.MaxResponseBodySizeToSave = httpxOptions.MaxResponseBodySizeToRead
}
httpxOptions.Resolvers = options.Resolvers
+ httpxOptions.TlsImpersonate = options.TlsImpersonate
var key, value string
httpxOptions.CustomHeaders = make(map[string]string)
@@ -161,7 +195,7 @@ func New(options *Options) (*Runner, error) {
gologger.Fatal().Msgf("Could not create httpx instance: %s\n", err)
}
- var scanopts scanOptions
+ var scanopts ScanOptions
if options.InputRawRequest != "" {
var rawRequest []byte
@@ -215,9 +249,10 @@ func New(options *Options) (*Runner, error) {
scanopts.StoreResponse = options.StoreResponse
scanopts.StoreResponseDirectory = options.StoreResponseDir
scanopts.OutputServerHeader = options.OutputServerHeader
+ scanopts.ResponseHeadersInStdout = options.ResponseHeadersInStdout
scanopts.OutputWithNoColor = options.NoColor
- scanopts.ResponseInStdout = options.responseInStdout
- scanopts.Base64ResponseInStdout = options.base64responseInStdout
+ scanopts.ResponseInStdout = options.ResponseInStdout
+ scanopts.Base64ResponseInStdout = options.Base64ResponseInStdout
scanopts.ChainInStdout = options.chainInStdout
scanopts.OutputWebSocket = options.OutputWebSocket
scanopts.TLSProbe = options.TLSProbe
@@ -240,9 +275,22 @@ func New(options *Options) (*Runner, error) {
scanopts.NoFallbackScheme = options.NoFallbackScheme
scanopts.TechDetect = options.TechDetect
scanopts.StoreChain = options.StoreChain
+ scanopts.StoreVisionReconClusters = options.StoreVisionReconClusters
scanopts.MaxResponseBodySizeToSave = options.MaxResponseBodySizeToSave
scanopts.MaxResponseBodySizeToRead = options.MaxResponseBodySizeToRead
scanopts.extractRegexps = make(map[string]*regexp.Regexp)
+ if options.Screenshot {
+ browser, err := NewBrowser(options.HTTPProxy, options.UseInstalledChrome, options.ParseHeadlessOptionalArguments())
+ if err != nil {
+ return nil, err
+ }
+ runner.browser = browser
+ }
+ scanopts.Screenshot = options.Screenshot
+ scanopts.NoScreenshotBytes = options.NoScreenshotBytes
+ scanopts.NoHeadlessBody = options.NoHeadlessBody
+ scanopts.UseInstalledChrome = options.UseInstalledChrome
+ scanopts.ScreenshotTimeout = options.ScreenshotTimeout
if options.OutputExtractRegexs != nil {
for _, regex := range options.OutputExtractRegexs {
@@ -270,7 +318,7 @@ func New(options *Options) (*Runner, error) {
scanopts.OutputMethod = true
}
- scanopts.ExcludeCDN = options.ExcludeCDN
+ scanopts.ExcludeCDN = runner.excludeCdn
scanopts.HostMaxErrors = options.HostMaxErrors
scanopts.ProbeAllIPS = options.ProbeAllIPS
scanopts.Favicon = options.Favicon
@@ -290,8 +338,6 @@ func New(options *Options) (*Runner, error) {
}
}
- hmapOptions := hybrid.DefaultDiskOptions
- hmapOptions.DBType = hybrid.PogrebDB
hm, err := hybrid.New(hybrid.DefaultDiskOptions)
if err != nil {
return nil, err
@@ -307,15 +353,35 @@ func New(options *Options) (*Runner, error) {
}
if options.HostMaxErrors >= 0 {
- gc := gcache.New(1000).
+ gc := gcache.New[string, int](1000).
ARC().
Build()
runner.HostErrorsCache = gc
}
+ runner.errorPageClassifier = errorpageclassifier.New()
+
return runner, nil
}
+func expandCIDRInputValue(value string) []string {
+ var ips []string
+ ipsCh, _ := mapcidr.IPAddressesAsStream(value)
+ for ip := range ipsCh {
+ ips = append(ips, ip)
+ }
+ return ips
+}
+
+func expandASNInputValue(value string) []string {
+ var ips []string
+ cidrs, _ := asn.GetCIDRsForASNNum(value)
+ for _, cidr := range cidrs {
+ ips = append(ips, expandCIDRInputValue(cidr.String())...)
+ }
+ return ips
+}
+
func (r *Runner) prepareInputPaths() {
// most likely, the user would provide the most simplified path to an existing file
isAbsoluteOrRelativePath := filepath.Clean(r.options.RequestURIs) == r.options.RequestURIs
@@ -366,7 +432,7 @@ func (r *Runner) prepareInput() {
numHosts += numTargetsFile
}
}
- if fileutil.HasStdin() {
+ if !r.options.DisableStdin && fileutil.HasStdin() {
numTargetsStdin, err := r.loadAndCloseFile(os.Stdin)
if err != nil {
gologger.Fatal().Msgf("Could not read input from stdin: %s\n", err)
@@ -379,11 +445,18 @@ func (r *Runner) prepareInput() {
r.stats.AddCounter("hosts", 0)
r.stats.AddStatic("startedAt", time.Now())
r.stats.AddCounter("requests", 0)
-
- err := r.stats.Start(makePrintCallback(), time.Duration(r.options.StatsInterval)*time.Second)
+ r.stats.AddDynamic("summary", makePrintCallback())
+ err := r.stats.Start()
if err != nil {
gologger.Warning().Msgf("Could not create statistics: %s\n", err)
}
+
+ r.stats.GetStatResponse(time.Duration(r.options.StatsInterval)*time.Second, func(s string, err error) error {
+ if err != nil && r.options.Verbose {
+ gologger.Error().Msgf("Could not read statistics: %s\n", err)
+ }
+ return nil
+ })
}
}
@@ -502,9 +575,9 @@ var (
lastRequestsCount float64
)
-func makePrintCallback() func(stats clistats.StatisticsClient) {
+func makePrintCallback() func(stats clistats.StatisticsClient) interface{} {
builder := &strings.Builder{}
- return func(stats clistats.StatisticsClient) {
+ return func(stats clistats.StatisticsClient) interface{} {
startedAt, _ := stats.GetStatic("startedAt")
duration := time.Since(startedAt.(time.Time))
@@ -539,11 +612,12 @@ func makePrintCallback() func(stats clistats.StatisticsClient) {
builder.WriteRune(')')
builder.WriteRune('\n')
-
- fmt.Fprintf(os.Stderr, "%s", builder.String())
+ statString := builder.String()
+ fmt.Fprintf(os.Stderr, "%s", statString)
builder.Reset()
lastRequestsCount = currentRequests
+ return statString
}
}
@@ -552,18 +626,36 @@ func (r *Runner) Close() {
// nolint:errcheck // ignore
r.hm.Close()
r.hp.Dialer.Close()
+ r.ratelimiter.Stop()
if r.options.HostMaxErrors >= 0 {
r.HostErrorsCache.Purge()
}
+ if r.options.Screenshot {
+ r.browser.Close()
+ }
}
// RunEnumeration on targets for httpx client
func (r *Runner) RunEnumeration() {
- // Try to create output folder if it doesn't exist
+ // Try to create output folders if it doesn't exist
if r.options.StoreResponse && !fileutil.FolderExists(r.options.StoreResponseDir) {
+ // main folder
if err := os.MkdirAll(r.options.StoreResponseDir, os.ModePerm); err != nil {
gologger.Fatal().Msgf("Could not create output directory '%s': %s\n", r.options.StoreResponseDir, err)
}
+ // response folder
+ responseFolder := filepath.Join(r.options.StoreResponseDir, "response")
+ if err := os.MkdirAll(responseFolder, os.ModePerm); err != nil {
+ gologger.Fatal().Msgf("Could not create output response directory '%s': %s\n", r.options.StoreResponseDir, err)
+ }
+ }
+
+ // screenshot folder
+ if r.options.Screenshot {
+ screenshotFolder := filepath.Join(r.options.StoreResponseDir, "screenshot")
+ if err := os.MkdirAll(screenshotFolder, os.ModePerm); err != nil {
+ gologger.Fatal().Msgf("Could not create output screenshot directory '%s': %s\n", r.options.StoreResponseDir, err)
+ }
}
r.prepareInputPaths()
@@ -585,49 +677,87 @@ func (r *Runner) RunEnumeration() {
}
// output routine
- wgoutput := sizedwaitgroup.New(1)
+ wgoutput := sizedwaitgroup.New(2)
wgoutput.Add()
+
output := make(chan Result)
- go func(output chan Result) {
+ nextStep := make(chan Result)
+
+ go func(output chan Result, nextSteps ...chan Result) {
defer wgoutput.Done()
- var f, indexFile *os.File
+ defer func() {
+ for _, nextStep := range nextSteps {
+ close(nextStep)
+ }
+ }()
- if r.options.Output != "" {
- var err error
- if r.options.Resume {
- f, err = os.OpenFile(r.options.Output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
- } else {
- f, err = os.Create(r.options.Output)
+ var plainFile, jsonFile, csvFile, indexFile, indexScreenshotFile *os.File
+
+ if r.options.Output != "" && r.options.OutputAll {
+ plainFile = openOrCreateFile(r.options.Resume, r.options.Output)
+ defer plainFile.Close()
+ jsonFile = openOrCreateFile(r.options.Resume, r.options.Output+".json")
+ defer jsonFile.Close()
+ csvFile = openOrCreateFile(r.options.Resume, r.options.Output+".csv")
+ defer csvFile.Close()
+ }
+
+ jsonOrCsv := (r.options.JSONOutput || r.options.CSVOutput)
+ jsonAndCsv := (r.options.JSONOutput && r.options.CSVOutput)
+ if r.options.Output != "" && plainFile == nil && !jsonOrCsv {
+ plainFile = openOrCreateFile(r.options.Resume, r.options.Output)
+ defer plainFile.Close()
+ }
+
+ if r.options.Output != "" && r.options.JSONOutput && jsonFile == nil {
+ ext := ""
+ if jsonAndCsv {
+ ext = ".json"
}
- if err != nil {
- gologger.Fatal().Msgf("Could not open/create output file '%s': %s\n", r.options.Output, err)
+ jsonFile = openOrCreateFile(r.options.Resume, r.options.Output+ext)
+ defer jsonFile.Close()
+ }
+
+ if r.options.Output != "" && r.options.CSVOutput && csvFile == nil {
+ ext := ""
+ if jsonAndCsv {
+ ext = ".csv"
}
- defer f.Close() //nolint
+ csvFile = openOrCreateFile(r.options.Resume, r.options.Output+ext)
+ defer csvFile.Close()
}
+
if r.options.CSVOutput {
outEncoding := strings.ToLower(r.options.CSVOutputEncoding)
switch outEncoding {
case "": // no encoding do nothing
case "utf-8", "utf8":
bomUtf8 := []byte{0xEF, 0xBB, 0xBF}
- _, err := f.Write(bomUtf8)
+ _, err := csvFile.Write(bomUtf8)
if err != nil {
gologger.Fatal().Msgf("err on file write: %s\n", err)
}
default: // unknown encoding
gologger.Fatal().Msgf("unknown csv output encoding: %s\n", r.options.CSVOutputEncoding)
}
- header := Result{}.CSVHeader()
- gologger.Silent().Msgf("%s\n", header)
- if f != nil {
+ headers := Result{}.CSVHeader()
+ if !r.options.OutputAll && !jsonAndCsv {
+ gologger.Silent().Msgf("%s\n", headers)
+ }
+
+ if csvFile != nil {
//nolint:errcheck // this method needs a small refactor to reduce complexity
- f.WriteString(header + "\n")
+ csvFile.WriteString(headers + "\n")
}
}
if r.options.StoreResponseDir != "" {
var err error
- indexPath := filepath.Join(r.options.StoreResponseDir, "index.txt")
+ responseDirPath := filepath.Join(r.options.StoreResponseDir, "response")
+ if err := os.MkdirAll(responseDirPath, 0755); err != nil {
+ gologger.Fatal().Msgf("Could not create response directory '%s': %s\n", responseDirPath, err)
+ }
+ indexPath := filepath.Join(responseDirPath, "index.txt")
if r.options.Resume {
indexFile, err = os.OpenFile(indexPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
} else {
@@ -638,14 +768,37 @@ func (r *Runner) RunEnumeration() {
}
defer indexFile.Close() //nolint
}
+ if r.options.Screenshot {
+ var err error
+ indexScreenshotPath := filepath.Join(r.options.StoreResponseDir, "screenshot", "index_screenshot.txt")
+ if r.options.Resume {
+ indexScreenshotFile, err = os.OpenFile(indexScreenshotPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
+ } else {
+ indexScreenshotFile, err = os.Create(indexScreenshotPath)
+ }
+ if err != nil {
+ gologger.Fatal().Msgf("Could not open/create index screenshot file '%s': %s\n", r.options.Output, err)
+ }
+ defer indexScreenshotFile.Close() //nolint
+ }
for resp := range output {
- if resp.err != nil {
+
+ if r.options.SniName != "" {
+ resp.SNI = r.options.SniName
+ }
+ // call the callback function if any
+ // be careful and check for result.Err
+ if r.options.OnResult != nil {
+ r.options.OnResult(resp)
+ }
+
+ if resp.Err != nil {
// Change the error message if any port value passed explicitly
if url, err := r.parseURL(resp.URL); err == nil && url.Port() != "" {
- resp.err = errors.New(strings.ReplaceAll(resp.err.Error(), "address", "port"))
+ resp.Err = errors.New(strings.ReplaceAll(resp.Err.Error(), "address", "port"))
}
- gologger.Debug().Msgf("Failed '%s': %s\n", resp.URL, resp.err)
+ gologger.Debug().Msgf("Failed '%s': %s\n", resp.URL, resp.Err)
}
if resp.str == "" {
continue
@@ -655,44 +808,31 @@ func (r *Runner) RunEnumeration() {
indexData := fmt.Sprintf("%s %s (%d %s)\n", resp.StoredResponsePath, resp.URL, resp.StatusCode, http.StatusText(resp.StatusCode))
_, _ = indexFile.WriteString(indexData)
}
+ if indexScreenshotFile != nil && resp.ScreenshotPathRel != "" {
+ indexData := fmt.Sprintf("%s %s (%d %s)\n", resp.ScreenshotPathRel, resp.URL, resp.StatusCode, http.StatusText(resp.StatusCode))
+ _, _ = indexScreenshotFile.WriteString(indexData)
+ }
// apply matchers and filters
if r.options.OutputFilterCondition != "" || r.options.OutputMatchCondition != "" {
- rawMap, err := ResultToMap(resp)
- if err != nil {
- gologger.Warning().Msgf("Could not decode response: %s\n", err)
- continue
- }
-
- flatMap := make(map[string]any)
- mapsutil.Walk(rawMap, func(k string, v any) {
- flatMap[k] = v
- })
-
if r.options.OutputMatchCondition != "" {
- res, err := dsl.EvalExpr(r.options.OutputMatchCondition, flatMap)
- if err != nil {
- gologger.Error().Msgf("Could not evaluate match condition: %s\n", err)
+ matched := evalDslExpr(resp, r.options.OutputMatchCondition)
+ if !matched {
continue
- } else {
- if res == false {
- continue
- }
}
}
if r.options.OutputFilterCondition != "" {
- res, err := dsl.EvalExpr(r.options.OutputFilterCondition, flatMap)
- if err != nil {
- gologger.Error().Msgf("Could not evaluate filter condition: %s\n", err)
+ matched := evalDslExpr(resp, r.options.OutputFilterCondition)
+ if matched {
continue
- } else {
- if res == true {
- continue
- }
}
}
}
+ if r.options.OutputFilterErrorPage && resp.KnowledgeBase["PageType"] == "error" {
+ logFilteredErrorPage(resp.URL)
+ continue
+ }
if len(r.options.filterStatusCode) > 0 && slice.IntSliceContains(r.options.filterStatusCode, resp.StatusCode) {
continue
}
@@ -705,10 +845,10 @@ func (r *Runner) RunEnumeration() {
if len(r.options.filterWordsCount) > 0 && slice.IntSliceContains(r.options.filterWordsCount, resp.Words) {
continue
}
- if r.options.filterRegex != nil && r.options.filterRegex.MatchString(resp.raw) {
+ if r.options.filterRegex != nil && r.options.filterRegex.MatchString(resp.Raw) {
continue
}
- if r.options.OutputFilterString != "" && strings.Contains(strings.ToLower(resp.raw), strings.ToLower(r.options.OutputFilterString)) {
+ if r.options.OutputFilterString != "" && strings.Contains(strings.ToLower(resp.Raw), strings.ToLower(r.options.OutputFilterString)) {
continue
}
if len(r.options.OutputFilterFavicon) > 0 && stringsutil.EqualFoldAny(resp.FavIconMMH3, r.options.OutputFilterFavicon...) {
@@ -720,10 +860,10 @@ func (r *Runner) RunEnumeration() {
if len(r.options.matchContentLength) > 0 && !slice.IntSliceContains(r.options.matchContentLength, resp.ContentLength) {
continue
}
- if r.options.matchRegex != nil && !r.options.matchRegex.MatchString(resp.raw) {
+ if r.options.matchRegex != nil && !r.options.matchRegex.MatchString(resp.Raw) {
continue
}
- if r.options.OutputMatchString != "" && !strings.Contains(strings.ToLower(resp.raw), strings.ToLower(r.options.OutputMatchString)) {
+ if r.options.OutputMatchString != "" && !strings.Contains(strings.ToLower(resp.Raw), strings.ToLower(r.options.OutputMatchString)) {
continue
}
if len(r.options.OutputMatchFavicon) > 0 && !stringsutil.EqualFoldAny(resp.FavIconMMH3, r.options.OutputMatchFavicon...) {
@@ -805,21 +945,115 @@ func (r *Runner) RunEnumeration() {
}
}
}
- row := resp.str
+
+ if r.scanopts.StoreVisionReconClusters {
+ foundCluster := false
+ pHash, _ := resp.KnowledgeBase["pHash"].(uint64)
+ for i, cluster := range r.pHashClusters {
+ distance, _ := goimagehash.NewImageHash(pHash, goimagehash.PHash).Distance(goimagehash.NewImageHash(cluster.BasePHash, goimagehash.PHash))
+ if distance <= hammingDistanceThreshold {
+ r.pHashClusters[i].Hashes = append(r.pHashClusters[i].Hashes, pHashUrl{PHash: pHash, Url: resp.URL})
+ foundCluster = true
+ break
+ }
+ }
+
+ if !foundCluster {
+ newCluster := pHashCluster{
+ BasePHash: pHash,
+ Hashes: []pHashUrl{{PHash: pHash, Url: resp.URL}},
+ }
+ r.pHashClusters = append(r.pHashClusters, newCluster)
+ }
+ }
+
+ if !jsonOrCsv || jsonAndCsv || r.options.OutputAll {
+ gologger.Silent().Msgf("%s\n", resp.str)
+ }
+
+ //nolint:errcheck // this method needs a small refactor to reduce complexity
+ if plainFile != nil {
+ plainFile.WriteString(resp.str + "\n")
+ }
+
if r.options.JSONOutput {
- row = resp.JSON(&r.scanopts)
- } else if r.options.CSVOutput {
- row = resp.CSVRow(&r.scanopts)
+ row := resp.JSON(&r.scanopts)
+ if !r.options.OutputAll && !jsonAndCsv {
+ gologger.Silent().Msgf("%s\n", row)
+ }
+
+ //nolint:errcheck // this method needs a small refactor to reduce complexity
+ if jsonFile != nil {
+ jsonFile.WriteString(row + "\n")
+ }
}
- gologger.Silent().Msgf("%s\n", row)
- if f != nil {
+ if r.options.CSVOutput {
+ row := resp.CSVRow(&r.scanopts)
+
+ if !r.options.OutputAll && !jsonAndCsv {
+ gologger.Silent().Msgf("%s\n", row)
+ }
+
//nolint:errcheck // this method needs a small refactor to reduce complexity
- f.WriteString(row + "\n")
+ if csvFile != nil {
+ csvFile.WriteString(row + "\n")
+ }
+ }
+
+ for _, nextStep := range nextSteps {
+ nextStep <- resp
+ }
+ }
+ }(output, nextStep)
+
+ // HTML Summary
+ // - needs output of previous routine
+ // - separate goroutine due to incapability of go templates to render from file
+ wgoutput.Add()
+ go func(output chan Result) {
+ defer wgoutput.Done()
+
+ if r.options.Screenshot {
+ screenshotHtmlPath := filepath.Join(r.options.StoreResponseDir, "screenshot", "screenshot.html")
+ screenshotHtml, err := os.Create(screenshotHtmlPath)
+ if err != nil {
+ gologger.Warning().Msgf("Could not create HTML file %s\n", err)
+ }
+ defer screenshotHtml.Close()
+
+ templateMap := template.FuncMap{
+ "safeURL": func(u string) template.URL {
+ if osutil.IsWindows() {
+ u = filepath.ToSlash(u)
+ }
+ return template.URL(u)
+ },
+ }
+ tmpl, err := template.
+ New("screenshotTemplate").
+ Funcs(templateMap).
+ Parse(static.HtmlTemplate)
+ if err != nil {
+ gologger.Warning().Msgf("Could not create HTML template: %v\n", err)
}
+
+ if err = tmpl.Execute(screenshotHtml, struct {
+ Options Options
+ Output chan Result
+ }{
+ Options: *r.options,
+ Output: output,
+ }); err != nil {
+ gologger.Warning().Msgf("Could not execute HTML template: %v\n", err)
+ }
+ }
+
+ // fallthrough if anything is left in the buffer unblocks if screenshot is false
+ for range output {
}
- }(output)
+ }(nextStep)
wg := sizedwaitgroup.New(r.options.Threads)
@@ -868,17 +1102,79 @@ func (r *Runner) RunEnumeration() {
close(output)
wgoutput.Wait()
+
+ if r.scanopts.StoreVisionReconClusters {
+ visionReconClusters := filepath.Join(r.options.StoreResponseDir, "vision_recon_clusters.json")
+ clusterReportJSON, err := json.Marshal(r.pHashClusters)
+ if err != nil {
+ gologger.Fatal().Msgf("Failed to marshal report to JSON: %v", err)
+ }
+ file, err := os.Create(visionReconClusters)
+ if err != nil {
+ gologger.Fatal().Msgf("Failed to create JSON file: %v", err)
+ }
+ defer file.Close()
+
+ _, err = file.Write(clusterReportJSON)
+ if err != nil {
+ gologger.Fatal().Msgf("Failed to write to JSON file: %v", err)
+ }
+ }
+}
+
+func logFilteredErrorPage(url string) {
+ fileName := "filtered_error_page.json"
+ file, err := os.OpenFile(fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
+ if err != nil {
+ gologger.Fatal().Msgf("Could not open/create output file '%s': %s\n", fileName, err)
+ return
+ }
+ defer file.Close()
+
+ info := map[string]interface{}{
+ "url": url,
+ "time_filtered": time.Now(),
+ }
+
+ data, err := json.Marshal(info)
+ if err != nil {
+ fmt.Println("Failed to marshal JSON:", err)
+ return
+ }
+
+ if _, err := file.Write(data); err != nil {
+ gologger.Fatal().Msgf("Failed to write to '%s': %s\n", fileName, err)
+ return
+ }
+
+ if _, err := file.WriteString("\n"); err != nil {
+ gologger.Fatal().Msgf("Failed to write newline to '%s': %s\n", fileName, err)
+ return
+ }
+}
+func openOrCreateFile(resume bool, filename string) *os.File {
+ var err error
+ var f *os.File
+ if resume {
+ f, err = os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
+ } else {
+ f, err = os.Create(filename)
+ }
+ if err != nil {
+ gologger.Fatal().Msgf("Could not open/create output file '%s': %s\n", filename, err)
+ }
+ return f
}
-func (r *Runner) GetScanOpts() scanOptions {
+func (r *Runner) GetScanOpts() ScanOptions {
return r.scanopts
}
-func (r *Runner) Process(t string, wg *sizedwaitgroup.SizedWaitGroup, protocol string, scanopts *scanOptions, output chan Result) {
+func (r *Runner) Process(t string, wg *sizedwaitgroup.SizedWaitGroup, protocol string, scanopts *ScanOptions, output chan Result) {
r.process(t, wg, r.hp, protocol, scanopts, output)
}
-func (r *Runner) process(t string, wg *sizedwaitgroup.SizedWaitGroup, hp *httpx.HTTPX, protocol string, scanopts *scanOptions, output chan Result) {
+func (r *Runner) process(t string, wg *sizedwaitgroup.SizedWaitGroup, hp *httpx.HTTPX, protocol string, scanopts *ScanOptions, output chan Result) {
protocols := []string{protocol}
if scanopts.NoFallback || protocol == httpx.HTTPandHTTPS {
protocols = []string{httpx.HTTPS, httpx.HTTP}
@@ -897,7 +1193,6 @@ func (r *Runner) process(t string, wg *sizedwaitgroup.SizedWaitGroup, hp *httpx.
result := r.analyze(hp, protocol, target, method, t, scanopts)
output <- result
if scanopts.TLSProbe && result.TLSData != nil {
- scanopts.TLSProbe = false
for _, tt := range result.TLSData.SubjectAN {
if !r.testAndSet(tt) {
continue
@@ -923,6 +1218,13 @@ func (r *Runner) process(t string, wg *sizedwaitgroup.SizedWaitGroup, hp *httpx.
}
for port, wantedProtocolForPort := range customport.Ports {
+ // NoFallbackScheme overrides custom ports scheme
+ // Example: httpx -u https://www.example.com -ports http:8080,https:443 --no-fallback-scheme
+ // In this case, the requests will be created with the target scheme (ignoring the custom ports scheme)
+ // Examples: https://www.example.com:8080 and https://www.example.com:443
+ if scanopts.NoFallbackScheme {
+ wantedProtocolForPort = protocol
+ }
wantedProtocols := []string{wantedProtocolForPort}
if wantedProtocolForPort == httpx.HTTPandHTTPS {
wantedProtocols = []string{httpx.HTTPS, httpx.HTTP}
@@ -938,12 +1240,11 @@ func (r *Runner) process(t string, wg *sizedwaitgroup.SizedWaitGroup, hp *httpx.
gologger.Warning().Msgf("failed to update port of %v got %v", target.Host, err)
} else {
urlx.UpdatePort(fmt.Sprint(port))
- target.Host = urlx.Host
+ target.Host = urlx.String()
}
result := r.analyze(hp, protocol, target, method, t, scanopts)
output <- result
if scanopts.TLSProbe && result.TLSData != nil {
- scanopts.TLSProbe = false
for _, tt := range result.TLSData.SubjectAN {
if !r.testAndSet(tt) {
continue
@@ -984,6 +1285,7 @@ func (r *Runner) targets(hp *httpx.HTTPX, target string) chan httpx.Target {
case asn.IsASN(target):
cidrIps, err := asn.GetIPAddressesAsStream(target)
if err != nil {
+ gologger.Warning().Msgf("Could not get ASN targets for '%s': %s\n", target, err)
return
}
for ip := range cidrIps {
@@ -1021,7 +1323,7 @@ func (r *Runner) targets(hp *httpx.HTTPX, target string) chan httpx.Target {
return results
}
-func (r *Runner) analyze(hp *httpx.HTTPX, protocol string, target httpx.Target, method, origInput string, scanopts *scanOptions) Result {
+func (r *Runner) analyze(hp *httpx.HTTPX, protocol string, target httpx.Target, method, origInput string, scanopts *ScanOptions) Result {
origProtocol := protocol
if protocol == httpx.HTTPorHTTPS || protocol == httpx.HTTPandHTTPS {
protocol = httpx.HTTPS
@@ -1033,22 +1335,22 @@ retry:
}
URL, err := r.parseURL(target.Host)
if err != nil {
- return Result{URL: target.Host, Input: origInput, err: err}
+ return Result{URL: target.Host, Input: origInput, Err: err}
}
// check if we have to skip the host:port as a result of a previous failure
hostPort := net.JoinHostPort(URL.Host, URL.Port())
if r.options.HostMaxErrors >= 0 && r.HostErrorsCache.Has(hostPort) {
numberOfErrors, err := r.HostErrorsCache.GetIFPresent(hostPort)
- if err == nil && numberOfErrors.(int) >= r.options.HostMaxErrors {
- return Result{URL: target.Host, err: errors.New("skipping as previously unresponsive")}
+ if err == nil && numberOfErrors >= r.options.HostMaxErrors {
+ return Result{URL: target.Host, Err: errors.New("skipping as previously unresponsive")}
}
}
// check if the combination host:port should be skipped if belonging to a cdn
- if r.skipCDNPort(URL.Host, URL.Port()) {
- gologger.Debug().Msgf("Skipping cdn target: %s:%s\n", URL.Host, URL.Port())
- return Result{URL: target.Host, Input: origInput, err: errors.New("cdn target only allows ports 80 and 443")}
+ skip, reason := r.skip(URL, target, origInput)
+ if skip {
+ return reason
}
URL.Scheme = protocol
@@ -1070,13 +1372,13 @@ retry:
} else {
requestIP = target.CustomIP
}
- ctx := context.WithValue(context.Background(), "ip", requestIP) //nolint
+ ctx := context.WithValue(context.Background(), fastdialer.IP, requestIP)
req, err = hp.NewRequestWithContext(ctx, method, URL.String())
} else {
req, err = hp.NewRequest(method, URL.String())
}
if err != nil {
- return Result{URL: URL.String(), Input: origInput, err: err}
+ return Result{URL: URL.String(), Input: origInput, Err: err}
}
if target.CustomHost != "" {
@@ -1117,7 +1419,7 @@ retry:
var errDump error
requestDump, errDump = rawhttp.DumpRequestRaw(req.Method, req.URL.String(), reqURI, req.Header, req.Body, rawhttp.DefaultOptions)
if errDump != nil {
- return Result{URL: URL.String(), Input: origInput, err: errDump}
+ return Result{URL: URL.String(), Input: origInput, Err: errDump}
}
} else {
// Create a copy on the fly of the request body
@@ -1128,7 +1430,7 @@ retry:
var errDump error
requestDump, errDump = httputil.DumpRequestOut(req.Request, true)
if errDump != nil {
- return Result{URL: URL.String(), Input: origInput, err: errDump}
+ return Result{URL: URL.String(), Input: origInput, Err: errDump}
}
// The original req.Body gets modified indirectly by httputil.DumpRequestOut so we set it again to nil if it was empty
// Otherwise redirects like 307/308 would fail (as they require the body to be sent along)
@@ -1140,7 +1442,7 @@ retry:
// fix the final output url
fullURL := req.URL.String()
if parsedURL, errParse := r.parseURL(fullURL); errParse != nil {
- return Result{URL: URL.String(), Input: origInput, err: errParse}
+ return Result{URL: URL.String(), Input: origInput, Err: errParse}
} else {
if r.options.Unsafe {
parsedURL.Path = reqURI
@@ -1200,17 +1502,17 @@ retry:
// mark the host:port as failed to avoid further checks
if r.options.HostMaxErrors >= 0 {
errorCount, err := r.HostErrorsCache.GetIFPresent(hostPort)
- if err != nil || errorCount == nil {
+ if err != nil || errorCount == 0 {
_ = r.HostErrorsCache.Set(hostPort, 1)
- } else if errorCount != nil {
- _ = r.HostErrorsCache.Set(hostPort, errorCount.(int)+1)
+ } else if errorCount > 0 {
+ _ = r.HostErrorsCache.Set(hostPort, errorCount+1)
}
}
if r.options.Probe {
- return Result{URL: URL.String(), Input: origInput, Timestamp: time.Now(), err: err, Failed: err != nil, Error: errString, str: builder.String()}
+ return Result{URL: URL.String(), Input: origInput, Timestamp: time.Now(), Err: err, Failed: err != nil, Error: errString, str: builder.String()}
} else {
- return Result{URL: URL.String(), Input: origInput, Timestamp: time.Now(), err: err}
+ return Result{URL: URL.String(), Input: origInput, Timestamp: time.Now(), Err: err}
}
}
@@ -1296,25 +1598,59 @@ retry:
builder.WriteRune(']')
}
+ var bodyPreview string
+ if r.options.ResponseBodyPreviewSize > 0 && resp != nil {
+ bodyPreview = string(resp.Data)
+ if stringsutil.EqualFoldAny(r.options.StripFilter, "html", "xml") {
+ bodyPreview = r.hp.Sanitize(bodyPreview, true, true)
+ } else {
+ bodyPreview = strings.ReplaceAll(bodyPreview, "\n", "\\n")
+ bodyPreview = httputilz.NormalizeSpaces(bodyPreview)
+ }
+ if len(bodyPreview) > r.options.ResponseBodyPreviewSize {
+ bodyPreview = bodyPreview[:r.options.ResponseBodyPreviewSize]
+ }
+ bodyPreview = strings.TrimSpace(bodyPreview)
+ builder.WriteString(" [")
+ if !scanopts.OutputWithNoColor {
+ builder.WriteString(aurora.Blue(bodyPreview).String())
+ } else {
+ builder.WriteString(bodyPreview)
+ }
+ builder.WriteRune(']')
+ }
+
serverHeader := resp.GetHeader("Server")
if scanopts.OutputServerHeader {
builder.WriteString(fmt.Sprintf(" [%s]", serverHeader))
}
- var serverResponseRaw string
- var request string
- var rawResponseHeader string
- var responseHeader map[string]interface{}
- if scanopts.ResponseInStdout {
- serverResponseRaw = string(resp.Data)
+ var (
+ serverResponseRaw string
+ request string
+ rawResponseHeaders string
+ responseHeaders map[string]interface{}
+ )
+
+ if scanopts.ResponseHeadersInStdout {
+ responseHeaders = normalizeHeaders(resp.Headers)
+ }
+
+ respData := string(resp.Data)
+ if r.options.NoDecode {
+ respData = string(resp.RawData)
+ }
+
+ if scanopts.ResponseInStdout || r.options.OutputMatchCondition != "" || r.options.OutputFilterCondition != "" {
+ serverResponseRaw = string(respData)
request = string(requestDump)
- responseHeader = normalizeHeaders(resp.Headers)
- rawResponseHeader = resp.RawHeaders
+ responseHeaders = normalizeHeaders(resp.Headers)
+ rawResponseHeaders = resp.RawHeaders
} else if scanopts.Base64ResponseInStdout {
- serverResponseRaw = stringz.Base64(resp.Data)
+ serverResponseRaw = stringz.Base64([]byte(respData))
request = stringz.Base64(requestDump)
- responseHeader = normalizeHeaders(resp.Headers)
- rawResponseHeader = stringz.Base64([]byte(resp.RawHeaders))
+ responseHeaders = normalizeHeaders(resp.Headers)
+ rawResponseHeaders = stringz.Base64([]byte(resp.RawHeaders))
}
// check for virtual host
@@ -1328,7 +1664,7 @@ retry:
}
// web socket
- isWebSocket := resp.StatusCode == 101
+ isWebSocket := isWebSocket(resp)
if scanopts.OutputWebSocket && isWebSocket {
builder.WriteString(" [websocket]")
}
@@ -1401,7 +1737,12 @@ retry:
builder.WriteString(fmt.Sprintf(" [%s]", ip))
}
- ips, cnames, err := getDNSData(hp, URL.Host)
+ var onlyHost string
+ onlyHost, _, err = net.SplitHostPort(URL.Host)
+ if err != nil {
+ onlyHost = URL.Host
+ }
+ ips, cnames, err := getDNSData(hp, onlyHost)
if err != nil {
ips = append(ips, ip)
}
@@ -1494,7 +1835,10 @@ retry:
hashesMap := make(map[string]interface{})
if scanopts.Hashes != "" {
hs := strings.Split(scanopts.Hashes, ",")
- builder.WriteString(" [")
+ outputHashes := !(r.options.JSONOutput || r.options.OutputAll)
+ if outputHashes {
+ builder.WriteString(" [")
+ }
for index, hashType := range hs {
var (
hashHeader, hashBody string
@@ -1523,17 +1867,21 @@ retry:
if hashBody != "" {
hashesMap[fmt.Sprintf("body_%s", hashType)] = hashBody
hashesMap[fmt.Sprintf("header_%s", hashType)] = hashHeader
- if !scanopts.OutputWithNoColor {
- builder.WriteString(aurora.Magenta(hashBody).String())
- } else {
- builder.WriteString(hashBody)
- }
- if index != len(hs)-1 {
- builder.WriteString(",")
+ if outputHashes {
+ if !scanopts.OutputWithNoColor {
+ builder.WriteString(aurora.Magenta(hashBody).String())
+ } else {
+ builder.WriteString(hashBody)
+ }
+ if index != len(hs)-1 {
+ builder.WriteString(",")
+ }
}
}
}
- builder.WriteRune(']')
+ if outputHashes {
+ builder.WriteRune(']')
+ }
}
if scanopts.OutputLinesCount {
builder.WriteString(" [")
@@ -1546,7 +1894,7 @@ retry:
}
jarmhash := ""
if r.options.Jarm {
- jarmhash = jarm.Jarm(r.fastdialer, fullURL, r.options.Timeout)
+ jarmhash = jarm.Jarm(r.hp.Dialer, fullURL, r.options.Timeout)
builder.WriteString(" [")
if !scanopts.OutputWithNoColor {
builder.WriteString(aurora.Magenta(jarmhash).String())
@@ -1566,40 +1914,43 @@ retry:
}
// store responses or chain in directory
- var responsePath string
+ domainFile := method + ":" + URL.EscapedString()
+ hash := hashes.Sha1([]byte(domainFile))
+ domainResponseFile := fmt.Sprintf("%s.txt", hash)
+ screenshotResponseFile := fmt.Sprintf("%s.png", hash)
+ hostFilename := strings.ReplaceAll(URL.Host, ":", "_")
+ domainResponseBaseDir := filepath.Join(scanopts.StoreResponseDirectory, "response")
+ domainScreenshotBaseDir := filepath.Join(scanopts.StoreResponseDirectory, "screenshot")
+ responseBaseDir := filepath.Join(domainResponseBaseDir, hostFilename)
+ screenshotBaseDir := filepath.Join(domainScreenshotBaseDir, hostFilename)
+
+ var responsePath, screenshotPath, screenshotPathRel string
+ // store response
if scanopts.StoreResponse || scanopts.StoreChain {
+ responsePath = fileutilz.AbsPathOrDefault(filepath.Join(responseBaseDir, domainResponseFile))
// URL.EscapedString returns that can be used as filename
- domainFile := URL.EscapedString()
- hash := hashes.Sha1([]byte(domainFile))
- domainFile = fmt.Sprintf("%s.txt", hash)
- domainBaseDir := filepath.Join(scanopts.StoreResponseDirectory, URL.Host)
- // store response
- responsePath = filepath.Join(domainBaseDir, domainFile)
respRaw := resp.Raw
reqRaw := requestDump
if len(respRaw) > scanopts.MaxResponseBodySizeToSave {
respRaw = respRaw[:scanopts.MaxResponseBodySizeToSave]
}
- data := append([]byte(fullURL), append([]byte("\n\n"), reqRaw...)...)
- data = append(data, append([]byte("\n"), respRaw...)...)
- _ = fileutil.CreateFolder(domainBaseDir)
+ data := reqRaw
+ if scanopts.StoreChain && resp.HasChain() {
+ data = append(data, append([]byte("\n"), []byte(resp.GetChain())...)...)
+ }
+ data = append(data, respRaw...)
+ data = append(data, []byte("\n\n\n")...)
+ data = append(data, []byte(fullURL)...)
+ _ = fileutil.CreateFolder(responseBaseDir)
writeErr := os.WriteFile(responsePath, data, 0644)
if writeErr != nil {
gologger.Error().Msgf("Could not write response at path '%s', to disk: %s", responsePath, writeErr)
}
- if scanopts.StoreChain && resp.HasChain() {
- domainFile = strings.ReplaceAll(domainFile, ".txt", ".chain.txt")
- responsePath = filepath.Join(domainBaseDir, domainFile)
- writeErr := os.WriteFile(responsePath, []byte(resp.GetChain()), 0644)
- if writeErr != nil {
- gologger.Warning().Msgf("Could not write response at path '%s', to disk: %s", responsePath, writeErr)
- }
- }
}
parsed, err := r.parseURL(fullURL)
if err != nil {
- return Result{URL: fullURL, Input: origInput, err: errors.Wrap(err, "could not parse url")}
+ return Result{URL: fullURL, Input: origInput, Err: errors.Wrap(err, "could not parse url")}
}
finalPort := parsed.Port()
@@ -1623,15 +1974,49 @@ retry:
chainItems = append(chainItems, resp.GetChainAsSlice()...)
}
+ // screenshot
+ var (
+ screenshotBytes []byte
+ headlessBody string
+ )
+ var pHash uint64
+ if scanopts.Screenshot {
+ var err error
+ screenshotBytes, headlessBody, err = r.browser.ScreenshotWithBody(fullURL, time.Duration(scanopts.ScreenshotTimeout)*time.Second)
+ if err != nil {
+ gologger.Warning().Msgf("Could not take screenshot '%s': %s", fullURL, err)
+ } else {
+ screenshotPath = fileutilz.AbsPathOrDefault(filepath.Join(screenshotBaseDir, screenshotResponseFile))
+ screenshotPathRel = filepath.Join(hostFilename, screenshotResponseFile)
+ _ = fileutil.CreateFolder(screenshotBaseDir)
+ err := os.WriteFile(screenshotPath, screenshotBytes, 0644)
+ if err != nil {
+ gologger.Error().Msgf("Could not write screenshot at path '%s', to disk: %s", screenshotPath, err)
+ }
+
+ pHash, err = calculatePerceptionHash(screenshotBytes)
+ if err != nil {
+ gologger.Warning().Msgf("%v: %s", err, fullURL)
+ }
+
+ }
+ if scanopts.NoScreenshotBytes {
+ screenshotBytes = []byte{}
+ }
+ if scanopts.NoHeadlessBody {
+ headlessBody = ""
+ }
+ }
+
result := Result{
Timestamp: time.Now(),
Request: request,
- ResponseHeader: responseHeader,
- RawHeader: rawResponseHeader,
+ ResponseHeaders: responseHeaders,
+ RawHeaders: rawResponseHeaders,
Scheme: parsed.Scheme,
Port: finalPort,
Path: finalPath,
- raw: resp.Raw,
+ Raw: resp.Raw,
URL: fullURL,
Input: origInput,
ContentLength: resp.ContentLength,
@@ -1645,6 +2030,7 @@ retry:
VHost: isvhost,
WebServer: serverHeader,
ResponseBody: serverResponseRaw,
+ BodyPreview: bodyPreview,
WebSocket: isWebSocket,
TLSData: resp.TLSData,
CSPData: resp.CSPData,
@@ -1669,13 +2055,48 @@ retry:
ASN: asnResponse,
ExtractRegex: extractRegex,
StoredResponsePath: responsePath,
- }
- if r.options.OnResult != nil {
- r.options.OnResult(result)
+ ScreenshotBytes: screenshotBytes,
+ ScreenshotPath: screenshotPath,
+ ScreenshotPathRel: screenshotPathRel,
+ HeadlessBody: headlessBody,
+ KnowledgeBase: map[string]interface{}{
+ "PageType": r.errorPageClassifier.Classify(respData),
+ "pHash": pHash,
+ },
}
return result
}
+func (r *Runner) skip(URL *urlutil.URL, target httpx.Target, origInput string) (bool, Result) {
+ if r.skipCDNPort(URL.Host, URL.Port()) {
+ gologger.Debug().Msgf("Skipping cdn target: %s:%s\n", URL.Host, URL.Port())
+ return true, Result{URL: target.Host, Input: origInput, Err: errors.New("cdn target only allows ports 80 and 443")}
+ }
+
+ if _, ok := r.excludePorts[URL.Port()]; ok {
+ gologger.Debug().Msgf("Skipping excluded port: %s:%s\n", URL.Hostname(), URL.Port())
+ return true, Result{URL: target.Host, Input: origInput, Err: errors.New("port is in the exclude list")}
+ }
+
+ return false, Result{}
+}
+
+func calculatePerceptionHash(screenshotBytes []byte) (uint64, error) {
+ reader := bytes.NewReader(screenshotBytes)
+ img, _, err := image.Decode(reader)
+ if err != nil {
+ return 0, errors.Wrap(err, "failed to decode screenshot")
+
+ }
+
+ pHash, err := goimagehash.PerceptionHash(img)
+ if err != nil {
+ return 0, errors.Wrap(err, "failed to calculate perceptual hash")
+ }
+
+ return pHash.GetHash(), nil
+}
+
func (r *Runner) handleFaviconHash(hp *httpx.HTTPX, req *retryablehttp.Request, currentResp *httpx.Response) (string, string, error) {
// Check if current URI is ending with .ico => use current body without additional requests
if path.Ext(req.URL.Path) == ".ico" {
@@ -1700,6 +2121,7 @@ func (r *Runner) handleFaviconHash(hp *httpx.HTTPX, req *retryablehttp.Request,
}
if URL.IsAbs() {
req.SetURL(URL)
+ req.Host = URL.Host
faviconPath = ""
} else {
faviconPath = URL.String()
@@ -1753,7 +2175,7 @@ func (r *Runner) SaveResumeConfig() error {
}
// JSON the result
-func (r Result) JSON(scanopts *scanOptions) string { //nolint
+func (r Result) JSON(scanopts *ScanOptions) string { //nolint
if scanopts != nil && len(r.ResponseBody) > scanopts.MaxResponseBodySizeToSave {
r.ResponseBody = r.ResponseBody[:scanopts.MaxResponseBodySizeToSave]
}
@@ -1788,7 +2210,7 @@ func (r Result) CSVHeader() string { //nolint
}
// CSVRow the CSV Row
-func (r Result) CSVRow(scanopts *scanOptions) string { //nolint
+func (r Result) CSVRow(scanopts *ScanOptions) string { //nolint
if scanopts != nil && len(r.ResponseBody) > scanopts.MaxResponseBodySizeToSave {
r.ResponseBody = r.ResponseBody[:scanopts.MaxResponseBodySizeToSave]
}
@@ -1823,7 +2245,7 @@ func (r Result) CSVRow(scanopts *scanOptions) string { //nolint
func (r *Runner) skipCDNPort(host string, port string) bool {
// if the option is not enabled we don't skip
- if !r.options.ExcludeCDN {
+ if !r.scanopts.ExcludeCDN {
return false
}
// uses the dealer to pre-resolve the target
@@ -1846,7 +2268,7 @@ func (r *Runner) skipCDNPort(host string, port string) bool {
}
// If the target is part of the CDN ips range - only ports 80 and 443 are allowed
- if isCdnIP && port != "80" && port != "443" {
+ if isCdnIP && port != "" && port != "80" && port != "443" {
return true
}
@@ -1881,3 +2303,27 @@ func normalizeHeaders(headers map[string][]string) map[string]interface{} {
}
return normalized
}
+
+func isWebSocket(resp *httpx.Response) bool {
+ if resp.StatusCode == 101 {
+ return true
+ }
+ // TODO: improve this checks
+ // Check for specific headers that indicate WebSocket support
+ keyHeaders := []string{`^Sec-WebSocket-Accept:\s+.+`, `^Upgrade:\s+websocket`, `^Connection:\s+upgrade`}
+ for _, header := range keyHeaders {
+ re := regexp.MustCompile(header)
+ if re.MatchString(resp.RawHeaders) {
+ return true
+ }
+ }
+ // Check for specific data that indicates WebSocket support
+ keyData := []string{`{"socket":true,"socketUrl":"(?:wss?|ws)://.*"}`, `{"sid":"[^"]*","upgrades":\["websocket"\].*}`}
+ for _, data := range keyData {
+ re := regexp.MustCompile(data)
+ if re.Match(resp.RawData) {
+ return true
+ }
+ }
+ return false
+}
diff --git a/runner/types.go b/runner/types.go
index 3cf7414..37b98c0 100644
--- a/runner/types.go
+++ b/runner/types.go
@@ -1,15 +1,21 @@
package runner
import (
+ "errors"
"fmt"
+ "os"
+ "strings"
"time"
- "github.com/bxcodec/faker/v4"
- "github.com/bxcodec/faker/v4/pkg/options"
+ "github.com/go-faker/faker/v4"
+ "github.com/go-faker/faker/v4/pkg/options"
"github.com/mitchellh/mapstructure"
- "github.com/projectdiscovery/httpx/common/httpx"
+ "github.com/projectdiscovery/dsl"
+ "github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/tlsx/pkg/tlsx/clients"
mapsutil "github.com/projectdiscovery/utils/maps"
+
+ "github.com/projectdiscovery/httpx/common/httpx"
)
type AsnResponse struct {
@@ -25,25 +31,27 @@ func (o AsnResponse) String() string {
// Result of a scan
type Result struct {
- Timestamp time.Time `json:"timestamp,omitempty" csv:"timestamp"`
- ASN *AsnResponse `json:"asn,omitempty" csv:"asn"`
- err error
+ Timestamp time.Time `json:"timestamp,omitempty" csv:"timestamp"`
+ ASN *AsnResponse `json:"asn,omitempty" csv:"asn"`
+ Err error `json:"-" csv:"-"`
CSPData *httpx.CSPData `json:"csp,omitempty" csv:"csp"`
TLSData *clients.Response `json:"tls,omitempty" csv:"tls"`
Hashes map[string]interface{} `json:"hash,omitempty" csv:"hash"`
ExtractRegex []string `json:"extract_regex,omitempty" csv:"extract_regex"`
CDNName string `json:"cdn_name,omitempty" csv:"cdn_name"`
+ SNI string `json:"sni,omitempty" csv:"sni"`
Port string `json:"port,omitempty" csv:"port"`
- raw string
- URL string `json:"url,omitempty" csv:"url"`
- Input string `json:"input,omitempty" csv:"input"`
- Location string `json:"location,omitempty" csv:"location"`
- Title string `json:"title,omitempty" csv:"title"`
+ Raw string `json:"-" csv:"-"`
+ URL string `json:"url,omitempty" csv:"url"`
+ Input string `json:"input,omitempty" csv:"input"`
+ Location string `json:"location,omitempty" csv:"location"`
+ Title string `json:"title,omitempty" csv:"title"`
str string
Scheme string `json:"scheme,omitempty" csv:"scheme"`
Error string `json:"error,omitempty" csv:"error"`
WebServer string `json:"webserver,omitempty" csv:"webserver"`
ResponseBody string `json:"body,omitempty" csv:"body"`
+ BodyPreview string `json:"body_preview,omitempty" csv:"body_preview"`
ContentType string `json:"content_type,omitempty" csv:"content_type"`
Method string `json:"method,omitempty" csv:"method"`
Host string `json:"host,omitempty" csv:"host"`
@@ -51,8 +59,8 @@ type Result struct {
FavIconMMH3 string `json:"favicon,omitempty" csv:"favicon"`
FaviconPath string `json:"favicon_path,omitempty" csv:"favicon_path"`
FinalURL string `json:"final_url,omitempty" csv:"final_url"`
- ResponseHeader map[string]interface{} `json:"header,omitempty" csv:"header"`
- RawHeader string `json:"raw_header,omitempty" csv:"raw_header"`
+ ResponseHeaders map[string]interface{} `json:"header,omitempty" csv:"header"`
+ RawHeaders string `json:"raw_header,omitempty" csv:"raw_header"`
Request string `json:"request,omitempty" csv:"request"`
ResponseTime string `json:"time,omitempty" csv:"time"`
Jarm string `json:"jarm,omitempty" csv:"jarm"`
@@ -72,17 +80,22 @@ type Result struct {
CDN bool `json:"cdn,omitempty" csv:"cdn"`
HTTP2 bool `json:"http2,omitempty" csv:"http2"`
Pipeline bool `json:"pipeline,omitempty" csv:"pipeline"`
+ HeadlessBody string `json:"headless_body,omitempty" csv:"headless_body"`
+ ScreenshotBytes []byte `json:"screenshot_bytes,omitempty" csv:"screenshot_bytes"`
StoredResponsePath string `json:"stored_response_path,omitempty" csv:"stored_response_path"`
+ ScreenshotPath string `json:"screenshot_path,omitempty" csv:"screenshot_path"`
+ ScreenshotPathRel string `json:"screenshot_path_rel,omitempty" csv:"screenshot_path_rel"`
+ KnowledgeBase map[string]interface{} `json:"knowledgebase,omitempty" csv:"knowledgebase"`
}
// function to get dsl variables from result struct
func dslVariables() ([]string, error) {
fakeResult := Result{}
- fieldsToIgnore := []string{"Hashes", "ResponseHeader"}
+ fieldsToIgnore := []string{"Hashes", "ResponseHeaders", "Err", "KnowledgeBase"}
if err := faker.FakeData(&fakeResult, options.WithFieldsToIgnore(fieldsToIgnore...)); err != nil {
return nil, err
}
- m, err := ResultToMap(fakeResult)
+ m, err := resultToMap(fakeResult)
if err != nil {
return nil, err
}
@@ -94,7 +107,22 @@ func dslVariables() ([]string, error) {
return vars, nil
}
-func ResultToMap(resp Result) (map[string]any, error) {
+func evalDslExpr(result Result, dslExpr string) bool {
+ resultMap, err := resultToMap(result)
+ if err != nil {
+ gologger.Warning().Msgf("Could not map result: %s\n", err)
+ return false
+ }
+
+ res, err := dsl.EvalExpr(dslExpr, resultMap)
+ if err != nil && !ignoreErr(err) {
+ gologger.Error().Msgf("Could not evaluate DSL expression: %s\n", err)
+ return false
+ }
+ return res == true
+}
+
+func resultToMap(resp Result) (map[string]any, error) {
m := make(map[string]any)
config := &mapstructure.DecoderConfig{
TagName: "json",
@@ -110,3 +138,19 @@ func ResultToMap(resp Result) (map[string]any, error) {
}
return m, nil
}
+
+var (
+ // showDSLErr controls whether to show hidden DSL errors or not
+ showDSLErr = strings.EqualFold(os.Getenv("SHOW_DSL_ERRORS"), "true")
+)
+
+// ignoreErr checks if the error is to be ignored or not
+func ignoreErr(err error) bool {
+ if showDSLErr {
+ return false
+ }
+ if errors.Is(err, dsl.ErrParsingArg) || strings.Contains(err.Error(), "No parameter") {
+ return true
+ }
+ return false
+}
diff --git a/sonar-project.properties b/sonar-project.properties
deleted file mode 100644
index 0a9a5a5..0000000
--- a/sonar-project.properties
+++ /dev/null
@@ -1,16 +0,0 @@
-sonar.projectKey=projectdiscovery_httpx
-sonar.organization=projectdiscovery
-
-# This is the name and version displayed in the SonarCloud UI.
-#sonar.projectName=httpx
-#sonar.projectVersion=1.0
-
-# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
-sonar.sources=.
-sonar.tests=.
-sonar.test.inclusions=**/*_test.go
-sonar.go.coverage.reportPaths=cov.out
-sonar.externalIssuesReportPaths=report.json
-
-# Encoding of the source code. Default is default system encoding
-#sonar.sourceEncoding=UTF-8
\ No newline at end of file
diff --git a/static/html-summary.html b/static/html-summary.html
new file mode 100644
index 0000000..dee56c4
--- /dev/null
+++ b/static/html-summary.html
@@ -0,0 +1,133 @@
+
+
+
+
+ Screenshot Table
+
+
+
+
+
+
+
+
+
+ Response Info
+ |
+
+ Screenshot
+ |
+
+
+
+ {{ $ExtractTitle := .Options.ExtractTitle }}
+ {{ $OutputStatusCode := .Options.StatusCode }}
+ {{ $OutputContentLength := .Options.ContentLength }}
+ {{ $Favicon := .Options.Favicon }}
+ {{ $OutputResponseTime := .Options.OutputResponseTime }}
+ {{ $OutputLinesCount := .Options.OutputLinesCount }}
+ {{ $OutputWordsCount := .Options.OutputWordsCount }}
+ {{ $OutputServerHeader := .Options.OutputServerHeader }}
+ {{ $TechDetect := .Options.TechDetect }}
+ {{range .Output}}
+ {{if ne .ScreenshotPath ""}}
+
+
+
+ |
+
+
+
+
+ |
+
+ {{end}}
+ {{end}}
+
+
+
+
+
diff --git a/static/static.go b/static/static.go
new file mode 100644
index 0000000..82bf659
--- /dev/null
+++ b/static/static.go
@@ -0,0 +1,8 @@
+package static
+
+import (
+ _ "embed"
+)
+
+//go:embed html-summary.html
+var HtmlTemplate string