Skip to content

Commit

Permalink
✨ use a pool of rotating upstream name servers
Browse files Browse the repository at this point in the history
  • Loading branch information
mdeous committed Jul 31, 2023
1 parent 2ca8c7a commit a74a520
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 36 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ Flags:
-e, --edge-cases include edge-case fingerprints (might cause false positives)
-f, --fingerprints string custom service fingerprints file
-h, --help help for dnscheck
-n, --nameserver string server and port to use for name resolution (default "8.8.8.8:53")
-o, --output string file to write findings to
-s, --skip-summary skip summary at the end of the scan
-t, --timeout uint timeout for HTTP requests (default 10)
-v, --verbose increase application verbosity
-w, --workers int amount of concurrent workers (default 10)
Expand Down
10 changes: 5 additions & 5 deletions checker/check_cname.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func (c *Checker) checkPattern(domain string, pattern string, body string) (bool
func (c *Checker) checkFingerprint(domain string, fp *Fingerprint, body string, hasCname bool, hasA bool) (DetectionMethod, string, error) {
var err error
if fp.NXDomain {
if c.dns.DomainIsNXDOMAIN(domain, c.cfg.Nameserver) {
if c.dns.DomainIsNXDOMAIN(domain) {
if hasCname {
return MethodCnameNxdomain, body, nil
}
Expand Down Expand Up @@ -72,7 +72,7 @@ func (c *Checker) checkFingerprint(domain string, fp *Fingerprint, body string,
// CheckCNAME checks if the CNAME entries for the provided domain are vulnerable
func (c *Checker) CheckCNAME(domain string) ([]*Match, error) {
var err error
cnames, err := c.dns.GetCNAME(domain, c.cfg.Nameserver)
cnames, err := c.dns.GetCNAME(domain)
if err != nil {
return nil, err
}
Expand All @@ -81,7 +81,7 @@ func (c *Checker) CheckCNAME(domain string) ([]*Match, error) {
body := ""

// handle cases where fingerprint target is an IP address
aRecords, err := c.dns.GetA(domain, c.cfg.Nameserver)
aRecords, err := c.dns.GetA(domain)
if err == nil {
for _, a := range aRecords {
// check if any fingerprint matches
Expand Down Expand Up @@ -141,7 +141,7 @@ func (c *Checker) CheckCNAME(domain string) ([]*Match, error) {
if len(findings) == 0 {
// no fingerprint matched target domain, check if CNAME target can be registered
c.verbose("%s: Checking CNAME target availability: %s", domain, cname)
available, err := c.dns.DomainIsAvailable(cname, c.cfg.Nameserver)
available, err := c.dns.DomainIsAvailable(cname)
if err != nil {
continue
}
Expand All @@ -159,7 +159,7 @@ func (c *Checker) CheckCNAME(domain string) ([]*Match, error) {
}

// target has no CNAME records, check fingerprints that don't expect one
resolveResults := c.dns.Resolve(domain, c.cfg.Nameserver)
resolveResults := c.dns.Resolve(domain)
if len(findings) == 0 && len(resolveResults) > 0 {
c.verbose("%s: No CNAMEs but domain resolves, checking relevant fingerprints", domain)
for _, fp := range c.fingerprints {
Expand Down
2 changes: 1 addition & 1 deletion checker/check_ns.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package checker
// CheckNS checks if provided domain has dangling NS records
func (c *Checker) CheckNS(domain string) ([]*Match, error) {
var findings []*Match
if c.dns.DomainIsSERVFAIL(domain, c.cfg.Nameserver) {
if c.dns.DomainIsSERVFAIL(domain) {
finding := &Match{
Domain: domain,
Target: "n/a",
Expand Down
1 change: 0 additions & 1 deletion checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
type CheckFunc func(string) ([]*Match, error)

type Config struct {
Nameserver string
Verbose bool
Workers int
CustomFpFile string
Expand Down
6 changes: 0 additions & 6 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@ var rootCmd = &cobra.Command{
if err != nil {
log.Fatal(err.Error())
}
nameserver, err := cmd.Flags().GetString("nameserver")
if err != nil {
log.Fatal(err.Error())
}
workers, err := cmd.Flags().GetInt("workers")
if err != nil {
log.Fatal(err.Error())
Expand All @@ -57,7 +53,6 @@ var rootCmd = &cobra.Command{

// instanciate domain checker
chk := checker.NewChecker(&checker.Config{
Nameserver: nameserver,
Verbose: verbose,
Workers: workers,
CustomFpFile: fpFile,
Expand Down Expand Up @@ -135,7 +130,6 @@ func Execute() {
func init() {
rootCmd.Flags().StringP("domain", "d", "", "single domain to check")
rootCmd.Flags().StringP("domains-file", "D", "domains.txt", "file containing domains to check")
rootCmd.Flags().StringP("nameserver", "n", "8.8.8.8:53", "server and port to use for name resolution")
rootCmd.Flags().IntP("workers", "w", 10, "amount of concurrent workers")
rootCmd.Flags().StringP("output", "o", "", "file to write findings to")
rootCmd.Flags().UintP("timeout", "t", 10, "timeout for HTTP requests")
Expand Down
46 changes: 24 additions & 22 deletions dns/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import (
)

type Client struct {
cache *Cache
cache *Cache
resolver *Resolver
}

func (c *Client) query(nameserver string, domain string, reqType uint16) (*dns.Msg, error) {
Expand All @@ -27,8 +28,8 @@ func (c *Client) query(nameserver string, domain string, reqType uint16) (*dns.M
return resp, nil
}

func (c *Client) GetCNAME(domain string, nameserver string) ([]string, error) {
ret, err := c.query(nameserver, domain, dns.TypeCNAME)
func (c *Client) GetCNAME(domain string) ([]string, error) {
ret, err := c.query(c.resolver.Get(), domain, dns.TypeCNAME)
if err != nil {
return nil, fmt.Errorf("could not get CNAME for %s: %v", domain, err)
}
Expand All @@ -41,8 +42,8 @@ func (c *Client) GetCNAME(domain string, nameserver string) ([]string, error) {
return records, nil
}

func (c *Client) GetSOA(domain string, nameserver string) ([]string, error) {
ret, err := c.query(nameserver, domain, dns.TypeSOA)
func (c *Client) GetSOA(domain string) ([]string, error) {
ret, err := c.query(c.resolver.Get(), domain, dns.TypeSOA)
if err != nil {
return nil, fmt.Errorf("could not get CNAME for %s: %v", domain, err)
}
Expand All @@ -55,8 +56,8 @@ func (c *Client) GetSOA(domain string, nameserver string) ([]string, error) {
return records, nil
}

func (c *Client) GetA(domain string, nameserver string) ([]string, error) {
ret, err := c.query(nameserver, domain, dns.TypeA)
func (c *Client) GetA(domain string) ([]string, error) {
ret, err := c.query(c.resolver.Get(), domain, dns.TypeA)
if err != nil {
return nil, fmt.Errorf("could not get A for %s: %v", domain, err)
}
Expand All @@ -69,8 +70,8 @@ func (c *Client) GetA(domain string, nameserver string) ([]string, error) {
return records, nil
}

func (c *Client) GetAAAA(domain string, nameserver string) ([]string, error) {
ret, err := c.query(nameserver, domain, dns.TypeAAAA)
func (c *Client) GetAAAA(domain string) ([]string, error) {
ret, err := c.query(c.resolver.Get(), domain, dns.TypeAAAA)
if err != nil {
return nil, fmt.Errorf("could not get AAAA for %s: %v", domain, err)
}
Expand Down Expand Up @@ -113,14 +114,14 @@ func (c *Client) GetNS(domain string, nameserver string) ([]string, error) {
return records, nil
}

func (c *Client) DomainIsSERVFAIL(domain string, nameserver string) bool {
func (c *Client) DomainIsSERVFAIL(domain string) bool {
rootDomain, err := publicsuffix.EffectiveTLDPlusOne(domain)
if err != nil {
log.Warn("%s: unable to determine root domain: %v", domain, err)
return false
}

rootNameservers, err := c.GetNS(rootDomain, nameserver)
rootNameservers, err := c.GetNS(rootDomain, c.resolver.Get())
if err != nil {
log.Warn("%s: unable to get nameserver: %v", domain, err)
return false
Expand Down Expand Up @@ -150,31 +151,31 @@ func (c *Client) DomainIsSERVFAIL(domain string, nameserver string) bool {
return false
}

func (c *Client) DomainIsNXDOMAIN(domain string, nameserver string) bool {
ret, err := c.query(nameserver, domain, dns.TypeA)
func (c *Client) DomainIsNXDOMAIN(domain string) bool {
ret, err := c.query(c.resolver.Get(), domain, dns.TypeA)
if err != nil {
log.Warn("%s: type A request to check NXDOMAIN failed: %v", domain, err)
return false
}
return ret.Rcode == dns.RcodeNameError
}

func (c *Client) DomainIsAvailable(domain, nameserver string) (bool, error) {
func (c *Client) DomainIsAvailable(domain string) (bool, error) {
// extract root domain from CNAME target
rootDomain, err := publicsuffix.EffectiveTLDPlusOne(domain)
if err != nil {
log.Warn("Unable to get root domain for %s: %v", domain, err)
return false, err
}
// check if domain resolves
resolveResults := c.Resolve(rootDomain, nameserver)
resolveResults := c.Resolve(rootDomain)
if err != nil {
log.Warn("Error while resolving %s: %v", rootDomain, err)
return false, err
}
if len(resolveResults) == 0 {
// domain does not resolve, does it have an SOA record?
soaRecords, err := c.GetSOA(rootDomain, nameserver)
soaRecords, err := c.GetSOA(rootDomain)
if err != nil {
log.Warn("Error while querying SOA for %s: %v", rootDomain, err)
return false, err
Expand All @@ -187,24 +188,24 @@ func (c *Client) DomainIsAvailable(domain, nameserver string) (bool, error) {
return false, nil
}

func (c *Client) Resolve(domain string, nameserver string) []string {
func (c *Client) Resolve(domain string) []string {
var resolutions []string
aRecs, err := c.GetA(domain, nameserver)
aRecs, err := c.GetA(domain)
if err == nil {
for _, a := range aRecs {
resolutions = append(resolutions, a)
}
}
aaaaRecs, err := c.GetAAAA(domain, nameserver)
aaaaRecs, err := c.GetAAAA(domain)
if err == nil {
for _, aaaa := range aaaaRecs {
resolutions = append(resolutions, aaaa)
}
}
cnameRecs, err := c.GetCNAME(domain, nameserver)
cnameRecs, err := c.GetCNAME(domain)
if err == nil {
for _, cname := range cnameRecs {
subResolutions := c.Resolve(cname, nameserver)
subResolutions := c.Resolve(cname)
for _, subResolution := range subResolutions {
resolutions = append(resolutions, subResolution)
}
Expand All @@ -215,6 +216,7 @@ func (c *Client) Resolve(domain string, nameserver string) []string {

func NewClient() *Client {
return &Client{
cache: NewCache(),
cache: NewCache(),
resolver: NewResolver(),
}
}
40 changes: 40 additions & 0 deletions dns/resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package dns

import (
_ "embed"
"strings"
)

const maxReqPerResolver = 5

//go:embed resolvers.txt
var resolvers string

type Resolver struct {
resolvers []string
current int
currentRequests int
}

func (r *Resolver) Get() string {
if r.currentRequests >= maxReqPerResolver {
r.current++
r.currentRequests = 0
}
if r.current >= len(r.resolvers) {
r.current = 0
}
r.currentRequests++
return r.resolvers[r.current]
}

func NewResolver() *Resolver {
r := &Resolver{}
for _, line := range strings.Split(resolvers, "\n") {
line = strings.TrimSpace(line)
if line != "" && !strings.HasPrefix(line, "#") {
r.resolvers = append(r.resolvers, line+":53")
}
}
return r
}
32 changes: 32 additions & 0 deletions dns/resolvers.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Google
8.8.8.8
8.8.4.4

# Quad9
9.9.9.9
149.112.112.112

# OpenDNS
208.67.222.222
208.67.220.220
208.67.222.220
208.67.220.222

# Cloudflare
1.1.1.1
1.0.0.1

# Verisign
64.6.64.6
64.6.65.6

# Level3
209.244.0.3
209.244.0.4

# HE
74.82.42.42

# OpenNIC
216.87.84.211
23.90.4.6

0 comments on commit a74a520

Please sign in to comment.