├── cmd ├── functional-test │ ├── test-data │ │ └── file.txt │ ├── run.sh │ ├── testcases.txt │ └── main.go ├── dnsx │ └── dnsx.go └── integration-test │ ├── integration-test.go │ └── dns.go ├── libs └── dnsx │ ├── doc.go │ ├── util.go │ └── dnsx.go ├── static ├── dnsx-logo.png └── dnsx-run.png ├── internal ├── runner │ ├── doc.go │ ├── resume.go │ ├── banner.go │ ├── util.go │ ├── wildcard.go │ ├── options.go │ └── runner.go └── testutils │ └── integration.go ├── .gitignore ├── SECURITY.md ├── Dockerfile ├── Makefile ├── integration_tests └── run.sh ├── .github ├── workflows │ ├── lint-test.yml │ ├── functional-test.yml │ ├── build-test.yml │ ├── release-binary.yml │ ├── codeql-analysis.yml │ └── dockerhub-push.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── issue-report.md └── dependabot.yml ├── .goreleaser.yml ├── LICENSE.md ├── go.mod ├── README.md └── go.sum /cmd/functional-test/test-data/file.txt: -------------------------------------------------------------------------------- 1 | example.com 2 | example.com 3 | -------------------------------------------------------------------------------- /libs/dnsx/doc.go: -------------------------------------------------------------------------------- 1 | // Package dnsx contains the library logic 2 | package dnsx 3 | -------------------------------------------------------------------------------- /static/dnsx-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/e1abrador/dnsx/master/static/dnsx-logo.png -------------------------------------------------------------------------------- /static/dnsx-run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/e1abrador/dnsx/master/static/dnsx-run.png -------------------------------------------------------------------------------- /internal/runner/doc.go: -------------------------------------------------------------------------------- 1 | // Package runner executes the enumeration process. 2 | package runner 3 | -------------------------------------------------------------------------------- /internal/runner/resume.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | type ResumeCfg struct { 4 | ResumeFrom string 5 | Index int 6 | current string 7 | currentIndex int 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cmd/dnsx/dnsx 2 | .DS_Store 3 | dist/ 4 | cmd/functional-test/dnsx_dev 5 | cmd/functional-test/functional-test 6 | cmd/functional-test/dnsx 7 | cmd/functional-test/log.txt 8 | cmd/functional-test/*.cfg 9 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | DO NOT CREATE AN ISSUE to report a security problem. Instead, please send an email to security@projectdiscovery.io and we will acknowledge it within 3 working days. 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.17.3-alpine3.14 AS build-env 2 | RUN go install -v github.com/projectdiscovery/dnsx/cmd/dnsx@latest 3 | 4 | FROM alpine:3.14 5 | RUN apk add --no-cache bind-tools ca-certificates 6 | COPY --from=build-env /go/bin/dnsx /usr/local/bin/dnsx 7 | ENTRYPOINT ["dnsx"] 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Go parameters 2 | GOCMD=go 3 | GOBUILD=$(GOCMD) build 4 | GOMOD=$(GOCMD) mod 5 | GOTEST=$(GOCMD) test 6 | GOGET=$(GOCMD) get 7 | 8 | all: build 9 | build: 10 | $(GOBUILD) -v -ldflags="-extldflags=-static" -o "dnsx" cmd/dnsx/dnsx.go 11 | test: 12 | $(GOTEST) -v ./... 13 | tidy: 14 | $(GOMOD) tidy 15 | -------------------------------------------------------------------------------- /integration_tests/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm integration-test dnsx 2>/dev/null 4 | cd ../cmd/dnsx 5 | go build 6 | mv dnsx ../../integration_tests/dnsx 7 | cd ../integration-test 8 | go build 9 | mv integration-test ../../integration_tests/integration-test 10 | cd ../../integration_tests 11 | ./integration-test 12 | if [ $? -eq 0 ] 13 | then 14 | exit 0 15 | else 16 | exit 1 17 | fi 18 | -------------------------------------------------------------------------------- /cmd/functional-test/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo 'Building functional-test binary' 4 | go build 5 | 6 | echo 'Building DNSX binary from current branch' 7 | go build -o dnsx_dev ../dnsx 8 | 9 | echo 'Installing latest release of DNSX' 10 | GO111MODULE=on go build -v github.com/projectdiscovery/dnsx/cmd/dnsx 11 | 12 | echo 'Starting DNSX functional test' 13 | ./functional-test -main ./dnsx -dev ./dnsx_dev -testcases testcases.txt 14 | -------------------------------------------------------------------------------- /.github/workflows/lint-test.yml: -------------------------------------------------------------------------------- 1 | name: 🙏🏻 Lint Test 2 | on: 3 | push: 4 | pull_request: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | lint: 9 | name: Lint Test 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v2 14 | - name: Run golangci-lint 15 | uses: golangci/golangci-lint-action@v2 16 | with: 17 | version: latest 18 | args: --timeout 5m 19 | working-directory: . -------------------------------------------------------------------------------- /.github/workflows/functional-test.yml: -------------------------------------------------------------------------------- 1 | name: 🧪 Functional Test 2 | on: 3 | push: 4 | pull_request: 5 | workflow_dispatch: 6 | 7 | 8 | jobs: 9 | functional: 10 | name: Functional Test 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Set up Go 14 | uses: actions/setup-go@v2 15 | with: 16 | go-version: 1.17 17 | 18 | - name: Check out code 19 | uses: actions/checkout@v2 20 | 21 | - name: Functional Tests 22 | run: | 23 | chmod +x run.sh 24 | bash run.sh 25 | working-directory: cmd/functional-test 26 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod tidy 4 | 5 | builds: 6 | - env: 7 | - CGO_ENABLED=0 8 | goos: 9 | - windows 10 | - linux 11 | - darwin 12 | goarch: 13 | - amd64 14 | - 386 15 | - arm 16 | - arm64 17 | 18 | ignore: 19 | - goos: darwin 20 | goarch: '386' 21 | - goos: windows 22 | goarch: 'arm' 23 | - goos: windows 24 | goarch: 'arm64' 25 | 26 | binary: '{{ .ProjectName }}' 27 | main: cmd/dnsx/dnsx.go 28 | 29 | archives: 30 | - format: zip 31 | replacements: 32 | darwin: macOS 33 | 34 | checksum: 35 | algorithm: sha256 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | 3 | contact_links: 4 | - name: Ask an question / advise on using dnsx 5 | url: https://github.com/projectdiscovery/dnsx/discussions/categories/q-a 6 | about: Ask a question or request support for using dnsx 7 | 8 | - name: Share idea / feature to discuss for dnsx 9 | url: https://github.com/projectdiscovery/dnsx/discussions/categories/ideas 10 | about: Share idea / feature to discuss for dnsx 11 | 12 | - name: Connect with PD Team (Discord) 13 | url: https://discord.gg/projectdiscovery 14 | about: Connect with PD Team for direct communication -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: 🔨 Build Test 2 | on: 3 | push: 4 | pull_request: 5 | workflow_dispatch: 6 | 7 | 8 | jobs: 9 | build: 10 | name: Test Builds 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-go@v2 15 | with: 16 | go-version: 1.17 17 | 18 | - name: Test 19 | run: go test . 20 | working-directory: cmd/dnsx/ 21 | 22 | - name: Integration Tests 23 | run: bash run.sh 24 | working-directory: integration_tests/ 25 | 26 | - name: Build 27 | run: go build . 28 | working-directory: cmd/dnsx/ 29 | -------------------------------------------------------------------------------- /libs/dnsx/util.go: -------------------------------------------------------------------------------- 1 | package dnsx 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/miekg/dns" 8 | ) 9 | 10 | // StringToRequestType conversion helper 11 | func StringToRequestType(tp string) (rt uint16, err error) { 12 | tp = strings.TrimSpace(strings.ToUpper(tp)) 13 | switch tp { 14 | case "A": 15 | rt = dns.TypeA 16 | case "NS": 17 | rt = dns.TypeNS 18 | case "CNAME": 19 | rt = dns.TypeCNAME 20 | case "SOA": 21 | rt = dns.TypeSOA 22 | case "PTR": 23 | rt = dns.TypePTR 24 | case "MX": 25 | rt = dns.TypeMX 26 | case "TXT": 27 | rt = dns.TypeTXT 28 | case "AAAA": 29 | rt = dns.TypeAAAA 30 | default: 31 | rt = dns.TypeNone 32 | err = fmt.Errorf("incorrect type") 33 | } 34 | 35 | return 36 | } 37 | -------------------------------------------------------------------------------- /internal/runner/banner.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import "github.com/projectdiscovery/gologger" 4 | 5 | const banner = ` 6 | _ __ __ 7 | __| | _ __ ___ \ \/ / 8 | / _' || '_ \ / __| \ / 9 | | (_| || | | |\__ \ / \ 10 | \__,_||_| |_||___//_/\_\ v1.0.7 11 | ` 12 | 13 | // Version is the current version of dnsx 14 | const Version = `1.0.7` 15 | 16 | // showBanner is used to show the banner to the user 17 | func showBanner() { 18 | gologger.Print().Msgf("%s\n", banner) 19 | gologger.Print().Msgf("\t\tprojectdiscovery.io\n\n") 20 | 21 | gologger.Print().Msgf("Use with caution. You are responsible for your actions\n") 22 | gologger.Print().Msgf("Developers assume no liability and are not responsible for any misuse or damage.\n") 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/release-binary.yml: -------------------------------------------------------------------------------- 1 | name: 🎉 Release Binary 2 | on: 3 | create: 4 | tags: 5 | - v* 6 | workflow_dispatch: 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - 13 | name: "Check out code" 14 | uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | - 18 | name: "Set up Go" 19 | uses: actions/setup-go@v2 20 | with: 21 | go-version: 1.17 22 | - 23 | env: 24 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 25 | name: "Create release on GitHub" 26 | uses: goreleaser/goreleaser-action@v2 27 | with: 28 | args: "release --rm-dist" 29 | version: latest 30 | workdir: . -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Request feature to implement in this project 4 | labels: 'Type: Enhancement' 5 | --- 6 | 7 | 13 | 14 | ### Please describe your feature request: 15 | 16 | 17 | ### Describe the use case of this feature: 18 | 19 | -------------------------------------------------------------------------------- /cmd/functional-test/testcases.txt: -------------------------------------------------------------------------------- 1 | example.com {{binary}} -silent 2 | example.com {{binary}} -silent -l test-data/file.txt 3 | example.com {{binary}} -silent -raw 4 | example.com {{binary}} -silent -resp 5 | example.com {{binary}} -silent -resp-only 6 | example.com {{binary}} -silent -a 7 | example.com {{binary}} -silent -a -resp 8 | example.com {{binary}} -silent -aaaa 9 | example.com {{binary}} -silent -aaaa -resp 10 | example.com {{binary}} -silent -ns 11 | example.com {{binary}} -silent -ns -resp 12 | example.com {{binary}} -silent -mx 13 | example.com {{binary}} -silent -mx -resp 14 | example.com {{binary}} -silent -soa 15 | example.com {{binary}} -silent -soa -resp 16 | example.com {{binary}} -silent -txt 17 | example.com {{binary}} -silent -txt -resp 18 | example.com {{binary}} -silent -rcode 0,1,2 19 | 1.1.1.1 {{binary}} -silent -ptr 20 | 1.1.1.1 {{binary}} -silent -ptr -resp 21 | www.projectdiscovery.io {{binary}} -silent -cname 22 | www.projectdiscovery.io {{binary}} -silent -cname -resp -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: 🚨 CodeQL Analysis 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | branches: 7 | - dev 8 | 9 | jobs: 10 | analyze: 11 | name: Analyze 12 | runs-on: ubuntu-latest 13 | permissions: 14 | actions: read 15 | contents: read 16 | security-events: write 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | language: [ 'go' ] 22 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 23 | 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@v2 27 | 28 | # Initializes the CodeQL tools for scanning. 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v1 31 | with: 32 | languages: ${{ matrix.language }} 33 | 34 | - name: Autobuild 35 | uses: github/codeql-action/autobuild@v1 36 | 37 | - name: Perform CodeQL Analysis 38 | uses: github/codeql-action/analyze@v1 -------------------------------------------------------------------------------- /cmd/dnsx/dnsx.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | 7 | "github.com/projectdiscovery/dnsx/internal/runner" 8 | "github.com/projectdiscovery/gologger" 9 | ) 10 | 11 | func main() { 12 | // Parse the command line flags and read config files 13 | options := runner.ParseOptions() 14 | 15 | dnsxRunner, err := runner.New(options) 16 | if err != nil { 17 | gologger.Fatal().Msgf("Could not create runner: %s\n", err) 18 | } 19 | 20 | // Setup graceful exits 21 | c := make(chan os.Signal, 1) 22 | signal.Notify(c, os.Interrupt) 23 | go func() { 24 | for range c { 25 | gologger.Info().Msgf("CTRL+C pressed: Exiting\n") 26 | dnsxRunner.Close() 27 | if options.ShouldSaveResume() { 28 | gologger.Info().Msgf("Creating resume file: %s\n", runner.DefaultResumeFile) 29 | err := dnsxRunner.SaveResumeConfig() 30 | if err != nil { 31 | gologger.Error().Msgf("Couldn't create resume file: %s\n", err) 32 | } 33 | } 34 | os.Exit(1) 35 | } 36 | }() 37 | 38 | // nolint:errcheck 39 | dnsxRunner.Run() 40 | dnsxRunner.Close() 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ProjectDiscovery, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | 9 | # Maintain dependencies for GitHub Actions 10 | - package-ecosystem: "github-actions" 11 | directory: "/" 12 | schedule: 13 | interval: "weekly" 14 | target-branch: "dev" 15 | commit-message: 16 | prefix: "chore" 17 | include: "scope" 18 | 19 | # Maintain dependencies for go modules 20 | - package-ecosystem: "gomod" 21 | directory: "/" 22 | schedule: 23 | interval: "weekly" 24 | target-branch: "dev" 25 | commit-message: 26 | prefix: "chore" 27 | include: "scope" 28 | 29 | # Maintain dependencies for docker 30 | - package-ecosystem: "docker" 31 | directory: "/" 32 | schedule: 33 | interval: "weekly" 34 | target-branch: "dev" 35 | commit-message: 36 | prefix: "chore" 37 | include: "scope" -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/projectdiscovery/dnsx 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/golang/snappy v0.0.4 // indirect 7 | github.com/logrusorgru/aurora v2.0.3+incompatible 8 | github.com/miekg/dns v1.1.43 9 | github.com/onsi/gomega v1.16.0 // indirect 10 | github.com/pkg/errors v0.9.1 11 | github.com/projectdiscovery/clistats v0.0.8 12 | github.com/projectdiscovery/fileutil v0.0.0-20210926202739-6050d0acf73c 13 | github.com/projectdiscovery/goconfig v0.0.0-20210804090219-f893ccd0c69c 14 | github.com/projectdiscovery/goflags v0.0.7 // indirect 15 | github.com/projectdiscovery/gologger v1.1.4 16 | github.com/projectdiscovery/hmap v0.0.1 17 | github.com/projectdiscovery/iputil v0.0.0-20210804143329-3a30fcde43f3 18 | github.com/projectdiscovery/mapcidr v0.0.8 19 | github.com/projectdiscovery/retryabledns v1.0.13-0.20210927160332-db15799e2e4d 20 | github.com/projectdiscovery/stringsutil v0.0.0-20210830151154-f567170afdd9 // indirect 21 | github.com/rs/xid v1.3.0 22 | go.uber.org/atomic v1.9.0 // indirect 23 | go.uber.org/ratelimit v0.2.0 24 | golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 // indirect 25 | golang.org/x/sys v0.0.0-20210915083310-ed5796bab164 // indirect 26 | golang.org/x/text v0.3.7 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /.github/workflows/dockerhub-push.yml: -------------------------------------------------------------------------------- 1 | name: 🌥 Docker Push 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["🎉 Release Binary"] 6 | types: 7 | - completed 8 | workflow_dispatch: 9 | 10 | jobs: 11 | docker: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Git Checkout 15 | uses: actions/checkout@v2 16 | 17 | - name: Get Github tag 18 | id: meta 19 | run: | 20 | echo "::set-output name=tag::$(curl --silent "https://api.github.com/repos/projectdiscovery/dnsx/releases/latest" | jq -r .tag_name)" 21 | 22 | - name: Set up QEMU 23 | uses: docker/setup-qemu-action@v1 24 | 25 | - name: Set up Docker Buildx 26 | uses: docker/setup-buildx-action@v1 27 | 28 | - name: Login to DockerHub 29 | uses: docker/login-action@v1 30 | with: 31 | username: ${{ secrets.DOCKER_USERNAME }} 32 | password: ${{ secrets.DOCKER_TOKEN }} 33 | 34 | - name: Build and push 35 | uses: docker/build-push-action@v2 36 | with: 37 | context: . 38 | platforms: linux/amd64,linux/arm64,linux/arm 39 | push: true 40 | tags: projectdiscovery/dnsx:latest,projectdiscovery/dnsx:${{ steps.meta.outputs.tag }} -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue report 3 | about: Create a report to help us to improve the project 4 | labels: 'Type: Bug' 5 | 6 | --- 7 | 8 | 13 | 14 | 15 | 16 | ### dnsx version: 17 | 18 | 19 | 20 | 21 | ### Current Behavior: 22 | 23 | 24 | ### Expected Behavior: 25 | 26 | 27 | ### Steps To Reproduce: 28 | 33 | 34 | 35 | ### Anything else: 36 | -------------------------------------------------------------------------------- /internal/runner/util.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "net/url" 7 | "os" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | func linesInFile(fileName string) ([]string, error) { 13 | result := []string{} 14 | f, err := os.Open(fileName) 15 | if err != nil { 16 | return result, err 17 | } 18 | defer f.Close() 19 | scanner := bufio.NewScanner(f) 20 | for scanner.Scan() { 21 | line := scanner.Text() 22 | result = append(result, line) 23 | } 24 | return result, nil 25 | } 26 | 27 | // isURL tests a string to determine if it is a well-structured url or not. 28 | func isURL(toTest string) bool { 29 | _, err := url.ParseRequestURI(toTest) 30 | if err != nil { 31 | return false 32 | } 33 | 34 | u, err := url.Parse(toTest) 35 | if err != nil || u.Scheme == "" || u.Host == "" { 36 | return false 37 | } 38 | 39 | return true 40 | } 41 | 42 | func extractDomain(URL string) string { 43 | u, err := url.Parse(URL) 44 | if err != nil { 45 | return "" 46 | } 47 | 48 | return u.Hostname() 49 | } 50 | 51 | func prepareResolver(resolver string) string { 52 | resolver = strings.TrimSpace(resolver) 53 | if !strings.Contains(resolver, ":") { 54 | resolver += ":53" 55 | } 56 | return resolver 57 | } 58 | 59 | func fmtDuration(d time.Duration) string { 60 | d = d.Round(time.Second) 61 | h := d / time.Hour 62 | d -= h * time.Hour 63 | m := d / time.Minute 64 | d -= m * time.Minute 65 | s := d / time.Second 66 | return fmt.Sprintf("%d:%02d:%02d", h, m, s) 67 | } 68 | -------------------------------------------------------------------------------- /cmd/integration-test/integration-test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/logrusorgru/aurora" 9 | "github.com/projectdiscovery/dnsx/internal/testutils" 10 | ) 11 | 12 | var ( 13 | debug = os.Getenv("DEBUG") == "true" 14 | customTest = os.Getenv("TEST") 15 | protocol = os.Getenv("PROTO") 16 | 17 | errored = false 18 | ) 19 | 20 | func main() { 21 | success := aurora.Green("[✓]").String() 22 | failed := aurora.Red("[✘]").String() 23 | 24 | tests := map[string]map[string]testutils.TestCase{ 25 | "dns": dnsTestcases, 26 | } 27 | for proto, tests := range tests { 28 | if protocol == "" || protocol == proto { 29 | fmt.Printf("Running test cases for \"%s\"\n", aurora.Blue(proto)) 30 | 31 | for name, test := range tests { 32 | if customTest != "" && !strings.Contains(name, customTest) { 33 | continue // only run tests user asked 34 | } 35 | err := test.Execute() 36 | if err != nil { 37 | fmt.Fprintf(os.Stderr, "%s Test \"%s\" failed: %s\n", failed, name, err) 38 | errored = true 39 | } else { 40 | fmt.Printf("%s Test \"%s\" passed!\n", success, name) 41 | } 42 | } 43 | } 44 | } 45 | if errored { 46 | os.Exit(1) 47 | } 48 | } 49 | 50 | func errIncorrectResultsCount(results []string) error { 51 | return fmt.Errorf("incorrect number of results %s", strings.Join(results, "\n\t")) 52 | } 53 | 54 | func errIncorrectResult(expected, got string) error { 55 | return fmt.Errorf("incorrect result: expected \"%s\" got \"%s\"", expected, got) 56 | } 57 | -------------------------------------------------------------------------------- /internal/testutils/integration.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "strings" 8 | ) 9 | 10 | // RunDnsxAndGetResults returns a list of results 11 | func RunDnsxAndGetResults(question string, debug bool, extra ...string) ([]string, error) { 12 | cmd := exec.Command("bash", "-c") 13 | cmdLine := `echo "` + question + `" | ./dnsx ` 14 | cmdLine += strings.Join(extra, " ") 15 | if debug { 16 | cmdLine += " -debug" 17 | cmd.Stderr = os.Stderr 18 | } else { 19 | cmdLine += " -silent" 20 | } 21 | 22 | cmd.Args = append(cmd.Args, cmdLine) 23 | 24 | data, err := cmd.Output() 25 | if err != nil { 26 | return nil, err 27 | } 28 | parts := []string{} 29 | items := strings.Split(string(data), "\n") 30 | for _, i := range items { 31 | if i != "" { 32 | parts = append(parts, i) 33 | } 34 | } 35 | return parts, nil 36 | } 37 | func RunDnsxBinaryAndGetResults(target string, dnsxBinary string, debug bool, args []string) ([]string, error) { 38 | cmd := exec.Command("bash", "-c") 39 | cmdLine := fmt.Sprintf(`echo %s | %s `, target, dnsxBinary) 40 | cmdLine += strings.Join(args, " ") 41 | if debug { 42 | cmdLine += " -debug" 43 | cmd.Stderr = os.Stderr 44 | } else { 45 | cmdLine += " -silent" 46 | } 47 | 48 | cmd.Args = append(cmd.Args, cmdLine) 49 | data, err := cmd.Output() 50 | if err != nil { 51 | return nil, err 52 | } 53 | parts := []string{} 54 | items := strings.Split(string(data), "\n") 55 | for _, i := range items { 56 | if i != "" { 57 | parts = append(parts, i) 58 | } 59 | } 60 | return parts,nil 61 | } 62 | 63 | // TestCase is a single integration test case 64 | type TestCase interface { 65 | // Execute executes a test case and returns any errors if occurred 66 | Execute() error 67 | } 68 | -------------------------------------------------------------------------------- /internal/runner/wildcard.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/rs/xid" 7 | ) 8 | 9 | // IsWildcard checks if a host is wildcard 10 | func (r *Runner) IsWildcard(host string) bool { 11 | orig := make(map[string]struct{}) 12 | wildcards := make(map[string]struct{}) 13 | 14 | in, err := r.dnsx.QueryOne(host) 15 | if err != nil || in == nil { 16 | return false 17 | } 18 | for _, A := range in.A { 19 | orig[A] = struct{}{} 20 | } 21 | 22 | subdomainPart := strings.TrimSuffix(host, "."+r.options.WildcardDomain) 23 | subdomainTokens := strings.Split(subdomainPart, ".") 24 | 25 | // Build an array by preallocating a slice of a length 26 | // and create the wildcard generation prefix. 27 | // We use a rand prefix at the beginning like %rand%.domain.tld 28 | // A permutation is generated for each level of the subdomain. 29 | var hosts []string 30 | hosts = append(hosts, r.options.WildcardDomain) 31 | 32 | if len(subdomainTokens) > 0 { 33 | for i := 1; i < len(subdomainTokens); i++ { 34 | newhost := strings.Join(subdomainTokens[i:], ".") + "." + r.options.WildcardDomain 35 | hosts = append(hosts, newhost) 36 | } 37 | } 38 | 39 | // Iterate over all the hosts generated for rand. 40 | for _, h := range hosts { 41 | r.wildcardscachemutex.Lock() 42 | listip, ok := r.wildcardscache[h] 43 | r.wildcardscachemutex.Unlock() 44 | if !ok { 45 | in, err := r.dnsx.QueryOne(xid.New().String() + "." + h) 46 | if err != nil || in == nil { 47 | continue 48 | } 49 | listip = in.A 50 | r.wildcardscachemutex.Lock() 51 | r.wildcardscache[h] = in.A 52 | r.wildcardscachemutex.Unlock() 53 | } 54 | 55 | // Get all the records and add them to the wildcard map 56 | for _, A := range listip { 57 | if _, ok := wildcards[A]; !ok { 58 | wildcards[A] = struct{}{} 59 | } 60 | } 61 | } 62 | 63 | // check if original ip are among wildcards 64 | for a := range orig { 65 | if _, ok := wildcards[a]; ok { 66 | return true 67 | } 68 | } 69 | 70 | return false 71 | } 72 | -------------------------------------------------------------------------------- /cmd/functional-test/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "os" 9 | "strings" 10 | 11 | "github.com/logrusorgru/aurora" 12 | "github.com/pkg/errors" 13 | 14 | "github.com/projectdiscovery/dnsx/internal/testutils" 15 | ) 16 | 17 | var ( 18 | debug = os.Getenv("DEBUG") == "true" 19 | success = aurora.Green("[✓]").String() 20 | failed = aurora.Red("[✘]").String() 21 | errored = false 22 | 23 | mainDnsxBinary = flag.String("main", "", "Main Branch Dnsx Binary") 24 | devDnsxBinary = flag.String("dev", "", "Dev Branch Dnsx Binary") 25 | testcases = flag.String("testcases", "", "Test cases file for dnsx functional tests") 26 | ) 27 | 28 | func main() { 29 | flag.Parse() 30 | 31 | if err := runFunctionalTests(); err != nil { 32 | log.Fatalf("Could not run functional tests: %s\n", err) 33 | } 34 | if errored { 35 | os.Exit(1) 36 | } 37 | } 38 | 39 | func runFunctionalTests() error { 40 | file, err := os.Open(*testcases) 41 | if err != nil { 42 | return errors.Wrap(err, "could not open test cases") 43 | } 44 | defer file.Close() 45 | 46 | scanner := bufio.NewScanner(file) 47 | for scanner.Scan() { 48 | text := strings.TrimSpace(scanner.Text()) 49 | if text == "" { 50 | continue 51 | } 52 | if err := runIndividualTestCase(text); err != nil { 53 | errored = true 54 | fmt.Fprintf(os.Stderr, "%s Test \"%s\" failed: %s\n", failed, text, err) 55 | } else { 56 | fmt.Printf("%s Test \"%s\" passed!\n", success, text) 57 | } 58 | } 59 | return nil 60 | } 61 | 62 | func runIndividualTestCase(testcase string) error { 63 | parts := strings.Fields(testcase) 64 | 65 | var finalArgs []string 66 | var target string 67 | if len(parts) > 1 { 68 | finalArgs = parts[2:] 69 | target = parts[0] 70 | } 71 | mainOutput, err := testutils.RunDnsxBinaryAndGetResults(target, *mainDnsxBinary, debug, finalArgs) 72 | if err != nil { 73 | return errors.Wrap(err, "could not run dnsx main test") 74 | } 75 | devOutput, err := testutils.RunDnsxBinaryAndGetResults(target, *devDnsxBinary, debug, finalArgs) 76 | if err != nil { 77 | return errors.Wrap(err, "could not run dnsx dev test") 78 | } 79 | if len(mainOutput) == len(devOutput) { 80 | return nil 81 | } 82 | return fmt.Errorf("%s main is not equal to %s dev", mainOutput, devOutput) 83 | } 84 | -------------------------------------------------------------------------------- /libs/dnsx/dnsx.go: -------------------------------------------------------------------------------- 1 | package dnsx 2 | 3 | import ( 4 | "errors" 5 | "math" 6 | 7 | miekgdns "github.com/miekg/dns" 8 | "github.com/projectdiscovery/iputil" 9 | retryabledns "github.com/projectdiscovery/retryabledns" 10 | ) 11 | 12 | // DNSX is structure to perform dns lookups 13 | type DNSX struct { 14 | dnsClient *retryabledns.Client 15 | Options *Options 16 | } 17 | 18 | // Options contains configuration options 19 | type Options struct { 20 | BaseResolvers []string 21 | MaxRetries int 22 | QuestionTypes []uint16 23 | Trace bool 24 | TraceMaxRecursion int 25 | Hostsfile bool 26 | } 27 | 28 | // DefaultOptions contains the default configuration options 29 | var DefaultOptions = Options{ 30 | BaseResolvers: DefaultResolvers, 31 | MaxRetries: 5, 32 | QuestionTypes: []uint16{miekgdns.TypeA}, 33 | TraceMaxRecursion: math.MaxUint16, 34 | Hostsfile: true, 35 | } 36 | 37 | // DefaultResolvers contains the list of resolvers known to be trusted. 38 | var DefaultResolvers = []string{ 39 | "udp:1.1.1.1:53", // Cloudflare 40 | "udp:1.0.0.1:53", // Cloudflare 41 | "udp:8.8.8.8:53", // Google 42 | "udp:8.8.4.4:53", // Google 43 | } 44 | 45 | // New creates a dns resolver 46 | func New(options Options) (*DNSX, error) { 47 | retryablednsOptions := retryabledns.Options{ 48 | BaseResolvers: options.BaseResolvers, 49 | MaxRetries: options.MaxRetries, 50 | Hostsfile: options.Hostsfile, 51 | } 52 | 53 | dnsClient := retryabledns.NewWithOptions(retryablednsOptions) 54 | 55 | return &DNSX{dnsClient: dnsClient, Options: &options}, nil 56 | } 57 | 58 | // Lookup performs a DNS A question and returns corresponding IPs 59 | func (d *DNSX) Lookup(hostname string) ([]string, error) { 60 | if iputil.IsIP(hostname) { 61 | return []string{hostname}, nil 62 | } 63 | 64 | dnsdata, err := d.dnsClient.Resolve(hostname) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | if dnsdata == nil || len(dnsdata.A) == 0 { 70 | return []string{}, errors.New("no ips found") 71 | } 72 | 73 | return dnsdata.A, nil 74 | } 75 | 76 | // QueryOne performs a DNS question of a specified type and returns raw responses 77 | func (d *DNSX) QueryOne(hostname string) (*retryabledns.DNSData, error) { 78 | return d.dnsClient.Query(hostname, d.Options.QuestionTypes[0]) 79 | } 80 | 81 | // QueryMultiple performs a DNS question of the specified types and returns raw responses 82 | func (d *DNSX) QueryMultiple(hostname string) (*retryabledns.DNSData, error) { 83 | return d.dnsClient.QueryMultiple(hostname, d.Options.QuestionTypes) 84 | } 85 | 86 | // Trace performs a DNS trace of the specified types and returns raw responses 87 | func (d *DNSX) Trace(hostname string) (*retryabledns.TraceData, error) { 88 | return d.dnsClient.Trace(hostname, d.Options.QuestionTypes[0], d.Options.TraceMaxRecursion) 89 | } 90 | -------------------------------------------------------------------------------- /cmd/integration-test/dns.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | 7 | "github.com/miekg/dns" 8 | "github.com/projectdiscovery/dnsx/internal/testutils" 9 | ) 10 | 11 | var dnsTestcases = map[string]testutils.TestCase{ 12 | "DNS A Request": &dnsARequest{question: "projectdiscovery.io", expectedOutput: "projectdiscovery.io"}, 13 | "DNS AAAA Request": &dnsAAAARequest{question: "projectdiscovery.io", expectedOutput: "projectdiscovery.io"}, 14 | } 15 | 16 | type dnsARequest struct { 17 | question string 18 | expectedOutput string 19 | } 20 | 21 | func (h *dnsARequest) Execute() error { 22 | handler := &dnshandler{ 23 | answers: []answer{ 24 | {question: h.question, questionType: dns.TypeA, values: []string{"1.2.3.4"}}, 25 | }, 26 | } 27 | srv := &dns.Server{ 28 | Handler: handler, 29 | Addr: "127.0.0.1:15000", 30 | Net: "udp", 31 | } 32 | go srv.ListenAndServe() //nolint 33 | defer srv.Shutdown() //nolint 34 | 35 | var extra []string 36 | extra = append(extra, "-r", "127.0.0.1:15000") 37 | extra = append(extra, "-a") 38 | 39 | results, err := testutils.RunDnsxAndGetResults(h.question, debug, extra...) 40 | if err != nil { 41 | return err 42 | } 43 | if len(results) != 1 { 44 | return errIncorrectResultsCount(results) 45 | } 46 | 47 | if h.expectedOutput != "" && !strings.EqualFold(results[0], h.expectedOutput) { 48 | return errIncorrectResult(results[0], h.expectedOutput) 49 | } 50 | 51 | return nil 52 | } 53 | 54 | type dnsAAAARequest struct { 55 | question string 56 | expectedOutput string 57 | } 58 | 59 | func (h *dnsAAAARequest) Execute() error { 60 | handler := &dnshandler{ 61 | answers: []answer{ 62 | {question: h.question, questionType: dns.TypeAAAA, values: []string{"2001:db8:3333:4444:5555:6666:7777:8888"}}, 63 | }, 64 | } 65 | srv := &dns.Server{ 66 | Handler: handler, 67 | Addr: "127.0.0.1:15000", 68 | Net: "udp", 69 | } 70 | go srv.ListenAndServe() //nolint 71 | defer srv.Shutdown() //nolint 72 | 73 | var extra []string 74 | extra = append(extra, "-r", "127.0.0.1:15000") 75 | extra = append(extra, "-aaaa") 76 | 77 | results, err := testutils.RunDnsxAndGetResults(h.question, debug, extra...) 78 | if err != nil { 79 | return err 80 | } 81 | if len(results) != 1 { 82 | return errIncorrectResultsCount(results) 83 | } 84 | 85 | if h.expectedOutput != "" && !strings.EqualFold(results[0], h.expectedOutput) { 86 | return errIncorrectResult(results[0], h.expectedOutput) 87 | } 88 | 89 | return nil 90 | } 91 | 92 | type answer struct { 93 | question string 94 | questionType uint16 95 | values []string 96 | } 97 | 98 | type dnshandler struct { 99 | answers []answer 100 | } 101 | 102 | func (t *dnshandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { 103 | question := r.Question[0].Name 104 | question = strings.TrimSuffix(question, ".") 105 | questionType := r.Question[0].Qtype 106 | for _, answer := range t.answers { 107 | if strings.EqualFold(question, answer.question) && answer.questionType == questionType { 108 | resp := buildAnswer(r, answer) 109 | w.WriteMsg(resp) //nolint 110 | } 111 | } 112 | } 113 | 114 | func buildAnswer(r *dns.Msg, ans answer) *dns.Msg { 115 | msg := dns.Msg{} 116 | msg.SetReply(r) 117 | msg.Authoritative = true 118 | switch ans.questionType { 119 | case dns.TypeA: 120 | for _, value := range ans.values { 121 | msg.Answer = append(msg.Answer, &dns.A{ 122 | Hdr: dns.RR_Header{Name: dns.Fqdn(ans.question), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 60}, 123 | A: net.ParseIP(value), 124 | }) 125 | } 126 | case dns.TypeAAAA: 127 | for _, value := range ans.values { 128 | msg.Answer = append(msg.Answer, &dns.AAAA{ 129 | Hdr: dns.RR_Header{Name: dns.Fqdn(ans.question), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 60}, 130 | AAAA: net.ParseIP(value), 131 | }) 132 | } 133 | } 134 | return &msg 135 | } 136 | -------------------------------------------------------------------------------- /internal/runner/options.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "errors" 5 | "math" 6 | "os" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/projectdiscovery/fileutil" 11 | "github.com/projectdiscovery/goconfig" 12 | "github.com/projectdiscovery/goflags" 13 | "github.com/projectdiscovery/gologger" 14 | "github.com/projectdiscovery/gologger/levels" 15 | ) 16 | 17 | const ( 18 | DefaultResumeFile = "resume.cfg" 19 | ) 20 | 21 | type Options struct { 22 | Resolvers string 23 | Hosts string 24 | Threads int 25 | RateLimit int 26 | Retries int 27 | OutputFormat string 28 | OutputFile string 29 | Raw bool 30 | Silent bool 31 | Verbose bool 32 | Version bool 33 | Response bool 34 | ResponseOnly bool 35 | A bool 36 | AAAA bool 37 | NS bool 38 | CNAME bool 39 | PTR bool 40 | MX bool 41 | SOA bool 42 | TXT bool 43 | JSON bool 44 | Trace bool 45 | TraceMaxRecursion int 46 | WildcardThreshold int 47 | WildcardDomain string 48 | ShowStatistics bool 49 | rcodes map[int]struct{} 50 | RCode string 51 | hasRCodes bool 52 | Resume bool 53 | resumeCfg *ResumeCfg 54 | FlushInterval int 55 | HostsFile bool 56 | } 57 | 58 | // ShouldLoadResume resume file 59 | func (options *Options) ShouldLoadResume() bool { 60 | return options.Resume && fileutil.FileExists(DefaultResumeFile) 61 | } 62 | 63 | // ShouldSaveResume file 64 | func (options *Options) ShouldSaveResume() bool { 65 | return true 66 | } 67 | 68 | // ParseOptions parses the command line options for application 69 | func ParseOptions() *Options { 70 | options := &Options{} 71 | flagSet := goflags.NewFlagSet() 72 | flagSet.SetDescription(`dnsx is a fast and multi-purpose DNS toolkit allow to run multiple probes using retryabledns library.`) 73 | 74 | createGroup(flagSet, "input", "Input", 75 | flagSet.StringVarP(&options.Hosts, "list", "l", "", "File input with list of sub(domains)/hosts"), 76 | ) 77 | 78 | createGroup(flagSet, "query", "Query", 79 | flagSet.BoolVar(&options.A, "a", false, "Query A record (default)"), 80 | flagSet.BoolVar(&options.AAAA, "aaaa", false, "Query AAAA record"), 81 | flagSet.BoolVar(&options.CNAME, "cname", false, "Query CNAME record"), 82 | flagSet.BoolVar(&options.NS, "ns", false, "Query NS record"), 83 | flagSet.BoolVar(&options.TXT, "txt", false, "Query TXT record"), 84 | flagSet.BoolVar(&options.PTR, "ptr", false, "Query PTR record"), 85 | flagSet.BoolVar(&options.MX, "mx", false, "Query MX record"), 86 | flagSet.BoolVar(&options.SOA, "soa", false, "Query SOA record"), 87 | ) 88 | 89 | createGroup(flagSet, "filters", "Filters", 90 | flagSet.BoolVar(&options.Response, "resp", false, "Display DNS response"), 91 | flagSet.BoolVar(&options.ResponseOnly, "resp-only", false, "Display DNS response only"), 92 | flagSet.StringVarP(&options.RCode, "rc", "rcode", "", "Display DNS status code (eg. -rcode noerror,servfail,refused)"), 93 | ) 94 | 95 | createGroup(flagSet, "rate-limit", "Rate-limit", 96 | flagSet.IntVarP(&options.Threads, "c", "t", 100, "Number of concurrent threads to use"), 97 | flagSet.IntVarP(&options.RateLimit, "rate-limit", "rl", -1, "Number of DNS request/second (disabled as default)"), 98 | ) 99 | 100 | createGroup(flagSet, "output", "Output", 101 | flagSet.StringVarP(&options.OutputFile, "output", "o", "", "File to write output"), 102 | flagSet.BoolVar(&options.JSON, "json", false, "Write output in JSONL(ines) format"), 103 | ) 104 | 105 | createGroup(flagSet, "debug", "Debug", 106 | flagSet.BoolVar(&options.Silent, "silent", false, "Show only results in the output"), 107 | flagSet.BoolVarP(&options.Verbose, "verbose", "v", false, "Verbose output"), 108 | flagSet.BoolVarP(&options.Raw, "debug", "raw", false, "Display RAW DNS response"), 109 | flagSet.BoolVar(&options.ShowStatistics, "stats", false, "Display stats of the running scan"), 110 | flagSet.BoolVar(&options.Version, "version", false, "Show version of dnsx"), 111 | ) 112 | 113 | createGroup(flagSet, "optimization", "Optimization", 114 | flagSet.IntVar(&options.Retries, "retry", 1, "Number of DNS retries"), 115 | flagSet.BoolVarP(&options.HostsFile, "hostsfile", "hf", false, "Parse system host file"), 116 | flagSet.BoolVar(&options.Trace, "trace", false, "Perform DNS trace"), 117 | flagSet.IntVar(&options.TraceMaxRecursion, "trace-max-recursion", math.MaxInt16, "Max recursion for dns trace"), 118 | flagSet.IntVar(&options.FlushInterval, "flush-interval", 10, "Flush interval of output file"), 119 | flagSet.BoolVar(&options.Resume, "resume", false, "Resume"), 120 | ) 121 | 122 | createGroup(flagSet, "configs", "Configurations", 123 | flagSet.StringVarP(&options.Resolvers, "resolver", "r", "", "List of resolvers (file or comma separated)"), 124 | flagSet.IntVarP(&options.WildcardThreshold, "wildcard-threshold", "wt", 5, "Wildcard Filter Threshold"), 125 | flagSet.StringVarP(&options.WildcardDomain, "wildcard-domain", "wd", "", "Domain name for wildcard filtering (other flags will be ignored)"), 126 | ) 127 | 128 | _ = flagSet.Parse() 129 | 130 | // Read the inputs and configure the logging 131 | options.configureOutput() 132 | 133 | err := options.configureRcodes() 134 | if err != nil { 135 | gologger.Fatal().Msgf("%s\n", err) 136 | } 137 | 138 | err = options.configureResume() 139 | if err != nil { 140 | gologger.Fatal().Msgf("%s\n", err) 141 | } 142 | 143 | showBanner() 144 | 145 | if options.Version { 146 | gologger.Info().Msgf("Current Version: %s\n", Version) 147 | os.Exit(0) 148 | } 149 | 150 | options.validateOptions() 151 | 152 | return options 153 | } 154 | 155 | func (options *Options) validateOptions() { 156 | if options.Response && options.ResponseOnly { 157 | gologger.Fatal().Msgf("resp and resp-only can't be used at the same time") 158 | } 159 | } 160 | 161 | // configureOutput configures the output on the screen 162 | func (options *Options) configureOutput() { 163 | // If the user desires verbose output, show verbose output 164 | if options.Verbose { 165 | gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose) 166 | } 167 | if options.Silent { 168 | gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent) 169 | } 170 | } 171 | 172 | func (options *Options) configureRcodes() error { 173 | options.rcodes = make(map[int]struct{}) 174 | rcodes := strings.Split(options.RCode, ",") 175 | for _, rcode := range rcodes { 176 | var rc int 177 | switch strings.ToLower(rcode) { 178 | case "": 179 | continue 180 | case "noerror": 181 | rc = 0 182 | case "formerr": 183 | rc = 1 184 | case "servfail": 185 | rc = 2 186 | case "nxdomain": 187 | rc = 3 188 | case "notimp": 189 | rc = 4 190 | case "refused": 191 | rc = 5 192 | case "yxdomain": 193 | rc = 6 194 | case "yxrrset": 195 | rc = 7 196 | case "nxrrset": 197 | rc = 8 198 | case "notauth": 199 | rc = 9 200 | case "notzone": 201 | rc = 10 202 | case "badsig", "badvers": 203 | rc = 16 204 | case "badkey": 205 | rc = 17 206 | case "badtime": 207 | rc = 18 208 | case "badmode": 209 | rc = 19 210 | case "badname": 211 | rc = 20 212 | case "badalg": 213 | rc = 21 214 | case "badtrunc": 215 | rc = 22 216 | case "badcookie": 217 | rc = 23 218 | default: 219 | var err error 220 | rc, err = strconv.Atoi(rcode) 221 | if err != nil { 222 | return errors.New("invalid rcode value") 223 | } 224 | } 225 | 226 | options.rcodes[rc] = struct{}{} 227 | } 228 | 229 | options.hasRCodes = options.RCode != "" 230 | 231 | // Set rcode to 0 if none was specified 232 | if len(options.rcodes) == 0 { 233 | options.rcodes[0] = struct{}{} 234 | } 235 | 236 | return nil 237 | } 238 | 239 | func (options *Options) configureResume() error { 240 | options.resumeCfg = &ResumeCfg{} 241 | if options.Resume && fileutil.FileExists(DefaultResumeFile) { 242 | return goconfig.Load(&options.resumeCfg, DefaultResumeFile) 243 | 244 | } 245 | return nil 246 | } 247 | 248 | func createGroup(flagSet *goflags.FlagSet, groupName, description string, flags ...*goflags.FlagData) { 249 | flagSet.SetGroup(groupName, description) 250 | for _, currentFlag := range flags { 251 | currentFlag.Group(groupName) 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | dnsx 3 |
4 |

