nuclei/internal/server/nuclei_sdk.go
HD Moore f26996cb89
Remove singletons from Nuclei engine (continuation of #6210) (#6296)
* introducing execution id

* wip

* .

* adding separate execution context id

* lint

* vet

* fixing pg dialers

* test ignore

* fixing loader FD limit

* test

* fd fix

* wip: remove CloseProcesses() from dev merge

* wip: fix merge issue

* protocolstate: stop memguarding on last dialer delete

* avoid data race in dialers.RawHTTPClient

* use shared logger and avoid race conditions

* use shared logger and avoid race conditions

* go mod

* patch executionId into compiled template cache

* clean up comment in Parse

* go mod update

* bump echarts

* address merge issues

* fix use of gologger

* switch cmd/nuclei to options.Logger

* address merge issues with go.mod

* go vet: address copy of lock with new Copy function

* fixing tests

* disable speed control

* fix nil ExecuterOptions

* removing deprecated code

* fixing result print

* default logger

* cli default logger

* filter warning from results

* fix performance test

* hardcoding path

* disable upload

* refactor(runner): uses `Warning` instead of `Print` for `pdcpUploadErrMsg`

Signed-off-by: Dwi Siswanto <git@dw1.io>

* Revert "disable upload"

This reverts commit 114fbe6663361bf41cf8b2645fd2d57083d53682.

* Revert "hardcoding path"

This reverts commit cf12ca800e0a0e974bd9fd4826a24e51547f7c00.

---------

Signed-off-by: Dwi Siswanto <git@dw1.io>
Co-authored-by: Mzack9999 <mzack9999@protonmail.com>
Co-authored-by: Dwi Siswanto <git@dw1.io>
Co-authored-by: Dwi Siswanto <25837540+dwisiswant0@users.noreply.github.com>
2025-07-10 01:17:26 +05:30

202 lines
6.6 KiB
Go

package server
import (
"context"
"fmt"
_ "net/http/pprof"
"strings"
"github.com/logrusorgru/aurora"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/frequency"
"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/stats"
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats"
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider/http"
"github.com/projectdiscovery/nuclei/v3/pkg/projectfile"
"gopkg.in/yaml.v3"
"github.com/pkg/errors"
"github.com/projectdiscovery/ratelimit"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
"github.com/projectdiscovery/nuclei/v3/pkg/core"
"github.com/projectdiscovery/nuclei/v3/pkg/input"
"github.com/projectdiscovery/nuclei/v3/pkg/loader/parser"
parsers "github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/progress"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/globalmatchers"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/excludematchers"
browserEngine "github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting"
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
)
type nucleiExecutor struct {
engine *core.Engine
store *loader.Store
options *NucleiExecutorOptions
executorOpts *protocols.ExecutorOptions
}
type NucleiExecutorOptions struct {
Options *types.Options
Output output.Writer
Progress progress.Progress
Catalog catalog.Catalog
IssuesClient reporting.Client
RateLimiter *ratelimit.Limiter
Interactsh *interactsh.Client
ProjectFile *projectfile.ProjectFile
Browser *browserEngine.Browser
FuzzStatsDB *stats.Tracker
Colorizer aurora.Aurora
Parser parser.Parser
TemporaryDirectory string
Logger *gologger.Logger
}
func newNucleiExecutor(opts *NucleiExecutorOptions) (*nucleiExecutor, error) {
fuzzFreqCache := frequency.New(frequency.DefaultMaxTrackCount, opts.Options.FuzzParamFrequency)
resumeCfg := types.NewResumeCfg()
// Create the executor options which will be used throughout the execution
// stage by the nuclei engine modules.
executorOpts := &protocols.ExecutorOptions{
Output: opts.Output,
Options: opts.Options,
Progress: opts.Progress,
Catalog: opts.Catalog,
IssuesClient: opts.IssuesClient,
RateLimiter: opts.RateLimiter,
Interactsh: opts.Interactsh,
ProjectFile: opts.ProjectFile,
Browser: opts.Browser,
Colorizer: opts.Colorizer,
ResumeCfg: resumeCfg,
ExcludeMatchers: excludematchers.New(opts.Options.ExcludeMatchers),
InputHelper: input.NewHelper(),
TemporaryDirectory: opts.TemporaryDirectory,
Parser: opts.Parser,
FuzzParamsFrequency: fuzzFreqCache,
GlobalMatchers: globalmatchers.New(),
FuzzStatsDB: opts.FuzzStatsDB,
Logger: opts.Logger,
}
if opts.Options.ShouldUseHostError() {
maxHostError := opts.Options.MaxHostError
if maxHostError == 30 {
maxHostError = 100 // auto adjust for fuzzings
}
if opts.Options.TemplateThreads > maxHostError {
opts.Logger.Info().Msgf("Adjusting max-host-error to the concurrency value: %d", opts.Options.TemplateThreads)
maxHostError = opts.Options.TemplateThreads
}
cache := hosterrorscache.New(maxHostError, hosterrorscache.DefaultMaxHostsCount, opts.Options.TrackError)
cache.SetVerbose(opts.Options.Verbose)
executorOpts.HostErrorsCache = cache
}
executorEngine := core.New(opts.Options)
executorEngine.SetExecuterOptions(executorOpts)
workflowLoader, err := parsers.NewLoader(executorOpts)
if err != nil {
return nil, errors.Wrap(err, "Could not create loader options.")
}
executorOpts.WorkflowLoader = workflowLoader
// If using input-file flags, only load http fuzzing based templates.
loaderConfig := loader.NewConfig(opts.Options, opts.Catalog, executorOpts)
if !strings.EqualFold(opts.Options.InputFileMode, "list") || opts.Options.DAST || opts.Options.DASTServer {
// if input type is not list (implicitly enable fuzzing)
opts.Options.DAST = true
}
store, err := loader.New(loaderConfig)
if err != nil {
return nil, errors.Wrap(err, "Could not create loader options.")
}
store.Load()
return &nucleiExecutor{
engine: executorEngine,
store: store,
options: opts,
executorOpts: executorOpts,
}, nil
}
// proxifyRequest is a request for proxify
type proxifyRequest struct {
URL string `json:"url"`
Request struct {
Header map[string]string `json:"header"`
Body string `json:"body"`
Raw string `json:"raw"`
} `json:"request"`
}
func (n *nucleiExecutor) ExecuteScan(target PostRequestsHandlerRequest) error {
finalTemplates := []*templates.Template{}
finalTemplates = append(finalTemplates, n.store.Templates()...)
finalTemplates = append(finalTemplates, n.store.Workflows()...)
if len(finalTemplates) == 0 {
return errors.New("no templates provided for scan")
}
payload := proxifyRequest{
URL: target.URL,
Request: struct {
Header map[string]string `json:"header"`
Body string `json:"body"`
Raw string `json:"raw"`
}{
Raw: target.RawHTTP,
},
}
marshalledYaml, err := yaml.Marshal(payload)
if err != nil {
return fmt.Errorf("error marshalling yaml: %s", err)
}
inputProvider, err := http.NewHttpInputProvider(&http.HttpMultiFormatOptions{
InputContents: string(marshalledYaml),
InputMode: "yaml",
Options: formats.InputFormatOptions{
Variables: make(map[string]interface{}),
},
})
if err != nil {
return errors.Wrap(err, "could not create input provider")
}
// We don't care about the result as its a boolean
// stating whether we got matches or not
_ = n.engine.ExecuteScanWithOpts(context.Background(), finalTemplates, inputProvider, true)
return nil
}
func (n *nucleiExecutor) Close() {
if n.executorOpts.FuzzStatsDB != nil {
n.executorOpts.FuzzStatsDB.Close()
}
if n.options.Interactsh != nil {
_ = n.options.Interactsh.Close()
}
if n.executorOpts.InputHelper != nil {
_ = n.executorOpts.InputHelper.Close()
}
}