├── CODEOWNERS ├── .gitignore ├── internal ├── option │ ├── vars.go │ ├── banner.go │ ├── options.go │ ├── parse.go │ └── const.go ├── executor │ ├── vars.go │ ├── util.go │ └── executor.go ├── errors │ └── errors.go └── runner │ ├── runner.go │ ├── util.go │ └── validator.go ├── pkg ├── fingerprint │ ├── dns.go │ ├── check.go │ └── fingerprint.go └── dnstake │ ├── resolv.go │ └── dnstake.go ├── .github ├── dependabot.yaml └── workflows │ ├── release.yaml │ ├── pr.yaml │ └── gosum.yaml ├── go.mod ├── .goreleaser.yaml ├── cmd └── dnstake │ └── dnstake.go ├── LICENSE ├── README.md └── go.sum /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @dwisiswant0 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/* 2 | dist/* 3 | *.txt 4 | dnstake -------------------------------------------------------------------------------- /internal/option/vars.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | var opt *Options 4 | -------------------------------------------------------------------------------- /internal/executor/vars.go: -------------------------------------------------------------------------------- 1 | package executor 2 | 3 | import "sync" 4 | 5 | var ( 6 | mu sync.Mutex 7 | ) 8 | -------------------------------------------------------------------------------- /internal/executor/util.go: -------------------------------------------------------------------------------- 1 | package executor 2 | 3 | func find(slice []int, val int) (int, bool) { 4 | for i, item := range slice { 5 | if item == val { 6 | return i, true 7 | } 8 | } 9 | 10 | return -1, false 11 | } 12 | -------------------------------------------------------------------------------- /internal/option/banner.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/logrusorgru/aurora" 8 | ) 9 | 10 | func showBanner() { 11 | fmt.Fprintf(os.Stderr, "%s\n\n", aurora.Green(banner)) 12 | } 13 | -------------------------------------------------------------------------------- /pkg/fingerprint/dns.go: -------------------------------------------------------------------------------- 1 | package fingerprint 2 | 3 | // DNS define DNS service providers 4 | type DNS struct { 5 | Provider string 6 | Status []int // 0 not vuln, 1 vulnerable, 2 edge case, 3 need purchase 7 | Pattern string 8 | } 9 | -------------------------------------------------------------------------------- /internal/option/options.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | // Options define available flag/options 4 | type Options struct { 5 | Target string 6 | Concurrency int 7 | Silent bool 8 | List []string 9 | Output string 10 | } 11 | -------------------------------------------------------------------------------- /pkg/dnstake/resolv.go: -------------------------------------------------------------------------------- 1 | package dnstake 2 | 3 | import "github.com/projectdiscovery/retryabledns" 4 | 5 | // Resolve target hostname by sending a query 6 | func Resolve(client *retryabledns.Client, hostname string, qtype uint16) (*retryabledns.DNSData, error) { 7 | return client.Query(hostname, qtype) 8 | } 9 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | labels: 8 | - "dependencies" 9 | 10 | - package-ecosystem: "github-actions" 11 | directory: "/" 12 | schedule: 13 | interval: "daily" 14 | labels: 15 | - "dependencies" -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pwnesia/dnstake 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d 7 | github.com/logrusorgru/aurora v2.0.3+incompatible 8 | github.com/projectdiscovery/gologger v1.1.4 9 | github.com/projectdiscovery/retryabledns v1.0.12 10 | github.com/remeh/sizedwaitgroup v1.0.0 11 | ) 12 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | builds: 2 | - binary: dnstake 3 | main: cmd/dnstake/dnstake.go 4 | goos: 5 | - linux 6 | - windows 7 | - darwin 8 | goarch: 9 | - amd64 10 | - 386 11 | - arm 12 | - arm64 13 | 14 | archives: 15 | - id: tgz 16 | format: tar.gz 17 | replacements: 18 | darwin: macOS 19 | format_overrides: 20 | - goos: windows 21 | format: zip 22 | -------------------------------------------------------------------------------- /pkg/fingerprint/check.go: -------------------------------------------------------------------------------- 1 | package fingerprint 2 | 3 | import "regexp" 4 | 5 | // Check do fingerprinting 6 | func Check(NS []string) (DNS, string, error) { 7 | for _, f := range Get() { 8 | for _, r := range NS { 9 | m, e := regexp.MatchString(f.Pattern, r) 10 | if e != nil { 11 | return DNS{}, "", e 12 | } 13 | 14 | if m { 15 | return f, r, nil 16 | } 17 | } 18 | } 19 | 20 | return DNS{}, "", nil 21 | } 22 | -------------------------------------------------------------------------------- /internal/errors/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | const ( 4 | ErrInvalid = "Invalid hostname" 5 | ErrNoNSRec = "has no nameserver record" 6 | ErrNoTarget = "No input target provided" 7 | ErrNoValid = "File has no valid hostnames" 8 | ErrPattern = "Invalid regExp pattern" 9 | ErrResolve = "unable to resolve target hostname" 10 | ErrUnknown = "Something error" 11 | ErrNotVuln = "target not known to be vulnerable" 12 | ErrVerify = "cannot verifying target hostname" 13 | ErrFinger = "DNS unknown when fingerprinting" 14 | ) 15 | -------------------------------------------------------------------------------- /cmd/dnstake/dnstake.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/projectdiscovery/gologger" 5 | "github.com/projectdiscovery/gologger/levels" 6 | "github.com/pwnesia/dnstake/internal/option" 7 | "github.com/pwnesia/dnstake/internal/runner" 8 | ) 9 | 10 | func init() { 11 | gologger.DefaultLogger.SetMaxLevel(levels.LevelDebug) 12 | } 13 | 14 | func main() { 15 | opt := option.Parse() 16 | 17 | if opt.Silent { 18 | gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent) 19 | } 20 | 21 | if err := runner.New(opt); err != nil { 22 | gologger.Fatal().Msg(err.Error()) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | create: 4 | tags: 5 | - v* 6 | 7 | jobs: 8 | release: 9 | name: "Release binary" 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: "Check out code" 13 | uses: actions/checkout@v3 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: "Set up Go" 18 | uses: actions/setup-go@v2 19 | with: 20 | go-version: 1.17 21 | 22 | - name: "Create release on GitHub" 23 | uses: goreleaser/goreleaser-action@v3.0.0 24 | env: 25 | GITHUB_TOKEN: "${{ secrets.RELEASE_TOKEN }}" 26 | with: 27 | args: "release --rm-dist" 28 | version: latest -------------------------------------------------------------------------------- /pkg/dnstake/dnstake.go: -------------------------------------------------------------------------------- 1 | package dnstake 2 | 3 | import "github.com/projectdiscovery/retryabledns" 4 | 5 | // Verify if the response is not NOERROR 6 | func Verify(IPs []string, hostname string) (bool, error) { 7 | retries := 3 8 | 9 | if len(IPs) < 1 { 10 | return false, nil 11 | } 12 | 13 | for i, k := range IPs { 14 | if k == "" { 15 | continue 16 | } 17 | 18 | IPs[i] += ":53" 19 | } 20 | 21 | if len(IPs) > retries { 22 | retries = len(IPs) 23 | } 24 | 25 | res, err := Resolve(retryabledns.New(IPs, retries), hostname, 1) 26 | if err != nil { 27 | return false, err 28 | } 29 | 30 | if res.StatusCode == "SERVFAIL" || res.StatusCode == "REFUSED" { 31 | return true, nil 32 | } 33 | 34 | return false, nil 35 | } 36 | -------------------------------------------------------------------------------- /internal/runner/runner.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "github.com/pwnesia/dnstake/internal/executor" 5 | "github.com/pwnesia/dnstake/internal/option" 6 | "github.com/remeh/sizedwaitgroup" 7 | ) 8 | 9 | // New of program runner 10 | func New(opt *option.Options) error { 11 | if err := validate(opt); err != nil { 12 | return err 13 | } 14 | 15 | job := make(chan string) 16 | con := opt.Concurrency 17 | swg := sizedwaitgroup.New(con) 18 | 19 | for i := 0; i < con; i++ { 20 | swg.Add() 21 | go func() { 22 | defer swg.Done() 23 | for hostname := range job { 24 | executor.New(opt, hostname) 25 | } 26 | }() 27 | } 28 | 29 | for _, hostname := range opt.List { 30 | job <- hostname 31 | } 32 | 33 | close(job) 34 | swg.Wait() 35 | 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /internal/runner/util.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/asaskevich/govalidator" 9 | "github.com/pwnesia/dnstake/internal/errors" 10 | ) 11 | 12 | func isStdin() bool { 13 | f, e := os.Stdin.Stat() 14 | if e != nil { 15 | return false 16 | } 17 | 18 | if f.Mode()&os.ModeNamedPipe == 0 { 19 | return false 20 | } 21 | 22 | return true 23 | } 24 | 25 | func read(file *os.File) ([]string, error) { 26 | var lines []string 27 | 28 | keys := make(map[string]bool) 29 | scanner := bufio.NewScanner(file) 30 | 31 | for scanner.Scan() { 32 | host := scanner.Text() 33 | if _, value := keys[host]; !value { 34 | if govalidator.IsHost(host) { 35 | keys[host] = true 36 | lines = append(lines, host) 37 | } 38 | } 39 | } 40 | 41 | if len(lines) < 1 { 42 | return lines, fmt.Errorf("%s", errors.ErrNoValid) 43 | } 44 | 45 | return lines, scanner.Err() 46 | } 47 | -------------------------------------------------------------------------------- /internal/runner/validator.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/asaskevich/govalidator" 8 | "github.com/pwnesia/dnstake/internal/errors" 9 | "github.com/pwnesia/dnstake/internal/option" 10 | ) 11 | 12 | func validate(opt *option.Options) error { 13 | var ( 14 | err error 15 | file *os.File 16 | ) 17 | 18 | if isStdin() { 19 | opt.List, err = read(os.Stdin) 20 | if err != nil { 21 | return err 22 | } 23 | } else if opt.Target != "" { 24 | _, err = os.Stat(opt.Target) 25 | 26 | if err != nil && govalidator.IsHost(opt.Target) { 27 | opt.List = []string{opt.Target} 28 | } else { 29 | file, err = os.Open(opt.Target) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | opt.List, err = read(file) 35 | if err != nil { 36 | return err 37 | } 38 | } 39 | } else { 40 | return fmt.Errorf("%s", errors.ErrNoTarget) 41 | } 42 | 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /internal/option/parse.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | // Parse user-supplied options 11 | func Parse() *Options { 12 | opt = &Options{} 13 | 14 | flag.StringVar(&opt.Target, "t", "", "") 15 | flag.StringVar(&opt.Target, "target", "", "") 16 | 17 | flag.IntVar(&opt.Concurrency, "c", 25, "") 18 | flag.IntVar(&opt.Concurrency, "concurrent", 25, "") 19 | 20 | flag.BoolVar(&opt.Silent, "s", false, "") 21 | flag.BoolVar(&opt.Silent, "silent", false, "") 22 | 23 | flag.StringVar(&opt.Output, "o", "", "") 24 | flag.StringVar(&opt.Output, "output", "", "") 25 | 26 | flag.Usage = func() { 27 | showBanner() 28 | h := []string{ 29 | "Usage:" + usage, 30 | "", 31 | "Options:" + options, 32 | "", 33 | "Examples:" + examples, 34 | "", 35 | } 36 | 37 | fmt.Fprint(os.Stderr, strings.Join(h, "\n")) 38 | } 39 | 40 | flag.Parse() 41 | 42 | if !opt.Silent { 43 | showBanner() 44 | } 45 | 46 | return opt 47 | } 48 | -------------------------------------------------------------------------------- /.github/workflows/pr.yaml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | checks: 9 | name: "Pull Request Checks" 10 | runs-on: ubuntu-latest 11 | if: contains(github.event.head_commit.message, '[ci skip]') == false && contains(github.event.head_commit.message, '[skip ci]') == false 12 | steps: 13 | - name: "Set up Go" 14 | uses: actions/setup-go@v2 15 | with: 16 | go-version: 1.16 17 | 18 | - name: "Check out code" 19 | uses: actions/checkout@v3 20 | 21 | - name: "GolangCI-Lint" 22 | uses: golangci/golangci-lint-action@v3.2.0 23 | with: 24 | version: v1.29 25 | 26 | - name: "Initialize CodeQL" 27 | uses: github/codeql-action/init@v2 28 | with: 29 | languages: go 30 | 31 | - name: "Compile the project" 32 | run: go build ./... 33 | 34 | - name: "Perform CodeQL Analysis" 35 | uses: github/codeql-action/analyze@v2 -------------------------------------------------------------------------------- /.github/workflows/gosum.yaml: -------------------------------------------------------------------------------- 1 | name: "Gosum" 2 | 3 | on: 4 | push: 5 | branches: 6 | - "master" 7 | paths: 8 | - "go.mod" 9 | - "go.sum" 10 | 11 | jobs: 12 | gosum: 13 | name: "Go mod tidy up" 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Check out code 17 | uses: actions/checkout@v3 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v2 21 | with: 22 | go-version: 1.16 23 | 24 | - name: Tidy 25 | run: | 26 | rm -f go.sum 27 | go mod tidy 28 | 29 | - name: Create Pull Request 30 | uses: peter-evans/create-pull-request@v4 31 | with: 32 | token: ${{ secrets.GITHUB_TOKEN }} 33 | commit-message: "[ci skip] go mod tidy" 34 | title: "Tidy up the Go module" 35 | body: | 36 | Current `go.mod` and `go.sum` don't match the source code. 37 | branch: go-mod-tidy 38 | branch-suffix: short-commit-hash 39 | labels: "dependencies" -------------------------------------------------------------------------------- /internal/option/const.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | const ( 4 | version = "0.1.1" 5 | author = "pwnesia.org" 6 | banner = ` 7 | ·▄▄▄▄ ▐ ▄ .▄▄ ·▄▄▄▄▄ ▄▄▄· ▄ •▄ ▄▄▄ . 8 | ██▪ ██ •█▌▐█▐█ ▀.•██ ▐█ ▀█ █▌▄▌▪▀▄.▀· 9 | ▐█· ▐█▌▐█▐▐▌▄▀▀▀█▄▐█.▪▄█▀▀█ ▐▀▀▄·▐▀▀▪▄ 10 | ██. ██ ██▐█▌▐█▄▪▐█▐█▌·▐█ ▪▐▌▐█.█▌▐█▄▄▌ 11 | ▀▀▀▀▀• ▀▀ █▪ ▀▀▀▀ ▀▀▀ ▀ ▀ ·▀ ▀ ▀▀▀ 12 | 13 | (c) ` + author + ` — v` + version 14 | usage = ` 15 | [stdin] | dnstake [options] 16 | dnstake -t HOSTNAME [options]` 17 | options = ` 18 | -t, --target Define single target host/list to check 19 | -c, --concurrent Set the concurrency level (default: 25) 20 | -s, --silent Suppress errors and/or clean output 21 | -o, --output Save vulnerable hosts to FILE 22 | -h, --help Display its help` 23 | examples = ` 24 | dnstake -t (sub.)domain.tld 25 | dnstake -t hosts.txt 26 | dnstake -t hosts.txt -o ./dnstake.out 27 | cat hosts.txt | dnstake 28 | subfinder -silent -d domain.tld | dnstake 29 | ` 30 | ) 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 dwisiswant0 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 | -------------------------------------------------------------------------------- /internal/executor/executor.go: -------------------------------------------------------------------------------- 1 | package executor 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/logrusorgru/aurora" 9 | "github.com/projectdiscovery/gologger" 10 | "github.com/projectdiscovery/retryabledns" 11 | "github.com/pwnesia/dnstake/internal/errors" 12 | "github.com/pwnesia/dnstake/internal/option" 13 | "github.com/pwnesia/dnstake/pkg/dnstake" 14 | "github.com/pwnesia/dnstake/pkg/fingerprint" 15 | ) 16 | 17 | // New to execute target hostname 18 | func New(opt *option.Options, hostname string) { 19 | var out = "" 20 | 21 | vuln, DNS, err := exec(hostname) 22 | if err != nil { 23 | gologger.Error().Msgf("%s: %s", hostname, err.Error()) 24 | } 25 | 26 | if vuln { 27 | if !opt.Silent { 28 | out += fmt.Sprintf("[%s] ", aurora.Green("VLN")) 29 | } 30 | 31 | out += hostname 32 | 33 | if !opt.Silent { 34 | out += fmt.Sprintf(" (%s)", aurora.Cyan(DNS.Provider)) 35 | } 36 | 37 | if !opt.Silent { 38 | for _, status := range DNS.Status { 39 | switch status { 40 | case 2: 41 | out += fmt.Sprintf(" (%s)", aurora.Magenta("Edge Case")) 42 | case 3: 43 | out += fmt.Sprintf(" (%s)", aurora.Yellow("$")) 44 | } 45 | } 46 | } 47 | 48 | if opt.Output != "" { 49 | writeToFile(hostname, opt.Output) 50 | } 51 | } 52 | 53 | if out != "" { 54 | fmt.Println(out) 55 | } 56 | } 57 | 58 | func writeToFile(data, output string) { 59 | mu.Lock() 60 | defer mu.Unlock() 61 | 62 | file, err := os.OpenFile(output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 63 | if err != nil { 64 | gologger.Fatal().Msg(err.Error()) 65 | } 66 | 67 | wrt := bufio.NewWriter(file) 68 | 69 | _, err = wrt.WriteString(data + "\n") 70 | if err != nil { 71 | gologger.Error().Msg(err.Error()) 72 | } 73 | 74 | wrt.Flush() 75 | file.Close() 76 | } 77 | 78 | func exec(hostname string) (bool, fingerprint.DNS, error) { 79 | var ( 80 | vuln bool 81 | DNS = fingerprint.DNS{} 82 | ) 83 | 84 | client := retryabledns.New([]string{"8.8.8.8:53", "1.1.1.1:53"}, 3) 85 | 86 | q1, err := dnstake.Resolve(client, hostname, 2) 87 | if err != nil { 88 | return vuln, DNS, fmt.Errorf("%s", errors.ErrResolve) 89 | } 90 | 91 | if len(q1.NS) < 1 { 92 | return vuln, DNS, fmt.Errorf("%s", errors.ErrNoNSRec) 93 | } 94 | 95 | fgp, rec, err := fingerprint.Check(q1.NS) 96 | if err != nil { 97 | return vuln, fgp, fmt.Errorf("%s (%s)", errors.ErrPattern, err.Error()) 98 | } 99 | 100 | if rec == "" { 101 | return false, fgp, fmt.Errorf("%s", errors.ErrFinger) 102 | } 103 | 104 | if _, m := find(fgp.Status, 0); m { 105 | return vuln, DNS, fmt.Errorf("%s", errors.ErrNotVuln) 106 | } 107 | 108 | q2, err := dnstake.Resolve(client, rec, 1) 109 | if err != nil { 110 | return vuln, DNS, fmt.Errorf("%s (%s)", errors.ErrResolve, rec) 111 | } 112 | 113 | vuln, err = dnstake.Verify(q2.A, hostname) 114 | if err != nil { 115 | return vuln, DNS, fmt.Errorf("%s (%s)", errors.ErrVerify, err.Error()) 116 | } 117 | 118 | if vuln { 119 | return vuln, fgp, nil 120 | } 121 | 122 | return false, fgp, fmt.Errorf("%s", errors.ErrUnknown) 123 | } 124 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DNSTake 2 | 3 | DNSTake 4 | 5 | A fast tool to check missing hosted DNS zones that can lead to subdomain takeover. 6 | 7 | --- 8 | 9 | 10 | ## What is a DNS takeover? 11 | 12 | DNS takeover vulnerabilities occur when a subdomain (subdomain.example.com) or domain has its authoritative nameserver set to a provider (e.g. AWS Route 53, Akamai, Microsoft Azure, etc.) but the hosted zone has been removed or deleted. Consequently, when making a [request for DNS records](https://www.diggui.com/#type=A&hostname=github.technology&nameserver=public&public=8.8.8.8&specify=&clientsubnet=&tcp=def&transport=def&mapped=def&nssearch=def&trace=def&recurse=def&edns=def&dnssec=def&subnet=def&cookie=def&all=def&cmd=def&question=def&answer=def&authority=def&additional=def&comments=def&stats=def&multiline=def&short=def&colorize=on) the server responds with a `SERVFAIL` error. This allows an attacker to create the missing hosted zone on the service that was being used and thus control all DNS records for that (sub)domain.¹ 13 | 14 | ## Installation 15 | 16 | ### from Binary 17 | 18 | The ez way! You can download a pre-built binary from [releases page](https://github.com/pwnesia/dnstake/releases), just unpack and run! 19 | 20 | ### from Source 21 | 22 | 23 | 24 |
NOTE: Go 1.16+ compiler should be installed & configured!
25 | 26 | Very quick & clean! 27 | 28 | ```bash 29 | ▶ go install github.com/pwnesia/dnstake/cmd/dnstake@latest 30 | ``` 31 | 32 | #### — or 33 | 34 | Manual building executable from source code: 35 | 36 | ```bash 37 | ▶ git clone https://github.com/pwnesia/dnstake 38 | ▶ cd dnstake/cmd/dnstake 39 | ▶ go build . 40 | ▶ (sudo) mv dnstake /usr/local/bin 41 | ``` 42 | 43 | ## Usage 44 | 45 | ```console 46 | $ dnstake -h 47 | 48 | ·▄▄▄▄ ▐ ▄ .▄▄ ·▄▄▄▄▄ ▄▄▄· ▄ •▄ ▄▄▄ . 49 | ██▪ ██ •█▌▐█▐█ ▀.•██ ▐█ ▀█ █▌▄▌▪▀▄.▀· 50 | ▐█· ▐█▌▐█▐▐▌▄▀▀▀█▄▐█.▪▄█▀▀█ ▐▀▀▄·▐▀▀▪▄ 51 | ██. ██ ██▐█▌▐█▄▪▐█▐█▌·▐█ ▪▐▌▐█.█▌▐█▄▄▌ 52 | ▀▀▀▀▀• ▀▀ █▪ ▀▀▀▀ ▀▀▀ ▀ ▀ ·▀ ▀ ▀▀▀ 53 | 54 | (c) pwnesia.org — v0.0.1 55 | 56 | Usage: 57 | [stdin] | dnstake [options] 58 | dnstake -t HOSTNAME [options] 59 | 60 | Options: 61 | -t, --target Define single target host/list to check 62 | -c, --concurrent Set the concurrency level (default: 25) 63 | -s, --silent Suppress errors and/or clean output 64 | -o, --output Save vulnerable hosts to FILE 65 | -h, --help Display its help 66 | 67 | Examples: 68 | dnstake -t (sub.)domain.tld 69 | dnstake -t hosts.txt 70 | dnstake -t hosts.txt -o ./dnstake.out 71 | cat hosts.txt | dnstake 72 | subfinder -silent -d domain.tld | dnstake 73 | ``` 74 | 75 | ## Workflow 76 | 77 | **DNSTake** use [RetryableDNS client library](https://github.com/projectdiscovery/retryabledns) to send DNS queries. Initial engagement using Google & Cloudflare DNS as the resolver, then check & fingerprinting the nameservers of target host — if there is one, it will resolving the target host again with its nameserver IPs as resolver, if it gets weird DNS status response (other than `NOERROR`/`NXDOMAIN`), then it's vulnerable to be taken over. More or less [like this](https://0xpatrik.com/content/images/2018/08/ns_automation-2.png) in form of a diagram. 78 | 79 | Currently supported DNS providers, see [here](https://github.com/indianajson/can-i-take-over-dns/blob/97104102c8ce911fd978521c703f26e1c547c613/README.md#dns-providers). 80 | 81 | ## References 82 | 83 | - [1] https://github.com/indianajson/can-i-take-over-dns#what-is-a-dns-takeover 84 | - https://0xpatrik.com/subdomain-takeover-ns/ 85 | 86 | ## License 87 | 88 | **DNSTake** is distributed under MIT. See `LICENSE`. 89 | -------------------------------------------------------------------------------- /pkg/fingerprint/fingerprint.go: -------------------------------------------------------------------------------- 1 | package fingerprint 2 | 3 | // Get predefined DNS service providers 4 | // taken from https://github.com/indianajson/can-i-take-over-dns#dns-providers 5 | func Get() []DNS { 6 | return []DNS{ 7 | { 8 | Provider: "000Domains", 9 | Status: []int{1, 3}, 10 | Pattern: `^(fw)?ns[\d]\.000domains\.com$`, 11 | }, 12 | { 13 | Provider: "AWS Route 53", 14 | Status: []int{0}, 15 | Pattern: `^ns\-([\w]{4}\.awsdns\-[\w]{2}\.(co\.uk|org)|[\w]{3}\.awsdns\-[\w]{2}\.(com|net))$`, 16 | }, 17 | { 18 | Provider: "Microsoft Azure", 19 | Status: []int{1}, 20 | Pattern: `^ns(4\-[\w]{2}\.azure\-dns\.info|1\-[\w]{2}\.azure\-dns\.com|2\-[\w]{2}\.azure\-dns\.net|3\-[\w]{2}\.azure\-dns\.org)$`, 21 | }, 22 | { 23 | Provider: "Bizland", 24 | Status: []int{1}, 25 | Pattern: `^(clickme2?\.click2site\.com|ns[12]\.bizland\.com)$`, 26 | }, 27 | { 28 | Provider: "Cloudflare", 29 | Status: []int{2}, 30 | Pattern: `^[\w]+\.ns\.cloudflare\.com$`, 31 | }, 32 | { 33 | Provider: "DigitalOcean", 34 | Status: []int{1}, 35 | Pattern: `^ns[1-3]\.digitalocean\.com$`, 36 | }, 37 | { 38 | Provider: "DNSMadeEasy", 39 | Status: []int{1}, 40 | Pattern: `^ns[\w]{,2}\.dnsmade{2}asy\.com$`, 41 | }, 42 | { 43 | Provider: "DNSimple", 44 | Status: []int{1}, 45 | Pattern: `^ns[1-4]\.dnsimple\.com$`, 46 | }, 47 | { 48 | Provider: "Domain.com", 49 | Status: []int{1, 3}, 50 | Pattern: `^ns[1-2]\.domain\.com$`, 51 | }, 52 | { 53 | Provider: "DomainPeople", 54 | Status: []int{0}, 55 | Pattern: `^ns[1-2]\.domainpeople\.com$`, 56 | }, 57 | { 58 | Provider: "Dotster", 59 | Status: []int{1, 3}, 60 | Pattern: `^ns[12]\.(nameresolve|dotster)\.com$`, 61 | }, 62 | { 63 | Provider: "Dotster", 64 | Status: []int{1, 3}, 65 | Pattern: `^ns[12]\.(nameresolve|dotster)\.com$`, 66 | }, 67 | { 68 | Provider: "EasyDNS", 69 | Status: []int{1}, 70 | Pattern: `^dns(?:4\.easydns\.info|1\.easydns\.com|2\.easydns\.net|3\.easydns\.org)$`, 71 | }, 72 | { 73 | Provider: "Gandi.net", 74 | Status: []int{0}, 75 | Pattern: `^[\w]+\.dns\.gandi\.net$`, 76 | }, 77 | { 78 | Provider: "Google Cloud", 79 | Status: []int{1}, 80 | Pattern: `^ns\-cloud\-[\w]+\.go{2}gledomains\.com$`, 81 | }, 82 | { 83 | Provider: "Hover", 84 | Status: []int{0}, 85 | Pattern: `^ns[1-2]\.hover\.com$`, 86 | }, 87 | { 88 | Provider: "Hurricane Electric", 89 | Status: []int{1}, 90 | Pattern: `^ns[1-5]\.he\.net$`, 91 | }, 92 | { 93 | Provider: "Linode", 94 | Status: []int{1}, 95 | Pattern: `^ns[1-2]\.linode\.com$`, 96 | }, 97 | { 98 | Provider: "MediaTemple", 99 | Status: []int{0}, 100 | Pattern: `^ns[1-2]\.mediatemple\.net$`, 101 | }, 102 | { 103 | Provider: "MyDomain", 104 | Status: []int{1, 3}, 105 | Pattern: `^ns[1-2]\.mydomain\.com$`, 106 | }, 107 | { 108 | Provider: "Name.com", 109 | Status: []int{1, 3}, 110 | Pattern: `^ns[1-4][\w]+?\.name\.com$`, 111 | }, 112 | { 113 | Provider: "Network Solutions", 114 | Status: []int{0}, 115 | Pattern: `^ns[\w]+?\.worldnic\.com$`, 116 | }, 117 | { 118 | Provider: "NS1", 119 | Status: []int{1}, 120 | Pattern: `^dns[1-4]\.p[\d]{,2}\.nsone\.net$`, 121 | }, 122 | { 123 | Provider: "TierraNet", 124 | Status: []int{1}, 125 | Pattern: `^ns[1-2]\.domaindiscover\.com$`, 126 | }, 127 | { 128 | Provider: "Reg.ru", 129 | Status: []int{1, 3}, 130 | Pattern: `^ns[1-2]\.reg\.ru$`, 131 | }, 132 | { 133 | Provider: "UltraDNS", 134 | Status: []int{0}, 135 | Pattern: `^[psu]dns[\d]{,3}\.ultradns\.com$`, 136 | }, 137 | { 138 | Provider: "Yahoo Small Business", 139 | Status: []int{1, 3}, 140 | Pattern: `^yns[1-2]\.yahoo\.com$`, 141 | }, 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= 2 | github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 3 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 8 | github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= 9 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 10 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 11 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 12 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 13 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 14 | github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= 15 | github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= 16 | github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg= 17 | github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= 18 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 19 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 20 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 21 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 22 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 23 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 24 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 25 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 26 | github.com/projectdiscovery/gologger v1.1.4 h1:qWxGUq7ukHWT849uGPkagPKF3yBPYAsTtMKunQ8O2VI= 27 | github.com/projectdiscovery/gologger v1.1.4/go.mod h1:Bhb6Bdx2PV1nMaFLoXNBmHIU85iROS9y1tBuv7T5pMY= 28 | github.com/projectdiscovery/retryabledns v1.0.12 h1:OzCsUaipN75OwjtH62FxBIhKye1NmnfG4DxtQclOtns= 29 | github.com/projectdiscovery/retryabledns v1.0.12/go.mod h1:4sMC8HZyF01HXukRleSQYwz4870bwgb4+hTSXTMrkf4= 30 | github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7Kyl5E= 31 | github.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo= 32 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 33 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 34 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 35 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 36 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 37 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= 38 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 39 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 40 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 41 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 42 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= 43 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 44 | golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= 45 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 46 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 47 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 48 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M= 49 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 50 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 51 | golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 52 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 53 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 54 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 55 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 56 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 57 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 58 | --------------------------------------------------------------------------------