More refactoring of nuclei packages

dev
Ice3man543 2020-12-24 20:47:41 +05:30
parent 2b50d99c0c
commit 60789f4ba2
9 changed files with 116 additions and 118 deletions

View File

@ -8,7 +8,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/projectdiscovery/gologger" "github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v2/internal/progress" "github.com/projectdiscovery/nuclei/v2/internal/progress"
"github.com/projectdiscovery/nuclei/v2/pkg/matchers"
"github.com/projectdiscovery/nuclei/v2/pkg/requests" "github.com/projectdiscovery/nuclei/v2/pkg/requests"
"github.com/projectdiscovery/nuclei/v2/pkg/templates" "github.com/projectdiscovery/nuclei/v2/pkg/templates"
) )
@ -95,45 +94,6 @@ func (e *DNSExecuter) ExecuteDNS(p *progress.Progress, reqURL string) *Result {
fmt.Fprintf(os.Stderr, "%s\n", resp.String()) fmt.Fprintf(os.Stderr, "%s\n", resp.String())
} }
matcherCondition := e.dnsRequest.GetMatchersCondition()
for _, matcher := range e.dnsRequest.Matchers {
// Check if the matcher matched
if !matcher.MatchDNS(resp) {
// If the condition is AND we haven't matched, return.
if matcherCondition == matchers.ANDCondition {
return result
}
} else {
// If the matcher has matched, and its an OR
// write the first output then move to next matcher.
if matcherCondition == matchers.ORCondition && len(e.dnsRequest.Extractors) == 0 {
e.writeOutputDNS(domain, compiledRequest, resp, matcher, nil)
result.GotResults = true
}
}
}
// All matchers have successfully completed so now start with the
// next task which is extraction of input from matchers.
var extractorResults []string
for _, extractor := range e.dnsRequest.Extractors {
for match := range extractor.ExtractDNS(resp) {
if !extractor.Internal {
extractorResults = append(extractorResults, match)
}
}
}
// Write a final string of output if matcher type is
// AND or if we have extractors for the mechanism too.
if len(e.dnsRequest.Extractors) > 0 || matcherCondition == matchers.ANDCondition {
e.writeOutputDNS(domain, compiledRequest, resp, nil, extractorResults)
result.GotResults = true
}
return result return result
} }

View File

