Skip to content

Commit

Permalink
Make DNS response buffer size adjustable (#15)
Browse files Browse the repository at this point in the history
Add `email` as a known DKIM selector (#16)
  • Loading branch information
wolveix committed Sep 10, 2023
1 parent 4bb0d96 commit 9bd4110
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 114 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,11 @@ You can then email this inbox from any address, and you'll receive an email back
| `--concurrent` | `-c` | The number of domains to scan concurrently (default 10) |
| `--debug` | `-d` | Print debug logs |
| `--dkimSelector` | | Specify a DKIM selector (default "x") |
| `--dnsBuffer` | | Specify the allocated buffer for DNS responses (default 1024) |
| `--format` | `-f` | Format to print results in (yaml, json, csv) (default "yaml") |
| `--nameservers` | `-n` | Use specific nameservers, in host[:port] format; may be specified multiple times |
| `--outputFile` | `-o` | Output the results to a specified file (creates a file with the current unix timestamp if no file is specified) |
| `--timeout` | `-t` | Timeout duration for a DNS query (default 15) |
| `--type` | `-r` | Type of DNS record to lookup (a, aaaa, cname, mx, sec [DKIM/DMARC/SPF], txt (default "sec") |
| `--zoneFile` | `-z` | Input file/pipe containing an RFC 1035 zone file |

## License
Expand Down
21 changes: 11 additions & 10 deletions cmd/dss/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ var (
Use: "dss",
Short: "Scan a domain's DNS records.",
Long: "Scan a domain's DNS records.\nhttps://github.com/GlobalCyberAlliance/DomainSecurityScanner",
Version: "2.3.2",
Version: "2.3.3",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
if debug {
log = zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339}).With().Timestamp().Logger().Level(zerolog.DebugLevel)
Expand All @@ -43,13 +43,14 @@ var (
}
},
}
cfg *Config
log zerolog.Logger
concurrent, writeToFileCounter int
dkimSelector, format, outputFile, recordType string
nameservers []string
timeout int64
advise, debug, cache, checkTls, zoneFile bool
cfg *Config
log zerolog.Logger
concurrent, writeToFileCounter int
dkimSelector, format, outputFile string
nameservers []string
timeout int64
advise, debug, cache, checkTls, zoneFile bool
dnsBuffer uint16
)

