2020-12-21 09:01:32 +00:00
package operators
import (
2022-03-11 12:32:55 +00:00
"fmt"
2021-10-05 19:02:09 +00:00
"strconv"
2022-11-25 16:22:50 +00:00
"strings"
2021-10-05 19:02:09 +00:00
2020-12-24 20:54:55 +00:00
"github.com/pkg/errors"
2021-07-19 18:04:08 +00:00
2023-10-17 12:14:13 +00:00
"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors"
"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/excludematchers"
2022-11-06 20:24:23 +00:00
sliceutil "github.com/projectdiscovery/utils/slice"
2020-12-21 09:01:32 +00:00
)
// Operators contains the operators that can be applied on protocols
type Operators struct {
2021-07-27 10:33:56 +00:00
// description: |
// Matchers contains the detection mechanism for the request to identify
// whether the request was successful by doing pattern matching
// on request/responses.
//
2021-09-07 14:31:46 +00:00
// Multiple matchers can be combined with `matcher-condition` flag
2021-07-27 10:33:56 +00:00
// which accepts either `and` or `or` as argument.
2023-02-07 08:10:40 +00:00
Matchers [ ] * matchers . Matcher ` yaml:"matchers,omitempty" json:"matchers,omitempty" jsonschema:"title=matchers to run on response,description=Detection mechanism to identify whether the request was successful by doing pattern matching" `
2021-07-27 10:33:56 +00:00
// description: |
// Extractors contains the extraction mechanism for the request to identify
// and extract parts of the response.
2023-02-07 08:10:40 +00:00
Extractors [ ] * extractors . Extractor ` yaml:"extractors,omitempty" json:"extractors,omitempty" jsonschema:"title=extractors to run on response,description=Extractors contains the extraction mechanism for the request to identify and extract parts of the response" `
2021-07-27 10:33:56 +00:00
// description: |
// MatchersCondition is the condition between the matchers. Default is OR.
// values:
// - "and"
// - "or"
2023-02-07 08:10:40 +00:00
MatchersCondition string ` yaml:"matchers-condition,omitempty" json:"matchers-condition,omitempty" jsonschema:"title=condition between the matchers,description=Conditions between the matchers,enum=and,enum=or" `
2020-12-21 09:01:32 +00:00
// cached variables that may be used along with request.
matchersCondition matchers . ConditionType
2022-06-24 17:39:27 +00:00
// TemplateID is the ID of the template for matcher
2022-11-01 10:40:41 +00:00
TemplateID string ` json:"-" yaml:"-" jsonschema:"-" `
2022-06-24 17:39:27 +00:00
// ExcludeMatchers is a list of excludeMatchers items
2022-11-01 10:40:41 +00:00
ExcludeMatchers * excludematchers . ExcludeMatchers ` json:"-" yaml:"-" jsonschema:"-" `
2020-12-21 09:01:32 +00:00
}
2020-12-24 20:54:55 +00:00
// Compile compiles the operators as well as their corresponding matchers and extractors
2021-10-01 11:30:04 +00:00
func ( operators * Operators ) Compile ( ) error {
if operators . MatchersCondition != "" {
operators . matchersCondition = matchers . ConditionTypes [ operators . MatchersCondition ]
2020-12-24 20:54:55 +00:00
} else {
2021-10-01 11:30:04 +00:00
operators . matchersCondition = matchers . ORCondition
2020-12-24 20:54:55 +00:00
}
2021-10-01 11:30:04 +00:00
for _ , matcher := range operators . Matchers {
2020-12-24 20:54:55 +00:00
if err := matcher . CompileMatchers ( ) ; err != nil {
return errors . Wrap ( err , "could not compile matcher" )
}
}
2021-10-01 11:30:04 +00:00
for _ , extractor := range operators . Extractors {
2020-12-24 20:54:55 +00:00
if err := extractor . CompileExtractors ( ) ; err != nil {
return errors . Wrap ( err , "could not compile extractor" )
}
}
return nil
}
2020-12-21 09:01:32 +00:00
// GetMatchersCondition returns the condition for the matchers
2021-10-01 11:30:04 +00:00
func ( operators * Operators ) GetMatchersCondition ( ) matchers . ConditionType {
return operators . matchersCondition
2020-12-21 09:01:32 +00:00
}
2020-12-24 07:26:28 +00:00
// Result is a result structure created from operators running on data.
type Result struct {
2021-02-21 11:01:34 +00:00
// Matched is true if any matchers matched
Matched bool
// Extracted is true if any result type values were extracted
Extracted bool
2020-12-24 07:26:28 +00:00
// Matches is a map of matcher names that we matched
2021-10-01 11:24:45 +00:00
Matches map [ string ] [ ] string
2020-12-24 07:26:28 +00:00
// Extracts contains all the data extracted from inputs
Extracts map [ string ] [ ] string
2020-12-24 15:17:41 +00:00
// OutputExtracts is the list of extracts to be displayed on screen.
OutputExtracts [ ] string
2022-02-26 08:06:43 +00:00
outputUnique map [ string ] struct { }
2022-01-13 07:52:43 +00:00
2020-12-24 07:26:28 +00:00
// DynamicValues contains any dynamic values to be templated
2021-11-24 15:38:08 +00:00
DynamicValues map [ string ] [ ] string
2020-12-28 20:00:07 +00:00
// PayloadValues contains payload values provided by user. (Optional)
PayloadValues map [ string ] interface { }
2022-02-10 10:29:05 +00:00
// Optional lineCounts for file protocol
LineCount string
2024-01-07 23:42:11 +00:00
// Operators is reference to operators that generated this result (Read-Only)
Operators * Operators
2020-12-24 07:26:28 +00:00
}
2022-11-25 16:22:50 +00:00
func ( result * Result ) HasMatch ( name string ) bool {
return result . hasItem ( name , result . Matches )
}
func ( result * Result ) HasExtract ( name string ) bool {
return result . hasItem ( name , result . Extracts )
}
func ( result * Result ) hasItem ( name string , m map [ string ] [ ] string ) bool {
for matchName := range m {
if strings . EqualFold ( name , matchName ) {
return true
}
}
return false
}
2021-11-24 15:38:08 +00:00
// MakeDynamicValuesCallback takes an input dynamic values map and calls
// the callback function with all variations of the data in input in form
// of map[string]string (interface{}).
2021-11-24 17:10:17 +00:00
func MakeDynamicValuesCallback ( input map [ string ] [ ] string , iterateAllValues bool , callback func ( map [ string ] interface { } ) bool ) {
2021-11-24 15:38:08 +00:00
output := make ( map [ string ] interface { } , len ( input ) )
2021-11-24 17:10:17 +00:00
if ! iterateAllValues {
for k , v := range input {
if len ( v ) > 0 {
output [ k ] = v [ 0 ]
}
}
callback ( output )
return
}
2021-11-24 15:38:08 +00:00
inputIndex := make ( map [ string ] int , len ( input ) )
var maxValue int
for _ , v := range input {
if len ( v ) > maxValue {
maxValue = len ( v )
}
}
for i := 0 ; i < maxValue ; i ++ {
for k , v := range input {
if len ( v ) == 0 {
continue
}
if len ( v ) == 1 {
output [ k ] = v [ 0 ]
continue
}
if gotIndex , ok := inputIndex [ k ] ; ! ok {
inputIndex [ k ] = 0
output [ k ] = v [ 0 ]
} else {
newIndex := gotIndex + 1
if newIndex >= len ( v ) {
output [ k ] = v [ len ( v ) - 1 ]
continue
}
output [ k ] = v [ newIndex ]
inputIndex [ k ] = newIndex
}
}
// skip if the callback says so
if callback ( output ) {
return
}
}
}
2021-04-18 10:40:10 +00:00
// Merge merges a result structure into the other.
func ( r * Result ) Merge ( result * Result ) {
if ! r . Matched && result . Matched {
r . Matched = result . Matched
}
if ! r . Extracted && result . Extracted {
r . Extracted = result . Extracted
}
for k , v := range result . Matches {
2022-03-02 07:31:23 +00:00
r . Matches [ k ] = sliceutil . Dedupe ( append ( r . Matches [ k ] , v ... ) )
2021-04-18 10:40:10 +00:00
}
for k , v := range result . Extracts {
2022-03-02 07:31:23 +00:00
r . Extracts [ k ] = sliceutil . Dedupe ( append ( r . Extracts [ k ] , v ... ) )
2021-04-18 10:40:10 +00:00
}
2022-01-13 07:52:43 +00:00
2022-02-26 08:06:43 +00:00
r . outputUnique = make ( map [ string ] struct { } )
2022-01-13 07:52:43 +00:00
output := r . OutputExtracts
r . OutputExtracts = make ( [ ] string , 0 , len ( output ) )
for _ , v := range output {
2022-02-26 08:06:43 +00:00
if _ , ok := r . outputUnique [ v ] ; ! ok {
r . outputUnique [ v ] = struct { } { }
2022-01-13 07:52:43 +00:00
r . OutputExtracts = append ( r . OutputExtracts , v )
}
}
for _ , v := range result . OutputExtracts {
2022-02-26 08:06:43 +00:00
if _ , ok := r . outputUnique [ v ] ; ! ok {
r . outputUnique [ v ] = struct { } { }
2022-01-13 07:52:43 +00:00
r . OutputExtracts = append ( r . OutputExtracts , v )
}
}
2021-04-18 10:40:10 +00:00
for k , v := range result . DynamicValues {
2024-01-07 23:42:11 +00:00
if _ , ok := r . DynamicValues [ k ] ; ! ok {
r . DynamicValues [ k ] = v
} else {
r . DynamicValues [ k ] = sliceutil . Dedupe ( append ( r . DynamicValues [ k ] , v ... ) )
}
2021-04-18 10:40:10 +00:00
}
for k , v := range result . PayloadValues {
r . PayloadValues [ k ] = v
}
}
2020-12-24 15:17:41 +00:00
// MatchFunc performs matching operation for a matcher on model and returns true or false.
2021-09-29 16:43:46 +00:00
type MatchFunc func ( data map [ string ] interface { } , matcher * matchers . Matcher ) ( bool , [ ] string )
2020-12-24 15:17:41 +00:00
2021-09-07 14:31:46 +00:00
// ExtractFunc performs extracting operation for an extractor on model and returns true or false.
2020-12-24 15:17:41 +00:00
type ExtractFunc func ( data map [ string ] interface { } , matcher * extractors . Extractor ) map [ string ] struct { }
2020-12-24 07:26:28 +00:00
// Execute executes the operators on data and returns a result structure
2021-10-12 17:06:55 +00:00
func ( operators * Operators ) Execute ( data map [ string ] interface { } , match MatchFunc , extract ExtractFunc , isDebug bool ) ( * Result , bool ) {
2021-10-01 11:30:04 +00:00
matcherCondition := operators . GetMatchersCondition ( )
2020-12-24 07:26:28 +00:00
2020-12-30 07:56:55 +00:00
var matches bool
2020-12-24 07:26:28 +00:00
result := & Result {
2021-10-01 11:24:45 +00:00
Matches : make ( map [ string ] [ ] string ) ,
2020-12-24 07:26:28 +00:00
Extracts : make ( map [ string ] [ ] string ) ,
2021-11-24 15:38:08 +00:00
DynamicValues : make ( map [ string ] [ ] string ) ,
2022-02-26 08:06:43 +00:00
outputUnique : make ( map [ string ] struct { } ) ,
2024-01-07 23:42:11 +00:00
Operators : operators ,
2020-12-24 07:26:28 +00:00
}
2021-04-18 10:40:10 +00:00
2024-01-04 23:55:31 +00:00
// state variable to check if all extractors are internal
var allInternalExtractors bool = true
2021-03-09 11:05:53 +00:00
// Start with the extractors first and evaluate them.
2021-10-01 11:30:04 +00:00
for _ , extractor := range operators . Extractors {
2024-01-04 23:55:31 +00:00
if ! extractor . Internal && allInternalExtractors {
allInternalExtractors = false
}
2020-12-24 15:17:41 +00:00
var extractorResults [ ] string
for match := range extract ( data , extractor ) {
2020-12-24 07:26:28 +00:00
extractorResults = append ( extractorResults , match )
if extractor . Internal {
2021-11-24 15:38:08 +00:00
if data , ok := result . DynamicValues [ extractor . Name ] ; ! ok {
result . DynamicValues [ extractor . Name ] = [ ] string { match }
} else {
result . DynamicValues [ extractor . Name ] = append ( data , match )
2020-12-24 07:26:28 +00:00
}
} else {
2022-02-26 08:06:43 +00:00
if _ , ok := result . outputUnique [ match ] ; ! ok {
2022-01-13 07:52:43 +00:00
result . OutputExtracts = append ( result . OutputExtracts , match )
2022-02-26 08:06:43 +00:00
result . outputUnique [ match ] = struct { } { }
2022-01-13 07:52:43 +00:00
}
2020-12-24 07:26:28 +00:00
}
}
2021-01-11 15:41:35 +00:00
if len ( extractorResults ) > 0 && ! extractor . Internal && extractor . Name != "" {
2020-12-30 07:56:55 +00:00
result . Extracts [ extractor . Name ] = extractorResults
}
2024-01-04 23:55:31 +00:00
// update data with whatever was extracted doesn't matter if it is internal or not (skip unless it empty)
if len ( extractorResults ) > 0 {
data [ extractor . Name ] = getExtractedValue ( extractorResults )
}
2020-12-24 07:26:28 +00:00
}
2022-03-11 12:32:55 +00:00
// expose dynamic values to same request matchers
if len ( result . DynamicValues ) > 0 {
dataDynamicValues := make ( map [ string ] interface { } )
for dynName , dynValues := range result . DynamicValues {
if len ( dynValues ) > 1 {
for dynIndex , dynValue := range dynValues {
dynKeyName := fmt . Sprintf ( "%s%d" , dynName , dynIndex )
dataDynamicValues [ dynKeyName ] = dynValue
}
2022-05-30 21:16:51 +00:00
dataDynamicValues [ dynName ] = dynValues
2022-03-11 12:32:55 +00:00
} else {
dataDynamicValues [ dynName ] = dynValues [ 0 ]
}
}
data = generators . MergeMaps ( data , dataDynamicValues )
}
2021-10-05 19:02:09 +00:00
for matcherIndex , matcher := range operators . Matchers {
2022-06-24 17:39:27 +00:00
// Skip matchers that are in the blocklist
if operators . ExcludeMatchers != nil {
if operators . ExcludeMatchers . Match ( operators . TemplateID , matcher . Name ) {
continue
}
}
2021-10-01 11:24:45 +00:00
if isMatch , matched := match ( data , matcher ) ; isMatch {
2021-10-12 17:06:55 +00:00
if isDebug { // matchers without an explicit name or with AND condition should only be made visible if debug is enabled
matcherName := getMatcherName ( matcher , matcherIndex )
result . Matches [ matcherName ] = matched
} else { // if it's a "named" matcher with OR condition, then display it
if matcherCondition == matchers . ORCondition && matcher . Name != "" {
result . Matches [ matcher . Name ] = matched
}
}
2021-03-09 11:05:53 +00:00
matches = true
2021-09-29 16:43:46 +00:00
} else if matcherCondition == matchers . ANDCondition {
if len ( result . DynamicValues ) > 0 {
return result , true
}
return nil , false
2021-03-09 11:05:53 +00:00
}
}
2021-02-21 11:01:34 +00:00
result . Matched = matches
result . Extracted = len ( result . OutputExtracts ) > 0
2024-01-04 23:55:31 +00:00
if len ( result . DynamicValues ) > 0 && allInternalExtractors {
// only return early if all extractors are internal
// if some are internal and some are not then followthrough
2021-02-21 11:01:34 +00:00
return result , true
}
2021-09-16 17:35:43 +00:00
2021-09-29 16:43:46 +00:00
// Don't print if we have matchers, and they have not matched, regardless of extractor
2021-10-01 11:30:04 +00:00
if len ( operators . Matchers ) > 0 && ! matches {
2020-12-30 07:56:55 +00:00
return nil , false
}
2020-12-24 07:26:28 +00:00
// Write a final string of output if matcher type is
// AND or if we have extractors for the mechanism too.
2021-02-21 11:01:34 +00:00
if len ( result . Extracts ) > 0 || len ( result . OutputExtracts ) > 0 || matches {
2020-12-24 07:26:28 +00:00
return result , true
}
return nil , false
}
2021-07-06 13:04:25 +00:00
2021-10-05 19:02:09 +00:00
func getMatcherName ( matcher * matchers . Matcher , matcherIndex int ) string {
if matcher . Name != "" {
return matcher . Name
} else {
2021-11-19 10:54:09 +00:00
return matcher . Type . String ( ) + "-" + strconv . Itoa ( matcherIndex + 1 ) // making the index start from 1 to be more readable
2021-10-05 19:02:09 +00:00
}
}
2021-07-06 13:04:25 +00:00
// ExecuteInternalExtractors executes internal dynamic extractors
2021-10-01 11:30:04 +00:00
func ( operators * Operators ) ExecuteInternalExtractors ( data map [ string ] interface { } , extract ExtractFunc ) map [ string ] interface { } {
2021-07-06 13:04:25 +00:00
dynamicValues := make ( map [ string ] interface { } )
// Start with the extractors first and evaluate them.
2021-10-01 11:30:04 +00:00
for _ , extractor := range operators . Extractors {
2021-07-06 13:04:25 +00:00
if ! extractor . Internal {
continue
}
for match := range extract ( data , extractor ) {
if _ , ok := dynamicValues [ extractor . Name ] ; ! ok {
dynamicValues [ extractor . Name ] = match
}
}
}
return dynamicValues
}
2022-12-20 20:59:28 +00:00
// IsEmpty determines if the operator has matchers or extractors
func ( operators * Operators ) IsEmpty ( ) bool {
return operators . Len ( ) == 0
}
// Len calculates the sum of the number of matchers and extractors
func ( operators * Operators ) Len ( ) int {
return len ( operators . Matchers ) + len ( operators . Extractors )
}
2024-01-04 23:55:31 +00:00
// getExtractedValue takes array of extracted values if it only has one value
// then it is flattened and returned as a string else original type is returned
func getExtractedValue ( values [ ] string ) any {
if len ( values ) == 1 {
return values [ 0 ]
} else {
return values
}
}