2021-11-22 00:19:53 +01:00
|
|
|
package templates
|
2021-01-13 12:18:56 +05:30
|
|
|
|
|
|
|
|
import (
|
2021-11-22 00:19:53 +01:00
|
|
|
"fmt"
|
2021-11-29 14:38:45 +01:00
|
|
|
"sort"
|
|
|
|
|
"strings"
|
2021-11-22 00:19:53 +01:00
|
|
|
|
2021-02-02 12:10:47 +05:30
|
|
|
"github.com/projectdiscovery/gologger"
|
2023-10-17 17:44:13 +05:30
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/model"
|
|
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/operators"
|
|
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
|
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
|
|
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/writer"
|
2023-11-27 19:54:45 +01:00
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/scan"
|
2023-10-17 17:44:13 +05:30
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
|
2022-11-06 21:24:23 +01:00
|
|
|
cryptoutil "github.com/projectdiscovery/utils/crypto"
|
2021-01-13 12:18:56 +05:30
|
|
|
)
|
|
|
|
|
|
2021-11-22 00:19:53 +01:00
|
|
|
// Cluster clusters a list of templates into a lesser number if possible based
|
|
|
|
|
// on the similarity between the sent requests.
|
|
|
|
|
//
|
|
|
|
|
// If the attributes match, multiple requests can be clustered into a single
|
|
|
|
|
// request which saves time and network resources during execution.
|
2022-03-11 22:08:30 +05:30
|
|
|
//
|
|
|
|
|
// The clusterer goes through all the templates, looking for templates with a single
|
2023-01-17 18:20:05 +05:30
|
|
|
// HTTP/DNS/TLS request to an endpoint (multiple requests aren't clustered as of now).
|
2022-03-11 22:08:30 +05:30
|
|
|
//
|
|
|
|
|
// All the templates are iterated and any templates with request that is identical
|
2023-01-17 13:01:20 +05:30
|
|
|
// to the first individual request is compared for equality.
|
2022-03-11 22:08:30 +05:30
|
|
|
// The equality check is performed as described below -
|
|
|
|
|
//
|
2023-08-01 14:33:43 -04:00
|
|
|
// Cases where clustering is not performed (request is considered different)
|
2022-10-03 12:12:20 +02:00
|
|
|
// - If request contains payloads,raw,body,unsafe,req-condition,name attributes
|
2023-11-18 10:32:10 +03:00
|
|
|
// - If request methods,max-redirects,disable-cookie,redirects are not equal
|
2022-10-03 12:12:20 +02:00
|
|
|
// - If request paths aren't identical.
|
|
|
|
|
// - If request headers aren't identical
|
2023-01-17 13:01:20 +05:30
|
|
|
// - Similarly for DNS, only identical DNS requests are clustered to a target.
|
2023-01-17 18:20:05 +05:30
|
|
|
// - Similarly for TLS, only identical TLS requests are clustered to a target.
|
2022-03-11 22:08:30 +05:30
|
|
|
//
|
|
|
|
|
// If multiple requests are identified as identical, they are appended to a slice.
|
|
|
|
|
// Finally, the engine creates a single executer with a clusteredexecuter for all templates
|
|
|
|
|
// in a cluster.
|
2023-09-13 18:57:48 +02:00
|
|
|
func Cluster(list []*Template) [][]*Template {
|
2024-06-27 07:44:43 +00:00
|
|
|
http := make(map[uint64][]*Template)
|
|
|
|
|
dns := make(map[uint64][]*Template)
|
|
|
|
|
ssl := make(map[uint64][]*Template)
|
|
|
|
|
|
2021-11-22 00:19:53 +01:00
|
|
|
final := [][]*Template{}
|
|
|
|
|
|
2024-06-27 07:44:43 +00:00
|
|
|
// Split up templates that might be clusterable
|
2023-09-13 18:57:48 +02:00
|
|
|
for _, template := range list {
|
2024-04-03 17:19:06 +05:30
|
|
|
// it is not possible to cluster flow and multiprotocol due to dependent execution
|
|
|
|
|
if template.Flow != "" || template.Options.IsMultiProtocol {
|
|
|
|
|
final = append(final, []*Template{template})
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-17 18:20:05 +05:30
|
|
|
switch {
|
|
|
|
|
case len(template.RequestsDNS) == 1:
|
2024-06-27 07:44:43 +00:00
|
|
|
if template.RequestsDNS[0].IsClusterable() {
|
|
|
|
|
hash := template.RequestsDNS[0].TmplClusterKey()
|
|
|
|
|
if dns[hash] == nil {
|
|
|
|
|
dns[hash] = []*Template{}
|
|
|
|
|
}
|
|
|
|
|
dns[hash] = append(dns[hash], template)
|
|
|
|
|
} else {
|
|
|
|
|
final = append(final, []*Template{template})
|
2024-04-03 17:19:06 +05:30
|
|
|
}
|
|
|
|
|
|
2024-06-27 07:44:43 +00:00
|
|
|
case len(template.RequestsHTTP) == 1:
|
|
|
|
|
if template.RequestsHTTP[0].IsClusterable() {
|
|
|
|
|
hash := template.RequestsHTTP[0].TmplClusterKey()
|
|
|
|
|
if http[hash] == nil {
|
|
|
|
|
http[hash] = []*Template{}
|
2021-11-22 00:19:53 +01:00
|
|
|
}
|
2024-06-27 07:44:43 +00:00
|
|
|
http[hash] = append(http[hash], template)
|
|
|
|
|
} else {
|
|
|
|
|
final = append(final, []*Template{template})
|
|
|
|
|
}
|
|
|
|
|
case len(template.RequestsSSL) == 1:
|
|
|
|
|
if template.RequestsSSL[0].IsClusterable() {
|
|
|
|
|
hash := template.RequestsSSL[0].TmplClusterKey()
|
|
|
|
|
if ssl[hash] == nil {
|
|
|
|
|
ssl[hash] = []*Template{}
|
2021-11-22 00:19:53 +01:00
|
|
|
}
|
2024-06-27 07:44:43 +00:00
|
|
|
ssl[hash] = append(ssl[hash], template)
|
|
|
|
|
} else {
|
|
|
|
|
final = append(final, []*Template{template})
|
2021-11-22 00:19:53 +01:00
|
|
|
}
|
2024-06-27 07:44:43 +00:00
|
|
|
default:
|
2023-09-13 18:57:48 +02:00
|
|
|
final = append(final, []*Template{template})
|
2021-11-22 00:19:53 +01:00
|
|
|
}
|
|
|
|
|
}
|
2024-06-27 07:44:43 +00:00
|
|
|
|
|
|
|
|
// add all clusterd templates
|
|
|
|
|
for _, templates := range http {
|
|
|
|
|
final = append(final, templates)
|
|
|
|
|
}
|
|
|
|
|
for _, templates := range dns {
|
|
|
|
|
final = append(final, templates)
|
|
|
|
|
}
|
|
|
|
|
for _, templates := range ssl {
|
|
|
|
|
final = append(final, templates)
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-22 00:19:53 +01:00
|
|
|
return final
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-07 16:41:55 +02:00
|
|
|
// ClusterID transforms clusterization into a mathematical hash repeatable across executions with the same templates
|
2021-11-29 14:38:45 +01:00
|
|
|
func ClusterID(templates []*Template) string {
|
|
|
|
|
allIDS := make([]string, len(templates))
|
|
|
|
|
for tplIndex, tpl := range templates {
|
|
|
|
|
allIDS[tplIndex] = tpl.ID
|
|
|
|
|
}
|
|
|
|
|
sort.Strings(allIDS)
|
|
|
|
|
ids := strings.Join(allIDS, ",")
|
|
|
|
|
return cryptoutil.SHA256Sum(ids)
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-09 14:47:26 -05:00
|
|
|
func ClusterTemplates(templatesList []*Template, options *protocols.ExecutorOptions) ([]*Template, int) {
|
2022-12-11 18:06:21 +05:30
|
|
|
if options.Options.OfflineHTTP || options.Options.DisableClustering {
|
2021-11-22 00:19:53 +01:00
|
|
|
return templatesList, 0
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-13 18:57:48 +02:00
|
|
|
var clusterCount int
|
2021-11-22 00:19:53 +01:00
|
|
|
|
|
|
|
|
finalTemplatesList := make([]*Template, 0, len(templatesList))
|
2023-09-13 18:57:48 +02:00
|
|
|
clusters := Cluster(templatesList)
|
2021-11-22 00:19:53 +01:00
|
|
|
for _, cluster := range clusters {
|
|
|
|
|
if len(cluster) > 1 {
|
|
|
|
|
executerOpts := options
|
2021-11-29 14:38:45 +01:00
|
|
|
clusterID := fmt.Sprintf("cluster-%s", ClusterID(cluster))
|
2021-11-22 00:19:53 +01:00
|
|
|
|
2023-01-17 13:01:20 +05:30
|
|
|
for _, req := range cluster[0].RequestsDNS {
|
|
|
|
|
req.Options().TemplateID = clusterID
|
|
|
|
|
}
|
2022-03-11 22:08:30 +05:30
|
|
|
for _, req := range cluster[0].RequestsHTTP {
|
|
|
|
|
req.Options().TemplateID = clusterID
|
|
|
|
|
}
|
2023-01-17 18:20:05 +05:30
|
|
|
for _, req := range cluster[0].RequestsSSL {
|
|
|
|
|
req.Options().TemplateID = clusterID
|
|
|
|
|
}
|
2022-03-11 22:08:30 +05:30
|
|
|
executerOpts.TemplateID = clusterID
|
2021-11-22 00:19:53 +01:00
|
|
|
finalTemplatesList = append(finalTemplatesList, &Template{
|
|
|
|
|
ID: clusterID,
|
2023-01-17 13:01:20 +05:30
|
|
|
RequestsDNS: cluster[0].RequestsDNS,
|
2021-11-22 00:19:53 +01:00
|
|
|
RequestsHTTP: cluster[0].RequestsHTTP,
|
2023-01-17 18:20:05 +05:30
|
|
|
RequestsSSL: cluster[0].RequestsSSL,
|
2025-07-09 14:47:26 -05:00
|
|
|
Executer: NewClusterExecuter(cluster, executerOpts),
|
2023-01-17 13:01:20 +05:30
|
|
|
TotalRequests: len(cluster[0].RequestsHTTP) + len(cluster[0].RequestsDNS),
|
2021-11-22 00:19:53 +01:00
|
|
|
})
|
|
|
|
|
clusterCount += len(cluster)
|
|
|
|
|
} else {
|
|
|
|
|
finalTemplatesList = append(finalTemplatesList, cluster...)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return finalTemplatesList, clusterCount
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 23:09:27 +05:30
|
|
|
// ClusterExecuter executes a group of requests for a protocol for a clustered
|
2021-01-13 12:18:56 +05:30
|
|
|
// request. It is different from normal executers since the original
|
|
|
|
|
// operators are all combined and post processed after making the request.
|
2022-06-24 23:09:27 +05:30
|
|
|
type ClusterExecuter struct {
|
2023-01-17 13:01:20 +05:30
|
|
|
requests protocols.Request
|
|
|
|
|
operators []*clusteredOperator
|
|
|
|
|
templateType types.ProtocolType
|
2023-05-31 16:58:10 -04:00
|
|
|
options *protocols.ExecutorOptions
|
2021-01-13 12:18:56 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type clusteredOperator struct {
|
|
|
|
|
templateID string
|
2021-06-05 18:01:08 +05:30
|
|
|
templatePath string
|
2021-07-12 17:20:01 +03:00
|
|
|
templateInfo model.Info
|
2021-01-13 12:18:56 +05:30
|
|
|
operator *operators.Operators
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 23:09:27 +05:30
|
|
|
var _ protocols.Executer = &ClusterExecuter{}
|
2021-01-13 12:18:56 +05:30
|
|
|
|
2022-06-24 23:09:27 +05:30
|
|
|
// NewClusterExecuter creates a new request executer for list of requests
|
2023-05-31 16:58:10 -04:00
|
|
|
func NewClusterExecuter(requests []*Template, options *protocols.ExecutorOptions) *ClusterExecuter {
|
2023-01-17 13:01:20 +05:30
|
|
|
executer := &ClusterExecuter{options: options}
|
|
|
|
|
if len(requests[0].RequestsDNS) == 1 {
|
|
|
|
|
executer.templateType = types.DNSProtocol
|
|
|
|
|
executer.requests = requests[0].RequestsDNS[0]
|
|
|
|
|
} else if len(requests[0].RequestsHTTP) == 1 {
|
|
|
|
|
executer.templateType = types.HTTPProtocol
|
|
|
|
|
executer.requests = requests[0].RequestsHTTP[0]
|
2023-01-17 18:20:05 +05:30
|
|
|
} else if len(requests[0].RequestsSSL) == 1 {
|
|
|
|
|
executer.templateType = types.SSLProtocol
|
|
|
|
|
executer.requests = requests[0].RequestsSSL[0]
|
2023-01-17 13:01:20 +05:30
|
|
|
}
|
|
|
|
|
appendOperator := func(req *Template, operator *operators.Operators) {
|
|
|
|
|
operator.TemplateID = req.ID
|
|
|
|
|
operator.ExcludeMatchers = options.ExcludeMatchers
|
|
|
|
|
|
|
|
|
|
executer.operators = append(executer.operators, &clusteredOperator{
|
|
|
|
|
operator: operator,
|
|
|
|
|
templateID: req.ID,
|
|
|
|
|
templateInfo: req.Info,
|
|
|
|
|
templatePath: req.Path,
|
|
|
|
|
})
|
2021-01-13 12:18:56 +05:30
|
|
|
}
|
|
|
|
|
for _, req := range requests {
|
2025-07-01 00:40:44 +07:00
|
|
|
switch executer.templateType {
|
|
|
|
|
case types.DNSProtocol:
|
2023-01-17 13:01:20 +05:30
|
|
|
if req.RequestsDNS[0].CompiledOperators != nil {
|
|
|
|
|
appendOperator(req, req.RequestsDNS[0].CompiledOperators)
|
|
|
|
|
}
|
2025-07-01 00:40:44 +07:00
|
|
|
case types.HTTPProtocol:
|
2023-01-17 13:01:20 +05:30
|
|
|
if req.RequestsHTTP[0].CompiledOperators != nil {
|
|
|
|
|
appendOperator(req, req.RequestsHTTP[0].CompiledOperators)
|
|
|
|
|
}
|
2025-07-01 00:40:44 +07:00
|
|
|
case types.SSLProtocol:
|
2023-01-17 18:20:05 +05:30
|
|
|
if req.RequestsSSL[0].CompiledOperators != nil {
|
|
|
|
|
appendOperator(req, req.RequestsSSL[0].CompiledOperators)
|
|
|
|
|
}
|
2022-06-16 14:41:05 +05:30
|
|
|
}
|
2021-01-13 12:18:56 +05:30
|
|
|
}
|
|
|
|
|
return executer
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compile compiles the execution generators preparing any requests possible.
|
2022-06-24 23:09:27 +05:30
|
|
|
func (e *ClusterExecuter) Compile() error {
|
2021-01-13 12:18:56 +05:30
|
|
|
return e.requests.Compile(e.options)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Requests returns the total number of requests the rule will perform
|
2022-06-24 23:09:27 +05:30
|
|
|
func (e *ClusterExecuter) Requests() int {
|
2021-01-13 12:18:56 +05:30
|
|
|
var count int
|
|
|
|
|
count += e.requests.Requests()
|
|
|
|
|
return count
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Execute executes the protocol group and returns true or false if results were found.
|
2023-11-27 19:54:45 +01:00
|
|
|
func (e *ClusterExecuter) Execute(ctx *scan.ScanContext) (bool, error) {
|
2021-01-13 12:18:56 +05:30
|
|
|
var results bool
|
|
|
|
|
|
2023-11-27 19:54:45 +01:00
|
|
|
inputItem := ctx.Input.Clone()
|
|
|
|
|
if e.options.InputHelper != nil && ctx.Input.MetaInput.Input != "" {
|
|
|
|
|
if inputItem.MetaInput.Input = e.options.InputHelper.Transform(ctx.Input.MetaInput.Input, e.templateType); ctx.Input.MetaInput.Input == "" {
|
2022-12-11 18:06:21 +05:30
|
|
|
return false, nil
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-06-03 10:26:41 +05:30
|
|
|
previous := make(map[string]interface{})
|
2021-01-13 12:18:56 +05:30
|
|
|
dynamicValues := make(map[string]interface{})
|
2022-12-11 18:06:21 +05:30
|
|
|
err := e.requests.ExecuteWithResults(inputItem, dynamicValues, previous, func(event *output.InternalWrappedEvent) {
|
2024-03-17 16:25:26 +05:30
|
|
|
if event == nil {
|
|
|
|
|
// unlikely but just in case
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if event.InternalEvent == nil {
|
|
|
|
|
event.InternalEvent = make(map[string]interface{})
|
|
|
|
|
}
|
2021-01-13 12:18:56 +05:30
|
|
|
for _, operator := range e.operators {
|
2024-09-24 18:43:35 +05:30
|
|
|
clonedEvent := event.CloneShallow()
|
|
|
|
|
|
|
|
|
|
result, matched := operator.operator.Execute(clonedEvent.InternalEvent, e.requests.Match, e.requests.Extract, e.options.Options.Debug || e.options.Options.DebugResponse)
|
|
|
|
|
clonedEvent.InternalEvent["template-id"] = operator.templateID
|
|
|
|
|
clonedEvent.InternalEvent["template-path"] = operator.templatePath
|
|
|
|
|
clonedEvent.InternalEvent["template-info"] = operator.templateInfo
|
2021-11-22 17:53:25 +05:30
|
|
|
|
2023-09-12 02:48:39 +05:30
|
|
|
if result == nil && !matched && e.options.Options.MatcherStatus {
|
2024-09-24 18:43:35 +05:30
|
|
|
if err := e.options.Output.WriteFailure(clonedEvent); err != nil {
|
2021-11-22 17:53:25 +05:30
|
|
|
gologger.Warning().Msgf("Could not write failure event to output: %s\n", err)
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
2021-01-13 12:18:56 +05:30
|
|
|
if matched && result != nil {
|
2024-09-24 18:43:35 +05:30
|
|
|
clonedEvent.OperatorsResult = result
|
|
|
|
|
clonedEvent.Results = e.requests.MakeResultEvent(clonedEvent)
|
2021-01-13 12:18:56 +05:30
|
|
|
results = true
|
2021-11-22 17:53:25 +05:30
|
|
|
|
2024-09-24 18:43:35 +05:30
|
|
|
_ = writer.WriteResult(clonedEvent, e.options.Output, e.options.Progress, e.options.IssuesClient)
|
2021-01-13 12:18:56 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-01-31 17:16:57 +07:00
|
|
|
if e.options.HostErrorsCache != nil {
|
|
|
|
|
e.options.HostErrorsCache.MarkFailedOrRemove(e.options.ProtocolType.String(), ctx.Input, err)
|
2021-08-17 16:05:29 +05:30
|
|
|
}
|
2021-02-01 16:21:49 +05:30
|
|
|
return results, err
|
2021-01-13 12:18:56 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
|
2023-11-27 19:54:45 +01:00
|
|
|
func (e *ClusterExecuter) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error) {
|
2024-04-25 15:37:56 +05:30
|
|
|
scanCtx := scan.NewScanContext(ctx.Context(), ctx.Input)
|
2021-01-13 12:18:56 +05:30
|
|
|
dynamicValues := make(map[string]interface{})
|
2022-12-11 18:06:21 +05:30
|
|
|
|
2023-11-27 19:54:45 +01:00
|
|
|
inputItem := ctx.Input.Clone()
|
|
|
|
|
if e.options.InputHelper != nil && ctx.Input.MetaInput.Input != "" {
|
|
|
|
|
if inputItem.MetaInput.Input = e.options.InputHelper.Transform(ctx.Input.MetaInput.Input, e.templateType); ctx.Input.MetaInput.Input == "" {
|
|
|
|
|
return nil, nil
|
2022-12-11 18:06:21 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
err := e.requests.ExecuteWithResults(inputItem, dynamicValues, nil, func(event *output.InternalWrappedEvent) {
|
2021-01-13 12:18:56 +05:30
|
|
|
for _, operator := range e.operators {
|
2024-09-24 18:43:35 +05:30
|
|
|
clonedEvent := event.CloneShallow()
|
|
|
|
|
|
|
|
|
|
result, matched := operator.operator.Execute(clonedEvent.InternalEvent, e.requests.Match, e.requests.Extract, e.options.Options.Debug || e.options.Options.DebugResponse)
|
2021-01-13 12:18:56 +05:30
|
|
|
if matched && result != nil {
|
2024-09-24 18:43:35 +05:30
|
|
|
clonedEvent.OperatorsResult = result
|
|
|
|
|
clonedEvent.InternalEvent["template-id"] = operator.templateID
|
|
|
|
|
clonedEvent.InternalEvent["template-path"] = operator.templatePath
|
|
|
|
|
clonedEvent.InternalEvent["template-info"] = operator.templateInfo
|
|
|
|
|
clonedEvent.Results = e.requests.MakeResultEvent(clonedEvent)
|
|
|
|
|
scanCtx.LogEvent(clonedEvent)
|
2021-01-13 12:18:56 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
2023-11-27 19:54:45 +01:00
|
|
|
if err != nil {
|
|
|
|
|
ctx.LogError(err)
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-31 17:16:57 +07:00
|
|
|
if e.options.HostErrorsCache != nil {
|
|
|
|
|
e.options.HostErrorsCache.MarkFailedOrRemove(e.options.ProtocolType.String(), ctx.Input, err)
|
2021-08-17 16:05:29 +05:30
|
|
|
}
|
2023-11-27 19:54:45 +01:00
|
|
|
return scanCtx.GenerateResult(), err
|
2021-01-13 12:18:56 +05:30
|
|
|
}
|