mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-19 23:55:25 +00:00
181 lines
5.3 KiB
Go
181 lines
5.3 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/projectdiscovery/gologger"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
|
|
"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/contextargs"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/offlinehttp"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/scan"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
|
|
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
|
)
|
|
|
|
type PassiveNucleiExecutor struct {
|
|
defaultTemplates []*templates.Template
|
|
pathBasedTemplates map[string][]*templates.Template
|
|
}
|
|
|
|
func NewPassiveNucleiExecutor(templatesDir string) (*PassiveNucleiExecutor, error) {
|
|
catalog := disk.NewCatalog(templatesDir)
|
|
|
|
opts := types.DefaultOptions()
|
|
opts.ExcludeTags = []string{"cve", "lfi", "xss", "rce", "ssrf", "cmdi", "oast", "ssti", "sqli"}
|
|
opts.ExcludedTemplates = []string{
|
|
"http/vulnerabilities/",
|
|
"http/cves",
|
|
"http/cnvd",
|
|
"http/fuzzing",
|
|
}
|
|
opts.ExcludeIds = []string{
|
|
"tech-detect", "fingerprinthub-web-fingerprints", "credentials-disclosure",
|
|
"aws-detect",
|
|
}
|
|
opts.Protocols = templateTypes.ProtocolTypes{templateTypes.HTTPProtocol}
|
|
|
|
executorOpts := protocols.ExecutorOptions{
|
|
Options: opts,
|
|
Catalog: catalog,
|
|
Parser: templates.NewParser(),
|
|
}
|
|
if err := protocolinit.Init(opts); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
store, err := loader.New(loader.NewConfig(opts, catalog, executorOpts))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
store.Load()
|
|
|
|
loadedTemplates := store.Templates()
|
|
offlinehttp.RawInputMode = true
|
|
|
|
var defaultTemplates []*templates.Template
|
|
pathBasedTemplates := make(map[string][]*templates.Template)
|
|
|
|
for _, tpl := range loadedTemplates {
|
|
if len(tpl.RequestsWithHTTP) != 1 {
|
|
continue
|
|
}
|
|
if tpl.SelfContained || tpl.Flow != "" {
|
|
continue
|
|
}
|
|
for _, item := range tpl.RequestsWithHTTP {
|
|
if len(item.Path) == 0 {
|
|
continue
|
|
}
|
|
if err := compileOfflineHTTPRequest(tpl, &executorOpts); err != nil {
|
|
slog.Warn("Error compiling template", slog.String("id", tpl.ID), slog.String("error", err.Error()))
|
|
continue
|
|
}
|
|
for _, path := range item.Path {
|
|
// Default templates are not path based
|
|
if path == "{{BaseURL}}" || path == "{{BaseURL}}/" {
|
|
defaultTemplates = append(defaultTemplates, tpl)
|
|
continue
|
|
}
|
|
pathCleaned := strings.TrimPrefix(path, "{{BaseURL}}")
|
|
pathBasedTemplates[pathCleaned] = append(pathBasedTemplates[pathCleaned], tpl)
|
|
}
|
|
}
|
|
}
|
|
return &PassiveNucleiExecutor{
|
|
defaultTemplates: defaultTemplates,
|
|
pathBasedTemplates: pathBasedTemplates,
|
|
}, nil
|
|
}
|
|
|
|
func (e *PassiveNucleiExecutor) Execute(ctx context.Context, input, URL string) ([]*output.ResultEvent, error) {
|
|
parsedURL, err := url.Parse(URL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var allResults []*output.ResultEvent
|
|
for _, tpl := range e.defaultTemplates {
|
|
results, err := executeTemplate(ctx, tpl, input, parsedURL)
|
|
if err != nil {
|
|
gologger.Warning().Msgf("Error executing template %s: %s\n", tpl.ID, err)
|
|
return nil, err
|
|
}
|
|
allResults = append(allResults, results...)
|
|
}
|
|
|
|
templates, ok := e.pathBasedTemplates[parsedURL.Path]
|
|
if !ok || len(templates) == 0 {
|
|
return allResults, nil
|
|
}
|
|
|
|
for _, tpl := range templates {
|
|
results, err := executeTemplate(ctx, tpl, input, parsedURL)
|
|
if err != nil {
|
|
gologger.Warning().Msgf("Error executing template %s: %s\n", tpl.ID, err)
|
|
continue
|
|
}
|
|
allResults = append(allResults, results...)
|
|
}
|
|
return allResults, nil
|
|
}
|
|
|
|
func executeTemplate(ctx context.Context, tpl *templates.Template, input string, parsedURL *url.URL) ([]*output.ResultEvent, error) {
|
|
results, err := tpl.Executer.ExecuteWithResults(scan.NewScanContext(ctx, contextargs.NewWithInput(ctx, input)))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
port := parsedURL.Port()
|
|
if port == "" {
|
|
if parsedURL.Scheme == "https" {
|
|
port = "443"
|
|
} else {
|
|
port = "80"
|
|
}
|
|
}
|
|
urlString := parsedURL.String()
|
|
for _, result := range results {
|
|
result.Info = tpl.Info
|
|
result.TemplateID = tpl.ID
|
|
result.TemplatePath = tpl.Path
|
|
result.URL = urlString
|
|
result.Host = parsedURL.Host
|
|
result.Path = parsedURL.Path
|
|
result.Scheme = parsedURL.Scheme
|
|
result.Path = parsedURL.Path
|
|
result.Port = port
|
|
result.Matched = urlString
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
// compileOfflineHTTPRequest iterates all requests if offline http mode is
|
|
// specified and collects all matchers for all the base request templates
|
|
// (those with URL {{BaseURL}} and it's slash variation.)
|
|
func compileOfflineHTTPRequest(template *templates.Template, options *protocols.ExecutorOptions) error {
|
|
operatorsList := []*operators.Operators{}
|
|
|
|
for _, req := range template.RequestsHTTP {
|
|
operatorsList = append(operatorsList, &req.Operators)
|
|
}
|
|
if len(operatorsList) > 0 {
|
|
options.Operators = operatorsList
|
|
var err error
|
|
|
|
request := &offlinehttp.Request{}
|
|
_ = request.Compile(options)
|
|
template.Executer, err = tmplexec.NewTemplateExecuter([]protocols.Request{request}, options)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|