@ -2,25 +2,8 @@ package extractors
import "github.com/projectdiscovery/nuclei/v2/pkg/types" import "github.com/projectdiscovery/nuclei/v2/pkg/types"
// Extract extracts data from an output structure based on user options // ExtractRegex extracts text from a corpus and returns it
func (e *Extractor) Extract(data map[string]interface{}) map[string]struct{} { func (e *Extractor) ExtractRegex(corpus string) map[string]struct{} {
part, ok := data[e.Part]
if !ok {
return nil
}
partString := types.ToString(part)
switch e.extractorType {
case RegexExtractor:
return e.extractRegex(partString)
case KValExtractor:
return e.extractKVal(data)
}
return nil
}
// extractRegex extracts text from a corpus and returns it
func (e *Extractor) extractRegex(corpus string) map[string]struct{} {
results := make(map[string]struct{}) results := make(map[string]struct{})
groupPlusOne := e.RegexGroup + 1 groupPlusOne := e.RegexGroup + 1
@ -41,8 +24,8 @@ func (e *Extractor) extractRegex(corpus string) map[string]struct{} {
return results return results
} }
// extractKVal extracts key value pairs from a data map // ExtractKval extracts key value pairs from a data map
func (e *Extractor) extractKVal(data map[string]interface{}) map[string]struct{} { func (e *Extractor) ExtractKval(data map[string]interface{}) map[string]struct{} {
results := make(map[string]struct{}) results := make(map[string]struct{})
for _, k := range e.KVal { for _, k := range e.KVal {

View File

@ -44,3 +44,8 @@ var ExtractorTypes = map[string]ExtractorType{
"regex": RegexExtractor, "regex": RegexExtractor,
"kval": KValExtractor, "kval": KValExtractor,
} }
// GetType returns the type of the matcher
func (e *Extractor) GetType() ExtractorType {
return e.extractorType
}

View File

@ -5,37 +5,8 @@ import (
"strings" "strings"
) )
// Match matches a generic data response again a given matcher // MatchStatusCode matches a status code check against a corpus
func (m *Matcher) Match(data map[string]interface{}) bool { func (m *Matcher) MatchStatusCode(statusCode int) bool {
part, ok := data[m.Part]
if !ok {
return false
}
partString := part.(string)
switch m.matcherType {
case StatusMatcher:
statusCode, ok := data["status_code"]
if !ok {
return false
}
return m.isNegative(m.matchStatusCode(statusCode.(int)))
case SizeMatcher:
return m.isNegative(m.matchSizeCode(len(partString)))
case WordsMatcher:
return m.isNegative(m.matchWords(partString))
case RegexMatcher:
return m.isNegative(m.matchRegex(partString))
case BinaryMatcher:
return m.isNegative(m.matchBinary(partString))
case DSLMatcher:
return m.isNegative(m.matchDSL(data))
}
return false
}
// matchStatusCode matches a status code check against an HTTP Response
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
// //
// Status codes don't support AND conditions. // Status codes don't support AND conditions.
@ -50,8 +21,8 @@ func (m *Matcher) matchStatusCode(statusCode int) bool {
return false return false
} }
// matchStatusCode matches a size check against an HTTP Response // MatchSize matches a size check against a corpus
func (m *Matcher) matchSizeCode(length int) bool { func (m *Matcher) MatchSize(length int) bool {
// Iterate over all the sizes accepted as valid // Iterate over all the sizes accepted as valid
// //
// Sizes codes don't support AND conditions. // Sizes codes don't support AND conditions.
@ -66,8 +37,8 @@ func (m *Matcher) matchSizeCode(length int) bool {
return false return false
} }
// matchWords matches a word check against an HTTP Response/Headers. // MatchWords matches a word check against a corpus.
func (m *Matcher) matchWords(corpus string) bool { func (m *Matcher) MatchWords(corpus string) bool {
// Iterate over all the words accepted as valid // Iterate over all the words accepted as valid
for i, word := range m.Words { for i, word := range m.Words {
// Continue if the word doesn't match // Continue if the word doesn't match
@ -94,8 +65,8 @@ func (m *Matcher) matchWords(corpus string) bool {
return false return false
} }
// matchRegex matches a regex check against an HTTP Response/Headers. // MatchRegex matches a regex check against a corpus
func (m *Matcher) matchRegex(corpus string) bool { func (m *Matcher) MatchRegex(corpus string) bool {
// Iterate over all the regexes accepted as valid // Iterate over all the regexes accepted as valid
for i, regex := range m.regexCompiled { for i, regex := range m.regexCompiled {
// Continue if the regex doesn't match // Continue if the regex doesn't match
@ -122,8 +93,8 @@ func (m *Matcher) matchRegex(corpus string) bool {
return false return false
} }
// matchWords matches a word check against an HTTP Response/Headers. // MatchBinary matches a binary check against a corpus
func (m *Matcher) matchBinary(corpus string) bool { func (m *Matcher) MatchBinary(corpus string) bool {
// Iterate over all the words accepted as valid // Iterate over all the words accepted as valid
for i, binary := range m.Binary { for i, binary := range m.Binary {
// Continue if the word doesn't match // Continue if the word doesn't match
@ -151,11 +122,11 @@ func (m *Matcher) matchBinary(corpus string) bool {
return false return false
} }
// matchDSL matches on a generic map result // MatchDSL matches on a generic map result
func (m *Matcher) matchDSL(mp map[string]interface{}) bool { func (m *Matcher) MatchDSL(data map[string]interface{}) bool {
// Iterate over all the expressions accepted as valid // Iterate over all the expressions accepted as valid
for i, expression := range m.dslCompiled { for i, expression := range m.dslCompiled {
result, err := expression.Evaluate(mp) result, err := expression.Evaluate(data)
if err != nil { if err != nil {
continue continue
} }

View File

@ -9,22 +9,22 @@ import (
func TestANDCondition(t *testing.T) { func TestANDCondition(t *testing.T) {
m := &Matcher{condition: ANDCondition, Words: []string{"a", "b"}} m := &Matcher{condition: ANDCondition, Words: []string{"a", "b"}}
matched := m.matchWords("a b") matched := m.MatchWords("a b")
require.True(t, matched, "Could not match valid AND condition") require.True(t, matched, "Could not match valid AND condition")
matched = m.matchWords("b") matched = m.MatchWords("b")
require.False(t, matched, "Could match invalid AND condition") require.False(t, matched, "Could match invalid AND condition")
} }
func TestORCondition(t *testing.T) { func TestORCondition(t *testing.T) {
m := &Matcher{condition: ORCondition, Words: []string{"a", "b"}} m := &Matcher{condition: ORCondition, Words: []string{"a", "b"}}
matched := m.matchWords("a b") matched := m.MatchWords("a b")
require.True(t, matched, "Could not match valid OR condition") require.True(t, matched, "Could not match valid OR condition")
matched = m.matchWords("b") matched = m.MatchWords("b")
require.True(t, matched, "Could not match valid OR condition") require.True(t, matched, "Could not match valid OR condition")
matched = m.matchWords("c") matched = m.MatchWords("c")
require.False(t, matched, "Could match invalid OR condition") require.False(t, matched, "Could match invalid OR condition")
} }

View File

@ -88,11 +88,15 @@ var ConditionTypes = map[string]ConditionType{
"or": ORCondition, "or": ORCondition,
} }
// isNegative reverts the results of the match if the matcher // Result reverts the results of the match if the matcher is of type negative.
// is of type negative. func (m *Matcher) Result(data bool) bool {
func (m *Matcher) isNegative(data bool) bool {
if m.Negative { if m.Negative {
return !data return !data
} }
return data return data
} }
// GetType returns the type of the matcher
func (m *Matcher) GetType() MatcherType {
return m.matcherType
}

View File

@ -3,6 +3,7 @@ package operators
import ( import (
"github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors" "github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors"
"github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers" "github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers"
"github.com/projectdiscovery/nuclei/v2/pkg/output"
) )
// Operators contains the operators that can be applied on protocols // Operators contains the operators that can be applied on protocols
@ -31,12 +32,20 @@ type Result struct {
Matches map[string]struct{} Matches map[string]struct{}
// Extracts contains all the data extracted from inputs // Extracts contains all the data extracted from inputs
Extracts map[string][]string Extracts map[string][]string
// OutputExtracts is the list of extracts to be displayed on screen.
OutputExtracts []string
// DynamicValues contains any dynamic values to be templated // DynamicValues contains any dynamic values to be templated
DynamicValues map[string]string DynamicValues map[string]string
} }
// MatchFunc performs matching operation for a matcher on model and returns true or false.
type MatchFunc func(data map[string]interface{}, matcher *matchers.Matcher) bool
// ExtractFunc performs extracting operation for a extractor on model and returns true or false.
type ExtractFunc func(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{}
// Execute executes the operators on data and returns a result structure // Execute executes the operators on data and returns a result structure
func (r *Operators) Execute(data map[string]interface{}) (*Result, bool) { func (r *Operators) Execute(data output.Event, match MatchFunc, extract ExtractFunc) (*Result, bool) {
matcherCondition := r.GetMatchersCondition() matcherCondition := r.GetMatchersCondition()
result := &Result{ result := &Result{
@ -46,7 +55,7 @@ func (r *Operators) Execute(data map[string]interface{}) (*Result, bool) {
} }
for _, matcher := range r.Matchers { for _, matcher := range r.Matchers {
// Check if the matcher matched // Check if the matcher matched
if !matcher.Match(data) { if !match(data, matcher) {
// If the condition is AND we haven't matched, try next request. // If the condition is AND we haven't matched, try next request.
if matcherCondition == matchers.ANDCondition { if matcherCondition == matchers.ANDCondition {
return nil, false return nil, false
@ -62,9 +71,10 @@ func (r *Operators) Execute(data map[string]interface{}) (*Result, bool) {
// All matchers have successfully completed so now start with the // All matchers have successfully completed so now start with the
// next task which is extraction of input from matchers. // next task which is extraction of input from matchers.
var extractorResults, outputExtractorResults []string
for _, extractor := range r.Extractors { for _, extractor := range r.Extractors {
for match := range extractor.Extract(data) { var extractorResults []string
for match := range extract(data, extractor) {
extractorResults = append(extractorResults, match) extractorResults = append(extractorResults, match)
if extractor.Internal { if extractor.Internal {
@ -72,7 +82,7 @@ func (r *Operators) Execute(data map[string]interface{}) (*Result, bool) {
result.DynamicValues[extractor.Name] = match result.DynamicValues[extractor.Name] = match
} }
} else { } else {
outputExtractorResults = append(outputExtractorResults, match) result.OutputExtracts = append(result.OutputExtracts, match)
} }
} }
result.Extracts[extractor.Name] = extractorResults result.Extracts[extractor.Name] = extractorResults

View File

@ -5,8 +5,69 @@ import (
"net/http/httputil" "net/http/httputil"
"strings" "strings"
"time" "time"
"github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors"
"github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers"
) )
// Match matches a generic data response again a given matcher
func (r *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) bool {
part, ok := data[matcher.Part]
if !ok {
return false
}
partString := part.(string)
switch partString {
case "header":
partString = "all_headers"
case "all":
partString = "raw"
}
switch matcher.GetType() {
case matchers.StatusMatcher:
statusCode, ok := data["status_code"]
if !ok {
return false
}
return matcher.Result(matcher.MatchStatusCode(statusCode.(int)))
case matchers.SizeMatcher:
return matcher.Result(matcher.MatchSize(len(partString)))
case matchers.WordsMatcher:
return matcher.Result(matcher.MatchWords(partString))
case matchers.RegexMatcher:
return matcher.Result(matcher.MatchRegex(partString))
case matchers.BinaryMatcher:
return matcher.Result(matcher.MatchBinary(partString))
case matchers.DSLMatcher:
return matcher.Result(matcher.MatchDSL(data))
}
return false
}
// Extract performs extracting operation for a extractor on model and returns true or false.
func (r *Request) Extract(data map[string]interface{}, extractor *extractors.Extractor) map[string]struct{} {
part, ok := data[extractor.Part]
if !ok {
return nil
}
partString := part.(string)
switch partString {
case "header":
partString = "all_headers"
case "all":
partString = "raw"
}
switch extractor.GetType() {
case extractors.RegexExtractor:
return extractor.ExtractRegex(partString)
case extractors.KValExtractor:
return extractor.ExtractKval(data)
}
return nil
}
// responseToDSLMap converts a HTTP response to a map for use in DSL matching // responseToDSLMap converts a HTTP response to a map for use in DSL matching
func responseToDSLMap(resp *http.Response, body, headers string, duration time.Duration, extra map[string]interface{}) map[string]interface{} { func responseToDSLMap(resp *http.Response, body, headers string, duration time.Duration, extra map[string]interface{}) map[string]interface{} {
data := make(map[string]interface{}, len(extra)+6+len(resp.Header)+len(resp.Cookies())) data := make(map[string]interface{}, len(extra)+6+len(resp.Header)+len(resp.Cookies()))
@ -25,13 +86,11 @@ func responseToDSLMap(resp *http.Response, body, headers string, duration time.D
k = strings.ToLower(strings.TrimSpace(strings.ReplaceAll(k, "-", "_"))) k = strings.ToLower(strings.TrimSpace(strings.ReplaceAll(k, "-", "_")))
data[k] = strings.Join(v, " ") data[k] = strings.Join(v, " ")
} }
data["header"] = headers
data["all_headers"] = headers data["all_headers"] = headers
if r, err := httputil.DumpResponse(resp, true); err == nil { if r, err := httputil.DumpResponse(resp, true); err == nil {
rawString := string(r) rawString := string(r)
data["raw"] = rawString data["raw"] = rawString
data["all"] = rawString
} }
data["duration"] = duration.Seconds() data["duration"] = duration.Seconds()
return data return data

View File

@ -1,6 +1,8 @@
package protocols package protocols
import ( import (
"github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors"
"github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers"
"github.com/projectdiscovery/nuclei/v2/pkg/output" "github.com/projectdiscovery/nuclei/v2/pkg/output"
"github.com/projectdiscovery/nuclei/v2/pkg/types" "github.com/projectdiscovery/nuclei/v2/pkg/types"
"go.uber.org/ratelimit" "go.uber.org/ratelimit"
@ -12,6 +14,10 @@ type Executer interface {
Compile(options ExecuterOptions) error Compile(options ExecuterOptions) error
// Requests returns the total number of requests the rule will perform // Requests returns the total number of requests the rule will perform
Requests() int64 Requests() int64
// Match performs matching operation for a matcher on model and returns true or false.
Match(data map[string]interface{}, matcher *matchers.Matcher) bool
// Extract performs extracting operation for a extractor on model and returns true or false.
Extract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{}
// Execute executes the protocol requests and returns an output event channel. // Execute executes the protocol requests and returns an output event channel.
Execute(input string) (bool, error) Execute(input string) (bool, error)
// ExecuteWithResults executes the protocol requests and returns results instead of writing them. // ExecuteWithResults executes the protocol requests and returns results instead of writing them.