5 | 6 | 7 |

Fast and multi-purpose DNS toolkit allow to run multiple DNS queries.

8 | 9 | 10 | 11 |

12 | 13 | 14 | 15 | 16 | 17 |

18 | 19 |

20 | Features • 21 | Installation • 22 | Usage • 23 | Running dnsx • 24 | Wildcard • 25 | Notes • 26 | Join Discord 27 |

28 | 29 | 30 | --- 31 | 32 | 33 | **dnsx** is a fast and multi-purpose DNS toolkit allow to run multiple probes using [retryabledns](https://github.com/projectdiscovery/retryabledns) library, that allows you to perform multiple DNS queries of your choice with a list of user supplied resolvers, additionally supports DNS wildcard filtering like [shuffledns](https://github.com/projectdiscovery/shuffledns). 34 | 35 | 36 | # Features 37 | 38 |

39 | dnsx 40 |
41 |

42 | 43 | 44 | - Simple and Handy utility to query DNS records. 45 | - Supports **A, AAAA, CNAME, PTR, NS, MX, TXT, SOA** 46 | - Supports DNS Status Code probing 47 | - Supports DNS Tracing 48 | - Handles wildcard subdomains in automated way. 49 | - **Stdin** and **stdout** support to work with other tools. 50 | 51 | # Installation Instructions 52 | 53 | 54 | dnsx requires **go1.17** to install successfully. Run the following command to get the repo - 55 | 56 | ```sh 57 | go install -v github.com/projectdiscovery/dnsx/cmd/dnsx@latest 58 | ``` 59 | 60 | # Usage 61 | 62 | ```sh 63 | dnsx -h 64 | ``` 65 | 66 | This will display help for the tool. Here are all the switches it supports. 67 | 68 | ```console 69 | INPUT: 70 | -l, -list string File input with list of sub(domains)/hosts 71 | 72 | QUERY: 73 | -a Query A record (default) 74 | -aaaa Query AAAA record 75 | -cname Query CNAME record 76 | -ns Query NS record 77 | -txt Query TXT record 78 | -ptr Query PTR record 79 | -mx Query MX record 80 | -soa Query SOA record 81 | 82 | FILTERS: 83 | -resp Display DNS response 84 | -resp-only Display DNS response only 85 | -rcode, -rc string Display DNS status code (eg. -rcode noerror,servfail,refused) 86 | 87 | RATE-LIMIT: 88 | -t, -c int Number of concurrent threads to use (default 100) 89 | -rl, -rate-limit int Number of DNS request/second (disabled as default) (default -1) 90 | 91 | OUTPUT: 92 | -o, -output string File to write output (optional) 93 | -json Write output in JSONL(ines) format 94 | 95 | DEBUG: 96 | -silent Show only results in the output 97 | -v, -verbose Verbose output 98 | -raw, -debug Display RAW DNS response 99 | -stats Display stats of the running scan 100 | -version Show version of dnsx 101 | 102 | OPTIMIZATION: 103 | -retry int Number of DNS retries (default 1) 104 | -hf, -hostsfile Parse system host file 105 | -trace Perform DNS trace 106 | -trace-max-recursion int Max recursion for dns trace (default 32767) 107 | -flush-interval int Flush interval of output file (default 10) 108 | -resume Resume 109 | 110 | CONFIGURATIONS: 111 | -r, -resolver string List of resolvers (file or comma separated) 112 | -wt, -wildcard-threshold int Wildcard Filter Threshold (default 5) 113 | -wd, -wildcard-domain string Domain name for wildcard filtering (other flags will be ignored) 114 | ``` 115 | 116 | ### Running dnsx 117 | 118 | **dnsx** can be used to filter dead records from the list of passive subdomains obtained from various sources, for example:- 119 | 120 | ```sh 121 | subfinder -silent -d hackerone.com | dnsx -silent 122 | 123 | a.ns.hackerone.com 124 | www.hackerone.com 125 | api.hackerone.com 126 | docs.hackerone.com 127 | mta-sts.managed.hackerone.com 128 | mta-sts.hackerone.com 129 | resources.hackerone.com 130 | b.ns.hackerone.com 131 | mta-sts.forwarding.hackerone.com 132 | events.hackerone.com 133 | support.hackerone.com 134 | ``` 135 | 136 | **dnsx** can be used to print **A** records for the given list of subdomains, for example:- 137 | 138 | ```sh 139 | subfinder -silent -d hackerone.com | dnsx -silent -a -resp 140 | 141 | a.ns.hackerone.com [162.159.0.31] 142 | b.ns.hackerone.com [162.159.1.31] 143 | mta-sts.hackerone.com [185.199.108.153] 144 | events.hackerone.com [208.100.11.134] 145 | mta-sts.managed.hackerone.com [185.199.108.153] 146 | resources.hackerone.com [52.60.160.16] 147 | resources.hackerone.com [52.60.165.183] 148 | www.hackerone.com [104.16.100.52] 149 | support.hackerone.com [104.16.53.111] 150 | ``` 151 | 152 | 153 | **dnsx** can be used to extract **A** records for the given list of subdomains, for example:- 154 | 155 | ```sh 156 | subfinder -silent -d hackerone.com | dnsx -silent -a -resp-only 157 | 158 | 104.16.99.52 159 | 104.16.100.52 160 | 162.159.1.31 161 | 104.16.99.52 162 | 104.16.100.52 163 | 185.199.110.153 164 | 185.199.111.153 165 | 185.199.108.153 166 | 185.199.109.153 167 | 104.16.99.52 168 | 104.16.100.52 169 | 104.16.51.111 170 | 104.16.53.111 171 | 185.199.108.153 172 | 185.199.111.153 173 | 185.199.110.153 174 | 185.199.111.153 175 | ``` 176 | 177 | **dnsx** can be used to extract **CNAME** records for the given list of subdomains, for example:- 178 | 179 | ```sh 180 | subfinder -silent -d hackerone.com | dnsx -silent -cname -resp 181 | 182 | support.hackerone.com [hackerone.zendesk.com] 183 | resources.hackerone.com [read.uberflip.com] 184 | mta-sts.hackerone.com [hacker0x01.github.io] 185 | mta-sts.forwarding.hackerone.com [hacker0x01.github.io] 186 | events.hackerone.com [whitelabel.bigmarker.com] 187 | ``` 188 | 189 | **dnsx** can be used to probe [DNS Staus code](https://github.com/projectdiscovery/dnsx/wiki/RCODE-ID-VALUE-Mapping) on given list of subdomains, for example:- 190 | 191 | ```sh 192 | subfinder -silent -d hackerone.com | dnsx -silent -rcode noerror,servfail,refused 193 | 194 | ns.hackerone.com [NOERROR] 195 | a.ns.hackerone.com [NOERROR] 196 | b.ns.hackerone.com [NOERROR] 197 | support.hackerone.com [NOERROR] 198 | resources.hackerone.com [NOERROR] 199 | mta-sts.hackerone.com [NOERROR] 200 | www.hackerone.com [NOERROR] 201 | mta-sts.forwarding.hackerone.com [NOERROR] 202 | docs.hackerone.com [NOERROR] 203 | ``` 204 | 205 | **dnsx** can be used to extract subdomains from given network range using `PTR` query, for example:- 206 | 207 | ```sh 208 | echo 173.0.84.0/24 | dnsx -silent -resp-only -ptr 209 | 210 | cors.api.paypal.com 211 | trinityadminauth.paypal.com 212 | cld-edge-origin-api.paypal.com 213 | appmanagement.paypal.com 214 | svcs.paypal.com 215 | trinitypie-serv.paypal.com 216 | ppn.paypal.com 217 | pointofsale-new.paypal.com 218 | pointofsale.paypal.com 219 | slc-a-origin-pointofsale.paypal.com 220 | fpdbs.paypal.com 221 | ``` 222 | 223 | 224 | 225 | ### Wildcard filtering 226 | 227 | A special feature of **dnsx** is its ability to handle **multi-level DNS based wildcards** and do it so with very less number of DNS requests. Sometimes all the subdomains will resolve which will lead to lots of garbage in the results. The way **dnsx** handles this is it will keep track of how many subdomains point to an IP and if the count of the Subdomains increase beyond a certain small threshold, it will check for wildcard on all the levels of the hosts for that IP iteratively. 228 | 229 | ```sh 230 | dnsx -l airbnb-subs.txt -wd airbnb.com -o output.txt 231 | ``` 232 | 233 | # 📋 Notes 234 | 235 | - As default, **dnsx** checks for **A** record. 236 | - As default dnsx uses Google, Cloudflare, Quad9 [resolver](https://github.com/projectdiscovery/dnsx/blob/43af78839e237ea8cbafe571df1ab0d6cbe7f445/libs/dnsx/dnsx.go#L31). 237 | - Custom resolver list can be used using `r` flag. 238 | - Domain name input is mandatory for wildcard elimination. 239 | - DNS record flag can not be used when using wildcard filtering. 240 | 241 | dnsx is made with 🖤 by the [projectdiscovery](https://projectdiscovery.io) team. 242 | -------------------------------------------------------------------------------- /internal/runner/runner.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "strings" 8 | "sync" 9 | "time" 10 | 11 | "github.com/miekg/dns" 12 | "github.com/projectdiscovery/clistats" 13 | "github.com/projectdiscovery/dnsx/libs/dnsx" 14 | "github.com/projectdiscovery/fileutil" 15 | "github.com/projectdiscovery/goconfig" 16 | "github.com/projectdiscovery/gologger" 17 | "github.com/projectdiscovery/hmap/store/hybrid" 18 | "github.com/projectdiscovery/iputil" 19 | "github.com/projectdiscovery/mapcidr" 20 | retryabledns "github.com/projectdiscovery/retryabledns" 21 | "go.uber.org/ratelimit" 22 | ) 23 | 24 | // Runner is a client for running the enumeration process. 25 | type Runner struct { 26 | options *Options 27 | dnsx *dnsx.DNSX 28 | wgoutputworker *sync.WaitGroup 29 | wgresolveworkers *sync.WaitGroup 30 | wgwildcardworker *sync.WaitGroup 31 | workerchan chan string 32 | outputchan chan string 33 | wildcardworkerchan chan string 34 | wildcards map[string]struct{} 35 | wildcardsmutex sync.RWMutex 36 | wildcardscache map[string][]string 37 | wildcardscachemutex sync.Mutex 38 | limiter ratelimit.Limiter 39 | hm *hybrid.HybridMap 40 | stats clistats.StatisticsClient 41 | } 42 | 43 | func New(options *Options) (*Runner, error) { 44 | dnsxOptions := dnsx.DefaultOptions 45 | dnsxOptions.MaxRetries = options.Retries 46 | dnsxOptions.TraceMaxRecursion = options.TraceMaxRecursion 47 | dnsxOptions.Hostsfile = options.HostsFile 48 | 49 | if options.Resolvers != "" { 50 | dnsxOptions.BaseResolvers = []string{} 51 | // If it's a file load resolvers from it 52 | if fileutil.FileExists(options.Resolvers) { 53 | rs, err := linesInFile(options.Resolvers) 54 | if err != nil { 55 | gologger.Fatal().Msgf("%s\n", err) 56 | } 57 | for _, rr := range rs { 58 | dnsxOptions.BaseResolvers = append(dnsxOptions.BaseResolvers, prepareResolver(rr)) 59 | } 60 | } else { 61 | // otherwise gets comma separated ones 62 | for _, rr := range strings.Split(options.Resolvers, ",") { 63 | dnsxOptions.BaseResolvers = append(dnsxOptions.BaseResolvers, prepareResolver(rr)) 64 | } 65 | } 66 | } 67 | 68 | var questionTypes []uint16 69 | if options.A { 70 | questionTypes = append(questionTypes, dns.TypeA) 71 | } 72 | if options.AAAA { 73 | questionTypes = append(questionTypes, dns.TypeAAAA) 74 | } 75 | if options.CNAME { 76 | questionTypes = append(questionTypes, dns.TypeCNAME) 77 | } 78 | if options.PTR { 79 | questionTypes = append(questionTypes, dns.TypePTR) 80 | } 81 | if options.SOA { 82 | questionTypes = append(questionTypes, dns.TypeSOA) 83 | } 84 | if options.TXT { 85 | questionTypes = append(questionTypes, dns.TypeTXT) 86 | } 87 | if options.MX { 88 | questionTypes = append(questionTypes, dns.TypeMX) 89 | } 90 | if options.NS { 91 | questionTypes = append(questionTypes, dns.TypeNS) 92 | } 93 | // If no option is specified or wildcard filter has been requested use query type A 94 | if len(questionTypes) == 0 || options.WildcardDomain != "" { 95 | options.A = true 96 | questionTypes = append(questionTypes, dns.TypeA) 97 | } 98 | dnsxOptions.QuestionTypes = questionTypes 99 | 100 | dnsX, err := dnsx.New(dnsxOptions) 101 | if err != nil { 102 | return nil, err 103 | } 104 | 105 | limiter := ratelimit.NewUnlimited() 106 | if options.RateLimit > 0 { 107 | limiter = ratelimit.New(options.RateLimit) 108 | } 109 | 110 | hm, err := hybrid.New(hybrid.DefaultDiskOptions) 111 | if err != nil { 112 | return nil, err 113 | } 114 | 115 | var stats clistats.StatisticsClient 116 | if options.ShowStatistics { 117 | stats, err = clistats.New() 118 | if err != nil { 119 | return nil, err 120 | } 121 | } 122 | 123 | r := Runner{ 124 | options: options, 125 | dnsx: dnsX, 126 | wgoutputworker: &sync.WaitGroup{}, 127 | wgresolveworkers: &sync.WaitGroup{}, 128 | wgwildcardworker: &sync.WaitGroup{}, 129 | workerchan: make(chan string), 130 | wildcardworkerchan: make(chan string), 131 | wildcards: make(map[string]struct{}), 132 | wildcardscache: make(map[string][]string), 133 | limiter: limiter, 134 | hm: hm, 135 | stats: stats, 136 | } 137 | 138 | return &r, nil 139 | } 140 | 141 | func (r *Runner) InputWorker() { 142 | r.hm.Scan(func(k, _ []byte) error { 143 | if r.options.ShowStatistics { 144 | r.stats.IncrementCounter("requests", len(r.dnsx.Options.QuestionTypes)) 145 | } 146 | item := string(k) 147 | if r.options.resumeCfg != nil { 148 | r.options.resumeCfg.current = item 149 | r.options.resumeCfg.currentIndex++ 150 | if r.options.resumeCfg.currentIndex <= r.options.resumeCfg.Index { 151 | return nil 152 | } 153 | } 154 | r.workerchan <- item 155 | return nil 156 | }) 157 | close(r.workerchan) 158 | } 159 | 160 | func (r *Runner) prepareInput() error { 161 | // process file if specified 162 | var f *os.File 163 | stat, _ := os.Stdin.Stat() 164 | if r.options.Hosts != "" { 165 | var err error 166 | f, err = os.Open(r.options.Hosts) 167 | if err != nil { 168 | return err 169 | } 170 | defer f.Close() 171 | } else if (stat.Mode() & os.ModeCharDevice) == 0 { 172 | f = os.Stdin 173 | } else { 174 | return fmt.Errorf("hosts file or stdin not provided") 175 | } 176 | 177 | numHosts := 0 178 | sc := bufio.NewScanner(f) 179 | for sc.Scan() { 180 | item := strings.TrimSpace(sc.Text()) 181 | hosts := []string{item} 182 | if iputil.IsCIDR(item) { 183 | hosts, _ = mapcidr.IPAddresses(item) 184 | } 185 | for _, host := range hosts { 186 | // Used just to get the exact number of targets 187 | if _, ok := r.hm.Get(host); ok { 188 | continue 189 | } 190 | numHosts++ 191 | // nolint:errcheck 192 | r.hm.Set(host, nil) 193 | } 194 | } 195 | 196 | if r.options.ShowStatistics { 197 | r.stats.AddStatic("hosts", numHosts) 198 | r.stats.AddStatic("startedAt", time.Now()) 199 | r.stats.AddCounter("requests", 0) 200 | r.stats.AddCounter("total", uint64(numHosts*len(r.dnsx.Options.QuestionTypes))) 201 | // nolint:errcheck 202 | r.stats.Start(makePrintCallback(), time.Duration(5)*time.Second) 203 | } 204 | 205 | return nil 206 | } 207 | 208 | // nolint:deadcode 209 | func makePrintCallback() func(stats clistats.StatisticsClient) { 210 | builder := &strings.Builder{} 211 | return func(stats clistats.StatisticsClient) { 212 | builder.WriteRune('[') 213 | startedAt, _ := stats.GetStatic("startedAt") 214 | duration := time.Since(startedAt.(time.Time)) 215 | builder.WriteString(fmtDuration(duration)) 216 | builder.WriteRune(']') 217 | 218 | hosts, _ := stats.GetStatic("hosts") 219 | builder.WriteString(" | Hosts: ") 220 | builder.WriteString(clistats.String(hosts)) 221 | 222 | requests, _ := stats.GetCounter("requests") 223 | total, _ := stats.GetCounter("total") 224 | 225 | builder.WriteString(" | RPS: ") 226 | builder.WriteString(clistats.String(uint64(float64(requests) / duration.Seconds()))) 227 | 228 | builder.WriteString(" | Requests: ") 229 | builder.WriteString(clistats.String(requests)) 230 | builder.WriteRune('/') 231 | builder.WriteString(clistats.String(total)) 232 | builder.WriteRune(' ') 233 | builder.WriteRune('(') 234 | //nolint:gomnd // this is not a magic number 235 | builder.WriteString(clistats.String(uint64(float64(requests) / float64(total) * 100.0))) 236 | builder.WriteRune('%') 237 | builder.WriteRune(')') 238 | builder.WriteRune('\n') 239 | 240 | fmt.Fprintf(os.Stderr, "%s", builder.String()) 241 | builder.Reset() 242 | } 243 | } 244 | 245 | // SaveResumeConfig to file 246 | func (r *Runner) SaveResumeConfig() error { 247 | var resumeCfg ResumeCfg 248 | resumeCfg.Index = r.options.resumeCfg.currentIndex 249 | resumeCfg.ResumeFrom = r.options.resumeCfg.current 250 | return goconfig.Save(resumeCfg, DefaultResumeFile) 251 | } 252 | 253 | func (r *Runner) Run() error { 254 | err := r.prepareInput() 255 | if err != nil { 256 | return err 257 | } 258 | 259 | // if resume is enabled inform the user 260 | if r.options.ShouldLoadResume() && r.options.resumeCfg.Index > 0 { 261 | gologger.Debug().Msgf("Resuming scan using file %s. Restarting at position %d: %s\n", DefaultResumeFile, r.options.resumeCfg.Index, r.options.resumeCfg.ResumeFrom) 262 | } 263 | 264 | r.startWorkers() 265 | 266 | r.wgresolveworkers.Wait() 267 | if r.stats != nil { 268 | err = r.stats.Stop() 269 | if err != nil { 270 | return err 271 | } 272 | } 273 | 274 | close(r.outputchan) 275 | r.wgoutputworker.Wait() 276 | 277 | if r.options.WildcardDomain != "" { 278 | gologger.Print().Msgf("Starting to filter wildcard subdomains\n") 279 | ipDomain := make(map[string]map[string]struct{}) 280 | listIPs := []string{} 281 | // prepare in memory structure similarly to shuffledns 282 | r.hm.Scan(func(k, v []byte) error { 283 | var dnsdata retryabledns.DNSData 284 | err := dnsdata.Unmarshal(v) 285 | if err != nil { 286 | // the item has no record - ignore 287 | return nil 288 | } 289 | 290 | for _, a := range dnsdata.A { 291 | _, ok := ipDomain[a] 292 | if !ok { 293 | ipDomain[a] = make(map[string]struct{}) 294 | listIPs = append(listIPs, a) 295 | } 296 | ipDomain[a][string(k)] = struct{}{} 297 | } 298 | 299 | return nil 300 | }) 301 | 302 | // wildcard workers 303 | numThreads := r.options.Threads 304 | if numThreads > len(listIPs) { 305 | numThreads = len(listIPs) 306 | } 307 | for i := 0; i < numThreads; i++ { 308 | r.wgwildcardworker.Add(1) 309 | go r.wildcardWorker() 310 | } 311 | 312 | seen := make(map[string]struct{}) 313 | for _, a := range listIPs { 314 | hosts := ipDomain[a] 315 | if len(hosts) >= r.options.WildcardThreshold { 316 | for host := range hosts { 317 | if _, ok := seen[host]; !ok { 318 | seen[host] = struct{}{} 319 | r.wildcardworkerchan <- host 320 | } 321 | } 322 | } 323 | } 324 | close(r.wildcardworkerchan) 325 | r.wgwildcardworker.Wait() 326 | 327 | // we need to restart output 328 | r.startOutputWorker() 329 | seen = make(map[string]struct{}) 330 | seenRemovedSubdomains := make(map[string]struct{}) 331 | numRemovedSubdomains := 0 332 | for _, A := range listIPs { 333 | for host := range ipDomain[A] { 334 | if host == r.options.WildcardDomain { 335 | if _, ok := seen[host]; !ok { 336 | seen[host] = struct{}{} 337 | r.outputchan <- host 338 | } 339 | } else if _, ok := r.wildcards[host]; !ok { 340 | if _, ok := seen[host]; !ok { 341 | seen[host] = struct{}{} 342 | r.outputchan <- host 343 | } 344 | } else { 345 | if _, ok := seenRemovedSubdomains[host]; !ok { 346 | numRemovedSubdomains++ 347 | seenRemovedSubdomains[host] = struct{}{} 348 | } 349 | } 350 | } 351 | } 352 | close(r.outputchan) 353 | // waiting output worker 354 | r.wgoutputworker.Wait() 355 | gologger.Print().Msgf("%d wildcard subdomains removed\n", numRemovedSubdomains) 356 | } 357 | 358 | return nil 359 | } 360 | 361 | func (r *Runner) HandleOutput() { 362 | defer r.wgoutputworker.Done() 363 | 364 | // setup output 365 | var ( 366 | foutput *os.File 367 | w *bufio.Writer 368 | ) 369 | if r.options.OutputFile != "" { 370 | var err error 371 | foutput, err = os.OpenFile(r.options.OutputFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 372 | if err != nil { 373 | gologger.Fatal().Msgf("%s\n", err) 374 | } 375 | defer foutput.Close() 376 | w = bufio.NewWriter(foutput) 377 | defer w.Flush() 378 | var flushTicker *time.Ticker 379 | if r.options.FlushInterval >= 0 { 380 | flushTicker = time.NewTicker(time.Duration(r.options.FlushInterval) * time.Second) 381 | defer flushTicker.Stop() 382 | go func() { 383 | for range flushTicker.C { 384 | w.Flush() 385 | } 386 | }() 387 | } 388 | } 389 | for item := range r.outputchan { 390 | if r.options.OutputFile != "" { 391 | // uses a buffer to write to file 392 | // nolint:errcheck 393 | w.WriteString(item + "\n") 394 | } 395 | // otherwise writes sequentially to stdout 396 | gologger.Silent().Msgf("%s\n", item) 397 | } 398 | } 399 | 400 | func (r *Runner) startOutputWorker() { 401 | // output worker 402 | r.outputchan = make(chan string) 403 | r.wgoutputworker.Add(1) 404 | go r.HandleOutput() 405 | } 406 | 407 | func (r *Runner) startWorkers() { 408 | go r.InputWorker() 409 | 410 | r.startOutputWorker() 411 | // resolve workers 412 | for i := 0; i < r.options.Threads; i++ { 413 | r.wgresolveworkers.Add(1) 414 | go r.worker() 415 | } 416 | } 417 | 418 | func (r *Runner) worker() { 419 | defer r.wgresolveworkers.Done() 420 | 421 | for domain := range r.workerchan { 422 | if isURL(domain) { 423 | domain = extractDomain(domain) 424 | } 425 | r.limiter.Take() 426 | 427 | // Ignoring errors as partial results are still good 428 | dnsData, _ := r.dnsx.QueryMultiple(domain) 429 | // Just skipping nil responses (in case of critical errors) 430 | if dnsData == nil { 431 | continue 432 | } 433 | 434 | if dnsData.Host == "" || dnsData.Timestamp.IsZero() { 435 | continue 436 | } 437 | 438 | // skip responses not having the expected response code 439 | if len(r.options.rcodes) > 0 { 440 | if _, ok := r.options.rcodes[dnsData.StatusCodeRaw]; !ok { 441 | continue 442 | } 443 | } 444 | 445 | if !r.options.Raw { 446 | dnsData.Raw = "" 447 | } 448 | 449 | if r.options.Trace { 450 | dnsData.TraceData, _ = r.dnsx.Trace(domain) 451 | if dnsData.TraceData != nil { 452 | for _, data := range dnsData.TraceData.DNSData { 453 | if r.options.Raw && data.RawResp != nil { 454 | rawRespString := data.RawResp.String() 455 | data.Raw = rawRespString 456 | // join the whole chain in raw field 457 | dnsData.Raw += fmt.Sprintln(rawRespString) 458 | } 459 | data.RawResp = nil 460 | } 461 | } 462 | } 463 | 464 | // if wildcard filtering just store the data 465 | if r.options.WildcardDomain != "" { 466 | // nolint:errcheck 467 | r.storeDNSData(dnsData) 468 | continue 469 | } 470 | if r.options.JSON { 471 | jsons, _ := dnsData.JSON() 472 | r.outputchan <- jsons 473 | continue 474 | } 475 | if r.options.Raw { 476 | r.outputchan <- dnsData.Raw 477 | continue 478 | } 479 | if r.options.hasRCodes { 480 | r.outputResponseCode(domain, dnsData.StatusCodeRaw) 481 | continue 482 | } 483 | if r.options.A { 484 | r.outputRecordType(domain, dnsData.A) 485 | } 486 | if r.options.AAAA { 487 | r.outputRecordType(domain, dnsData.AAAA) 488 | } 489 | if r.options.CNAME { 490 | r.outputRecordType(domain, dnsData.CNAME) 491 | } 492 | if r.options.PTR { 493 | r.outputRecordType(domain, dnsData.PTR) 494 | } 495 | if r.options.MX { 496 | r.outputRecordType(domain, dnsData.MX) 497 | } 498 | if r.options.NS { 499 | r.outputRecordType(domain, dnsData.NS) 500 | } 501 | if r.options.SOA { 502 | r.outputRecordType(domain, dnsData.SOA) 503 | } 504 | if r.options.TXT { 505 | r.outputRecordType(domain, dnsData.TXT) 506 | } 507 | } 508 | } 509 | 510 | func (r *Runner) outputRecordType(domain string, items []string) { 511 | for _, item := range items { 512 | item := strings.ToLower(item) 513 | if r.options.ResponseOnly { 514 | r.outputchan <- item 515 | } else if r.options.Response { 516 | r.outputchan <- domain + " [" + item + "]" 517 | } else { 518 | // just prints out the domain if it has a record type and exit 519 | r.outputchan <- domain 520 | break 521 | } 522 | } 523 | } 524 | 525 | func (r *Runner) outputResponseCode(domain string, responsecode int) { 526 | responseCodeExt, ok := dns.RcodeToString[responsecode] 527 | if ok { 528 | r.outputchan <- domain + " [" + responseCodeExt + "]" 529 | } 530 | } 531 | 532 | func (r *Runner) storeDNSData(dnsdata *retryabledns.DNSData) error { 533 | data, err := dnsdata.Marshal() 534 | if err != nil { 535 | return err 536 | } 537 | return r.hm.Set(dnsdata.Host, data) 538 | } 539 | 540 | // Close running instance 541 | func (r *Runner) Close() { 542 | r.hm.Close() 543 | } 544 | 545 | func (r *Runner) wildcardWorker() { 546 | defer r.wgwildcardworker.Done() 547 | 548 | for { 549 | host, more := <-r.wildcardworkerchan 550 | if !more { 551 | break 552 | } 553 | 554 | if r.IsWildcard(host) { 555 | // mark this host as a wildcard subdomain 556 | r.wildcardsmutex.Lock() 557 | r.wildcards[host] = struct{}{} 558 | r.wildcardsmutex.Unlock() 559 | } 560 | } 561 | } 562 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI= 2 | github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= 3 | github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ= 4 | github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4= 5 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 6 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 10 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 11 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 12 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 13 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 14 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 15 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 16 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 17 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 18 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 19 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 20 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 21 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 22 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 23 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 24 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 25 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 26 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 27 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 28 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 29 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 30 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 31 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 32 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 33 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 34 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 35 | github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= 36 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 37 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 38 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 39 | github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= 40 | github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= 41 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 42 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 43 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 44 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 45 | github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= 46 | github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= 47 | github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= 48 | github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= 49 | github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= 50 | github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= 51 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 52 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 53 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 54 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 55 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 56 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 57 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 58 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 59 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 60 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 61 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 62 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 63 | github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= 64 | github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= 65 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 66 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 67 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 68 | github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= 69 | github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= 70 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 71 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 72 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 73 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 74 | github.com/projectdiscovery/blackrock v0.0.0-20210415162320-b38689ae3a2e h1:7bwaFH1jvtOo5ndhTQgoA349ozhX+1dc4b6tbaPnBOA= 75 | github.com/projectdiscovery/blackrock v0.0.0-20210415162320-b38689ae3a2e/go.mod h1:/IsapnEYiWG+yEDPXp0e8NWj3npzB9Ccy9lXEUJwMZs= 76 | github.com/projectdiscovery/clistats v0.0.8 h1:tjmWb15mqsPf/yrQXVHLe2ThZX/5+mgKSfZBKWWLh20= 77 | github.com/projectdiscovery/clistats v0.0.8/go.mod h1:lV6jUHAv2bYWqrQstqW8iVIydKJhWlVaLl3Xo9ioVGg= 78 | github.com/projectdiscovery/fileutil v0.0.0-20210926202739-6050d0acf73c h1:KDmCXhLLnS/Gc1VDyTxxamRzc8OmHCm1X+f8WQoaTRs= 79 | github.com/projectdiscovery/fileutil v0.0.0-20210926202739-6050d0acf73c/go.mod h1:U+QCpQnX8o2N2w0VUGyAzjM3yBAe4BKedVElxiImsx0= 80 | github.com/projectdiscovery/goconfig v0.0.0-20210804090219-f893ccd0c69c h1:1XRSp+44bhWudAWz+2+wHYJBHvDfE8mk9uWpzX+DU9k= 81 | github.com/projectdiscovery/goconfig v0.0.0-20210804090219-f893ccd0c69c/go.mod h1:mBv7GRD5n3WNbFE9blG8ynzXTM5eh9MmwaK6EOyn6Pk= 82 | github.com/projectdiscovery/goflags v0.0.7 h1:aykmRkrOgDyRwcvGrK3qp+9aqcjGfAMs/+LtRmtyxwk= 83 | github.com/projectdiscovery/goflags v0.0.7/go.mod h1:Jjwsf4eEBPXDSQI2Y+6fd3dBumJv/J1U0nmpM+hy2YY= 84 | github.com/projectdiscovery/gologger v1.0.1/go.mod h1:Ok+axMqK53bWNwDSU1nTNwITLYMXMdZtRc8/y1c7sWE= 85 | github.com/projectdiscovery/gologger v1.1.4 h1:qWxGUq7ukHWT849uGPkagPKF3yBPYAsTtMKunQ8O2VI= 86 | github.com/projectdiscovery/gologger v1.1.4/go.mod h1:Bhb6Bdx2PV1nMaFLoXNBmHIU85iROS9y1tBuv7T5pMY= 87 | github.com/projectdiscovery/hmap v0.0.1 h1:VAONbJw5jP+syI5smhsfkrq9XPGn4aiYy5pR6KR1wog= 88 | github.com/projectdiscovery/hmap v0.0.1/go.mod h1:VDEfgzkKQdq7iGTKz8Ooul0NuYHQ8qiDs6r8bPD1Sb0= 89 | github.com/projectdiscovery/ipranger v0.0.2/go.mod h1:kcAIk/lo5rW+IzUrFkeYyXnFJ+dKwYooEOHGVPP/RWE= 90 | github.com/projectdiscovery/iputil v0.0.0-20210804143329-3a30fcde43f3 h1:VZ9H51A7tI7R/9I5W5l960Nkq7eMJqGd3y1wsuwzdjE= 91 | github.com/projectdiscovery/iputil v0.0.0-20210804143329-3a30fcde43f3/go.mod h1:blmYJkS8lSrrx3QcmcgS2tZIxlojeVmoGeA9twslCBU= 92 | github.com/projectdiscovery/mapcidr v0.0.4/go.mod h1:ALOIj6ptkWujNoX8RdQwB2mZ+kAmKuLJBq9T5gR5wG0= 93 | github.com/projectdiscovery/mapcidr v0.0.7/go.mod h1:7CzdUdjuLVI0s33dQ33lWgjg3vPuLFw2rQzZ0RxkT00= 94 | github.com/projectdiscovery/mapcidr v0.0.8 h1:16U05F2x3o/jSTsxSCY2hCuCs9xOSwVxjo2zlsL4L4E= 95 | github.com/projectdiscovery/mapcidr v0.0.8/go.mod h1:7CzdUdjuLVI0s33dQ33lWgjg3vPuLFw2rQzZ0RxkT00= 96 | github.com/projectdiscovery/reflectutil v0.0.0-20210804085554-4d90952bf92f h1:HR3R/nhELwLXufUlO1ZkKVqrZl4lN1cWFBdN8RcMuLo= 97 | github.com/projectdiscovery/reflectutil v0.0.0-20210804085554-4d90952bf92f/go.mod h1:3L0WfNIcVWXIDur8k+gKDLZLWY2F+rs0SQXtcn/3AYU= 98 | github.com/projectdiscovery/retryabledns v1.0.13-0.20210927160332-db15799e2e4d h1:tFPD9+3lq+XMHpQ/zMMp41RdXEl90h+KZli7UQt1LNY= 99 | github.com/projectdiscovery/retryabledns v1.0.13-0.20210927160332-db15799e2e4d/go.mod h1:c5lCypH3Wv7PNRHFsTbmTWlOAV502VlqUmS9A/2wlNU= 100 | github.com/projectdiscovery/retryablehttp-go v1.0.2 h1:LV1/KAQU+yeWhNVlvveaYFsjBYRwXlNEq0PvrezMV0U= 101 | github.com/projectdiscovery/retryablehttp-go v1.0.2/go.mod h1:dx//aY9V247qHdsRf0vdWHTBZuBQ2vm6Dq5dagxrDYI= 102 | github.com/projectdiscovery/stringsutil v0.0.0-20210804142656-fd3c28dbaafe/go.mod h1:oTRc18WBv9t6BpaN9XBY+QmG28PUpsyDzRht56Qf49I= 103 | github.com/projectdiscovery/stringsutil v0.0.0-20210823090203-2f5f137e8e1d/go.mod h1:oTRc18WBv9t6BpaN9XBY+QmG28PUpsyDzRht56Qf49I= 104 | github.com/projectdiscovery/stringsutil v0.0.0-20210830151154-f567170afdd9 h1:xbL1/7h0k6HE3RzPdYk9W/8pUxESrGWewTaZdIB5Pes= 105 | github.com/projectdiscovery/stringsutil v0.0.0-20210830151154-f567170afdd9/go.mod h1:oTRc18WBv9t6BpaN9XBY+QmG28PUpsyDzRht56Qf49I= 106 | github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4= 107 | github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 108 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= 109 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 110 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= 111 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 112 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 113 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 114 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 115 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 116 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 117 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 118 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 119 | github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= 120 | github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= 121 | github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= 122 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 123 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 124 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 125 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 126 | go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA= 127 | go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg= 128 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 129 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 130 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 131 | golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 132 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 133 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 134 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 135 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 136 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 137 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 138 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 139 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 140 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 141 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 142 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= 143 | golang.org/x/net v0.0.0-20210521195947-fe42d452be8f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 144 | golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk= 145 | golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 146 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 147 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 148 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 149 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 150 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 151 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 152 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 153 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 154 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 155 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 156 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 157 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 158 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 159 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 160 | golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 161 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 162 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 163 | golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 164 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 165 | golang.org/x/sys v0.0.0-20210915083310-ed5796bab164 h1:7ZDGnxgHAMw7thfC5bEos0RDAccZKxioiWBhfIe+tvw= 166 | golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 167 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 168 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 169 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 170 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 171 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 172 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 173 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 174 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 175 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 176 | golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 177 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 178 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 179 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 180 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 181 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 182 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 183 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 184 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 185 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 186 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 187 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 188 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 189 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 190 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 191 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 192 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 193 | gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= 194 | gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 195 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 196 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 197 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 198 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 199 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 200 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 201 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 202 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 203 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 204 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 205 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 206 | --------------------------------------------------------------------------------