func main() {
Expand All @@ -59,10 +60,10 @@ func main() {
cmd.PersistentFlags().IntVarP(&concurrent, "concurrent", "c", runtime.NumCPU(), "The number of domains to scan concurrently")
cmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "Print debug logs")
cmd.PersistentFlags().StringVar(&dkimSelector, "dkimSelector", "x", "Specify a DKIM selector")
cmd.PersistentFlags().Uint16Var(&dnsBuffer, "dnsBuffer", 1024, "Specify the allocated buffer for DNS responses")
cmd.PersistentFlags().StringVarP(&format, "format", "f", "yaml", "Format to print results in (yaml, json)")
cmd.PersistentFlags().StringSliceVarP(&nameservers, "nameservers", "n", nil, "Use specific nameservers, in `host[:port]` format; may be specified multiple times")
cmd.PersistentFlags().StringVarP(&outputFile, "outputFile", "o", "", "Output the results to a specified file (creates a file with the current unix timestamp if no file is specified)")
cmd.PersistentFlags().StringVar(&recordType, "type", "sec", "Type of DNS record to lookup (a, aaaa, cname, mx, sec [DKIM/DMARC/SPF], txt")
cmd.PersistentFlags().Int64VarP(&timeout, "timeout", "t", 15, "Timeout duration for a DNS query")
cmd.PersistentFlags().BoolVarP(&zoneFile, "zoneFile", "z", false, "Input file/pipe containing an RFC 1035 zone file")

Expand Down Expand Up @@ -120,7 +121,7 @@ func marshal(data interface{}) (output []byte) {
// write to csv in buffer
var buffer bytes.Buffer
writer := csv.NewWriter(&buffer)
writer.Write(scan.Csv())
_ = writer.Write(scan.Csv())
writer.Flush()
output = buffer.Bytes()
case "json":
Expand Down
2 changes: 1 addition & 1 deletion cmd/dss/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ var cmdScan = &cobra.Command{
scanner.ConcurrentScans(concurrent),
scanner.UseCache(cache),
scanner.UseNameservers(nameservers),
scanner.WithDnsBuffer(dnsBuffer),
scanner.WithTimeout(time.Duration(timeout) * time.Second),
}

Expand All @@ -48,7 +49,6 @@ var cmdScan = &cobra.Command{
}

sc.DKIMSelector = dkimSelector
sc.RecordType = recordType

if format == "csv" && outputFile == "" {
log.Info().Msg("CSV header: domain,A,AAAA,BIMI,CNAME,DKIM,DMARC,MX,SPF,TXT,duration,error,advice")
Expand Down
48 changes: 19 additions & 29 deletions pkg/http/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,37 +14,31 @@ type bulkDomainRequest struct {
}

func (s *Server) registerScanRoutes(r *gin.RouterGroup) {
r.GET("/scan/:domain", s.handleScanDomain)
r.GET("/scan/:domain", s.handleScanDomains)
r.POST("/scan", s.handleScanDomains)
}

func (s *Server) handleScanDomain(c *gin.Context) {
domain := c.Param("domain")

if queryParam, ok := c.GetQuery("dkimSelector"); ok {
s.Scanner.DKIMSelector = queryParam
}

if queryParam, ok := c.GetQuery("recordType"); ok {
s.Scanner.RecordType = queryParam
}

result := s.Scanner.Scan(domain)
advice := domainAdvisor.CheckAll(result.BIMI, result.DKIM, result.DMARC, result.Domain, result.MX, result.SPF, s.CheckTls)

resultWithAdvice := model.ScanResultWithAdvice{
ScanResult: result,
Advice: advice,
}

s.respond(c, 200, &resultWithAdvice)
}

func (s *Server) handleScanDomains(c *gin.Context) {
var domains bulkDomainRequest

if err := Decode(c, &domains); err != nil {
s.logger.Error().Err(err).Msg("error occurred during handleScanDomains request")
switch c.Request.Method {
case "GET":
domains.Domains[0] = c.Param("domain")
break
case "POST":
if err := Decode(c, &domains); err != nil {
s.logger.Error().Err(err).Msg("error occurred during handleScanDomains request")
s.respond(c, 400, "you need to supply an array of domains in the body of the request, formatted as json")
return
}
break
default:
s.respond(c, 405, "method not allowed")
return
}

// check for empty array
if len(domains.Domains) == 0 {
s.respond(c, 400, "you need to supply an array of domains in the body of the request, formatted as json")
return
}
Expand All @@ -62,10 +56,6 @@ func (s *Server) handleScanDomains(c *gin.Context) {
s.Scanner.DKIMSelector = queryParam
}

if queryParam, ok := c.GetQuery("recordType"); ok {
s.Scanner.RecordType = queryParam
}

var resultsWithAdvice []model.ScanResultWithAdvice

for result := range s.Scanner.Start(source) {
Expand Down
13 changes: 6 additions & 7 deletions pkg/http/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,19 @@ type Server struct {
CheckTls bool
Routes *gin.RouterGroup

// Services used by the various HTTP routes.
// Services used by the various HTTP routes
Scanner *scanner.Scanner
}

// NewServer returns a new instance of Server.
// NewServer returns a new instance of Server
func NewServer(logger zerolog.Logger) *Server {
gin.SetMode(gin.ReleaseMode)

rateLimiter := tollbooth.NewLimiter(10, &limiter.ExpirableOptions{DefaultExpirationTTL: time.Hour})
rateLimiter.SetIPLookups([]string{"RemoteAddr", "X-Forwarded-For", "X-Real-IP"}).
SetMethods([]string{"GET", "POST"})

// Create a new server that wraps the net/http server & add a gin router.
// Create a new server that wraps the net/http server & add a gin router
s := &Server{
logger: logger,
lmt: rateLimiter,
Expand All @@ -55,15 +55,15 @@ func NewServer(logger zerolog.Logger) *Server {
logger.Fatal().Err(err).Msg("failed to set trusted proxies")
}

// Setup error handling routes.
// Setup error handling routes
s.router.NoRoute(func(c *gin.Context) {
c.JSON(404, gin.H{"message": "not found"})
})

v1 := s.router.Group("/api/v1")
v1.Use(s.handleRateLimit(s.lmt))

// Register unauthenticated routes.
// Register unauthenticated routes
{
s.Routes = v1.Group("")
s.registerScanRoutes(s.Routes)
Expand Down Expand Up @@ -113,8 +113,7 @@ func (s *Server) handleRateLimit(lmt *limiter.Limiter) gin.HandlerFunc {

func (s *Server) respond(c *gin.Context, code int, data interface{}) {
if code/100 == 4 || code/100 == 5 {
text := fmt.Sprintf("%v", data)
data = map[string]string{"message": text}
data = map[string]string{"message": fmt.Sprintf("%v", data)}
}

c.Writer.Header().Set("Content-Type", "application/json")
Expand Down
23 changes: 15 additions & 8 deletions pkg/scanner/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
func (s *Scanner) getDNSAnswers(domain string, recordType uint16) ([]dns.RR, error) {
req := new(dns.Msg)
req.SetQuestion(dns.Fqdn(domain), recordType)
req.SetEdns0(s.dnsBuffer, true) // increases the response buffer size

in, _, err := s.dc.Exchange(req, s.GetNS())
if err != nil {
Expand Down Expand Up @@ -76,8 +77,8 @@ func (s *Scanner) getTypeAAAA(domain string) (records []string, err error) {
return nil, err
}

for _, ans := range answers {
if t, ok := ans.(*dns.AAAA); ok {
for _, answer := range answers {
if t, ok := answer.(*dns.AAAA); ok {
records = append(records, t.AAAA.String())
}
}
Expand All @@ -95,9 +96,10 @@ func (s *Scanner) getTypeBIMI(domain string) (string, error) {
return "", nil
}

for _, txt := range txtRecords {
for index, txt := range txtRecords {
if strings.HasPrefix(txt, BIMIPrefix) {
return txt, nil
// TXT records can be split across multiple strings, so we need to join them
return strings.Join(txtRecords[index:], ""), nil
}
}
}
Expand Down Expand Up @@ -127,6 +129,7 @@ func (s *Scanner) getTypeDKIM(name string) (string, error) {

for _, dname := range []string{
s.DKIMSelector + "._domainkey." + name,
"email._domainkey." + name, // Generic
"google._domainkey." + name, // Google
"selector1._domainkey." + name, // Microsoft
"selector2._domainkey." + name, // Microsoft
Expand All @@ -143,9 +146,10 @@ func (s *Scanner) getTypeDKIM(name string) (string, error) {
return "", nil
}

for _, txt := range txtRecords {
for index, txt := range txtRecords {
if strings.HasPrefix(txt, DKIMPrefix) {
return txt, nil
// TXT records can be split across multiple strings, so we need to join them
return strings.Join(txtRecords[index:], ""), nil
}
}
}
Expand All @@ -163,9 +167,10 @@ func (s *Scanner) getTypeDMARC(domain string) (string, error) {
return "", nil
}

for _, txt := range txtRecords {
for index, txt := range txtRecords {
if strings.HasPrefix(txt, DMARCPrefix) {
return txt, nil
// TXT records can be split across multiple strings, so we need to join them
return strings.Join(txtRecords[index:], ""), nil
}
}
}
Expand Down Expand Up @@ -194,6 +199,8 @@ func (s *Scanner) getTypeSPF(domain string) (string, error) {
return "", err
}

fmt.Println(txtRecords)

for _, txt := range txtRecords {
if strings.HasPrefix(txt, SPFPrefix) {
if !strings.Contains(txt, "redirect=") {
Expand Down
Loading

0 comments on commit 9bd4110

Please sign in to comment.