poc implementation of dns templating

This commit is contained in:
Mzack9999 2020-04-22 22:45:02 +02:00
parent dc6b010625
commit 603456ddbb
9 changed files with 285 additions and 18 deletions

View File

@ -7,6 +7,15 @@ import (
"github.com/projectdiscovery/gologger" "github.com/projectdiscovery/gologger"
) )
// DefaultResolvers contains the list of resolvers known to be trusted.
var DefaultResolvers = []string{
"1.1.1.1:53", // Cloudflare
"1.0.0.1:53", // Cloudflare
"8.8.8.8:53", // Google
"8.8.4.4:53", // Google
"9.9.9.9:53", // Quad9
}
// Options contains the configuration options for tuning // Options contains the configuration options for tuning
// the template requesting process. // the template requesting process.
type Options struct { type Options struct {

View File

@ -13,17 +13,20 @@ import (
"sync" "sync"
"time" "time"
"github.com/miekg/dns"
"github.com/projectdiscovery/gologger" "github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/pkg/extractors" "github.com/projectdiscovery/nuclei/pkg/extractors"
"github.com/projectdiscovery/nuclei/pkg/matchers" "github.com/projectdiscovery/nuclei/pkg/matchers"
"github.com/projectdiscovery/nuclei/pkg/templates" "github.com/projectdiscovery/nuclei/pkg/templates"
retryabledns "github.com/projectdiscovery/retryabledns"
retryablehttp "github.com/projectdiscovery/retryablehttp-go" retryablehttp "github.com/projectdiscovery/retryablehttp-go"
) )
// Runner is a client for running the enumeration process. // Runner is a client for running the enumeration process.
type Runner struct { type Runner struct {
// client is the http client with retries // httpClient is the http client with retries
client *retryablehttp.Client httpClient *retryablehttp.Client
dnsClient *retryabledns.Client
// output is the output file to write if any // output is the output file to write if any
output *os.File output *os.File
outputMutex *sync.Mutex outputMutex *sync.Mutex
@ -43,7 +46,7 @@ func New(options *Options) (*Runner, error) {
retryablehttpOptions.RetryMax = options.Retries retryablehttpOptions.RetryMax = options.Retries
// Create the HTTP Client // Create the HTTP Client
client := retryablehttp.NewWithHTTPClient(&http.Client{ httpClient := retryablehttp.NewWithHTTPClient(&http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
MaxIdleConnsPerHost: -1, MaxIdleConnsPerHost: -1,
TLSClientConfig: &tls.Config{ TLSClientConfig: &tls.Config{
@ -57,9 +60,13 @@ func New(options *Options) (*Runner, error) {
return http.ErrUseLastResponse return http.ErrUseLastResponse
}, },
}, retryablehttpOptions) }, retryablehttpOptions)
client.CheckRetry = retryablehttp.HostSprayRetryPolicy() httpClient.CheckRetry = retryablehttp.HostSprayRetryPolicy()
runner.client = client runner.httpClient = httpClient
// Create the dns client
dnsClient, _ := retryabledns.New(DefaultResolvers, options.Retries)
runner.dnsClient = dnsClient
// Create the output file if asked // Create the output file if asked
if options.Output != "" { if options.Output != "" {
@ -165,18 +172,19 @@ func (r *Runner) processTemplateWithList(template *templates.Template, reader io
// sendRequest sends a request to the target based on a template // sendRequest sends a request to the target based on a template
func (r *Runner) sendRequest(template *templates.Template, URL string, writer *bufio.Writer) { func (r *Runner) sendRequest(template *templates.Template, URL string, writer *bufio.Writer) {
for _, request := range template.Requests { // process http requests
for _, request := range template.RequestsHTTP {
// Compile each request for the template based on the URL // Compile each request for the template based on the URL
compiledRequest, err := request.MakeRequest(URL) compiledRequest, err := request.MakeHTTPRequest(URL)
if err != nil { if err != nil {
gologger.Warningf("[%s] Could not make request %s: %s\n", template.ID, URL, err) gologger.Warningf("[%s] Could not make request %s: %s\n", template.ID, URL, err)
continue continue
} }
// Send the request to the target servers // Send the request to the target servers
reqLoop: reqLoopHTTP:
for _, req := range compiledRequest { for _, req := range compiledRequest {
resp, err := r.client.Do(req) resp, err := r.httpClient.Do(req)
if err != nil { if err != nil {
if resp != nil { if resp != nil {
resp.Body.Close() resp.Body.Close()
@ -206,7 +214,7 @@ func (r *Runner) sendRequest(template *templates.Template, URL string, writer *b
// Check if the matcher matched // Check if the matcher matched
if !matcher.Match(resp, body, headers) { if !matcher.Match(resp, body, headers) {
continue reqLoop continue reqLoopHTTP
} }
} }
@ -231,6 +239,46 @@ func (r *Runner) sendRequest(template *templates.Template, URL string, writer *b
} }
} }
} }
// process dns messages
for _, request := range template.RequestsDNS {
// Compile each request for the template based on the URL
compiledRequest, err := request.MakeDNSRequest(URL)
if err != nil {
gologger.Warningf("[%s] Could not make request %s: %s\n", template.ID, URL, err)
continue
}
// Send the request to the target servers
resp, err := r.dnsClient.Do(compiledRequest)
if err != nil {
gologger.Warningf("[%s] Could not send request %s: %s\n", template.ID, URL, err)
return
}
for _, matcher := range request.Matchers {
// Check if the matcher matched
if !matcher.MatchDNS(resp) {
continue
}
}
// If there is an extractor, run it.
var extractorResults []string
for _, extractor := range request.Extractors {
extractorResults = append(extractorResults, extractor.ExtractDNS(resp.String())...)
}
// All the matchers matched, print the output on the screen
output := buildOutputDNS(template, resp, extractorResults)
gologger.Silentf("%s", output)
if writer != nil {
r.outputMutex.Lock()
writer.WriteString(output)
r.outputMutex.Unlock()
}
}
} }
// buildOutput builds an output text for writing results // buildOutput builds an output text for writing results
@ -259,3 +307,32 @@ func buildOutput(template *templates.Template, req *retryablehttp.Request, extra
return builder.String() return builder.String()
} }
// buildOutput builds an output text for writing results
func buildOutputDNS(template *templates.Template, msg *dns.Msg, extractorResults []string) string {
builder := &strings.Builder{}
builder.WriteRune('[')
builder.WriteString(template.ID)
builder.WriteString("] ")
// domain name from question
if len(msg.Question) > 0 {
domain := msg.Question[0].Name
builder.WriteString(domain)
}
// If any extractors, write the results
if len(extractorResults) > 0 {
builder.WriteString(" [")
for i, result := range extractorResults {
builder.WriteString(result)
if i != len(extractorResults)-1 {
builder.WriteRune(',')
}
}
builder.WriteString("]")
}
builder.WriteRune('\n')
return builder.String()
}

View File

@ -2,8 +2,11 @@ package runner
import ( import (
"net/http" "net/http"
"net/url"
"strings" "strings"
"unsafe" "unsafe"
"github.com/asaskevich/govalidator"
) )
// unsafeToString converts byte slice to string with zero allocations // unsafeToString converts byte slice to string with zero allocations
@ -29,3 +32,25 @@ func headersToString(headers http.Header) string {
} }
return builder.String() return builder.String()
} }
// isURL tests a string to determine if it is a well-structured url or not.
func isURL(toTest string) bool {
_, err := url.ParseRequestURI(toTest)
if err != nil {
return false
}
u, err := url.Parse(toTest)
if err != nil || u.Scheme == "" || u.Host == "" {
return false
}
return true
}
// isDNS tests a string to determine if it is a well-structured dns or not
// even if it's oneliner, we leave it wrapped in a function call for
// future improvements
func isDNS(toTest string) bool {
return govalidator.IsDNSName(toTest)
}

View File

@ -16,6 +16,12 @@ func (e *Extractor) Extract(body, headers string) []string {
} }
} }
// ExtractDNS extracts response from dns message using a regex
func (e *Extractor) ExtractDNS(msg string) []string {
// Match the parts as required for regex check
return e.extractRegex(msg)
}
// extractRegex extracts text from a corpus and returns it // extractRegex extracts text from a corpus and returns it
func (e *Extractor) extractRegex(corpus string) []string { func (e *Extractor) extractRegex(corpus string) []string {
results := []string{} results := []string{}

View File

@ -4,6 +4,8 @@ import (
"encoding/hex" "encoding/hex"
"net/http" "net/http"
"strings" "strings"
"github.com/miekg/dns"
) )
// Match matches a http response again a given matcher // Match matches a http response again a given matcher
@ -53,6 +55,25 @@ func (m *Matcher) Match(resp *http.Response, body, headers string) bool {
return false return false
} }
// MatchDNS matches a dns response again a given matcher
func (m *Matcher) MatchDNS(msg *dns.Msg) bool {
switch m.matcherType {
// [WIP] add dns status code matcher
case SizeMatcher:
return m.matchSizeCode(msg.Len())
case WordsMatcher:
// Match for word check
return m.matchWords(msg.String())
case RegexMatcher:
// Match regex check
return m.matchRegex(msg.String())
case BinaryMatcher:
// Match binary characters check
return m.matchBinary(msg.String())
}
return false
}
// matchStatusCode matches a status code check against an HTTP Response // matchStatusCode matches a status code check against an HTTP Response
func (m *Matcher) matchStatusCode(statusCode int) bool { func (m *Matcher) matchStatusCode(statusCode int) bool {
// Iterate over all the status codes accepted as valid // Iterate over all the status codes accepted as valid

View File

@ -0,0 +1,112 @@
package requests
import (
"fmt"
"strings"
"github.com/miekg/dns"
"github.com/projectdiscovery/nuclei/pkg/extractors"
"github.com/projectdiscovery/nuclei/pkg/matchers"
"github.com/valyala/fasttemplate"
)
// DNSRequest contains a request to be made from a template
type DNSRequest struct {
Recursion bool `yaml:"recursion"`
// Path contains the path/s for the request
Name string `yaml:"name"`
Type string `yaml:"type"`
Class string `yaml:"class"`
// Matchers contains the detection mechanism for the request to identify
// whether the request was successful
Matchers []*matchers.Matcher `yaml:"matchers,omitempty"`
// Extractors contains the extraction mechanism for the request to identify
// and extract parts of the response.
Extractors []*extractors.Extractor `yaml:"extractors,omitempty"`
}
// MakeDNSRequest creates a *dns.Request from a request template
func (r *DNSRequest) MakeDNSRequest(domain string) (*dns.Msg, error) {
domain = dns.Fqdn(domain)
// Build a request on the specified URL
req := new(dns.Msg)
req.Id = dns.Id()
req.RecursionDesired = r.Recursion
var q dns.Question
t := fasttemplate.New(r.Name, "{{", "}}")
q.Name = dns.Fqdn(t.ExecuteString(map[string]interface{}{
"FQDN": domain,
}))
qclass, err := toQClass(r.Class)
if err != nil {
return nil, err
}
q.Qclass = qclass
qtype, err := toQType(r.Type)
if err != nil {
return nil, err
}
q.Qtype = qtype
req.Question = append(req.Question, q)
return req, nil
}
func toQType(ttype string) (rtype uint16, err error) {
ttype = strings.TrimSpace(strings.ToUpper(ttype))
switch ttype {
case "A":
rtype = dns.TypeA
case "NS":
rtype = dns.TypeNS
case "CNAME":
rtype = dns.TypeCNAME
case "SOA":
rtype = dns.TypeSOA
case "PTR":
rtype = dns.TypePTR
case "MX":
rtype = dns.TypeMX
case "TXT":
rtype = dns.TypeTXT
case "AAAA":
rtype = dns.TypeAAAA
default:
rtype = dns.TypeNone
err = fmt.Errorf("incorrect type")
}
return
}
func toQClass(tclass string) (rclass uint16, err error) {
tclass = strings.TrimSpace(strings.ToUpper(tclass))
switch tclass {
case "INET":
rclass = dns.ClassINET
case "CSNET":
rclass = dns.ClassCSNET
case "CHAOS":
rclass = dns.ClassCHAOS
case "HESIOD":
rclass = dns.ClassHESIOD
case "NONE":
rclass = dns.ClassNONE
case "ANY":
rclass = dns.ClassANY
default:
err = fmt.Errorf("incorrect class")
}
return
}

View File

@ -12,8 +12,8 @@ import (
"github.com/valyala/fasttemplate" "github.com/valyala/fasttemplate"
) )
// Request contains a request to be made from a template // HTTPRequest contains a request to be made from a template
type Request struct { type HTTPRequest struct {
// Method is the request method, whether GET, POST, PUT, etc // Method is the request method, whether GET, POST, PUT, etc
Method string `yaml:"method"` Method string `yaml:"method"`
// Path contains the path/s for the request // Path contains the path/s for the request
@ -30,8 +30,8 @@ type Request struct {
Extractors []*extractors.Extractor `yaml:"extractors,omitempty"` Extractors []*extractors.Extractor `yaml:"extractors,omitempty"`
} }
// MakeRequest creates a *http.Request from a request template // MakeHTTPRequest creates a *http.Request from a request template
func (r *Request) MakeRequest(baseURL string) ([]*retryablehttp.Request, error) { func (r *HTTPRequest) MakeHTTPRequest(baseURL string) ([]*retryablehttp.Request, error) {
parsed, err := url.Parse(baseURL) parsed, err := url.Parse(baseURL)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -22,8 +22,23 @@ func ParseTemplate(file string) (*Template, error) {
} }
f.Close() f.Close()
// Compile the matchers and the extractors // Compile the matchers and the extractors for http requests
for _, request := range template.Requests { for _, request := range template.RequestsHTTP {
for _, matcher := range request.Matchers {
if err = matcher.CompileMatchers(); err != nil {
return nil, err
}
}
for _, extractor := range request.Extractors {
if err := extractor.CompileExtractors(); err != nil {
return nil, err
}
}
}
// Compile the matchers and the extractors for dns requests
for _, request := range template.RequestsDNS {
for _, matcher := range request.Matchers { for _, matcher := range request.Matchers {
if err = matcher.CompileMatchers(); err != nil { if err = matcher.CompileMatchers(); err != nil {
return nil, err return nil, err

View File

@ -10,8 +10,10 @@ type Template struct {
ID string `yaml:"id"` ID string `yaml:"id"`
// Info contains information about the template // Info contains information about the template
Info Info `yaml:"info"` Info Info `yaml:"info"`
// Request contains the request to make in the template // Request contains the http request to make in the template
Requests []*requests.Request `yaml:"requests"` RequestsHTTP []*requests.HTTPRequest `yaml:"requests"`
// Request contains the dns request to make in the template
RequestsDNS []*requests.DNSRequest `yaml:"dns"`
} }
// Info contains information about the request template // Info contains information about the request template