2023-09-02 09:04:05 +00:00
|
|
|
package nuclei
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
2023-10-30 13:32:06 +00:00
|
|
|
"bytes"
|
2023-09-02 09:04:05 +00:00
|
|
|
"io"
|
|
|
|
|
2024-03-13 21:38:53 +00:00
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
|
2023-10-17 12:14:13 +00:00
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk"
|
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
|
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/core"
|
2024-03-13 21:38:53 +00:00
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
|
|
|
|
providerTypes "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
|
2024-03-13 20:46:30 +00:00
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow"
|
2023-10-17 12:14:13 +00:00
|
|
|
"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/hosterrorscache"
|
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh"
|
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine"
|
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/reporting"
|
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
|
2023-10-30 13:32:06 +00:00
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/templates/signer"
|
2023-10-17 12:14:13 +00:00
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
2023-09-02 09:04:05 +00:00
|
|
|
"github.com/projectdiscovery/ratelimit"
|
|
|
|
"github.com/projectdiscovery/retryablehttp-go"
|
|
|
|
errorutil "github.com/projectdiscovery/utils/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
// NucleiSDKOptions contains options for nuclei SDK
|
|
|
|
type NucleiSDKOptions func(e *NucleiEngine) error
|
|
|
|
|
|
|
|
var (
|
|
|
|
// ErrNotImplemented is returned when a feature is not implemented
|
|
|
|
ErrNotImplemented = errorutil.New("Not implemented")
|
|
|
|
// ErrNoTemplatesAvailable is returned when no templates are available to execute
|
|
|
|
ErrNoTemplatesAvailable = errorutil.New("No templates available")
|
|
|
|
// ErrNoTargetsAvailable is returned when no targets are available to scan
|
|
|
|
ErrNoTargetsAvailable = errorutil.New("No targets available")
|
|
|
|
// ErrOptionsNotSupported is returned when an option is not supported in thread safe mode
|
|
|
|
ErrOptionsNotSupported = errorutil.NewWithFmt("Option %v not supported in thread safe mode")
|
|
|
|
)
|
|
|
|
|
|
|
|
type engineMode uint
|
|
|
|
|
|
|
|
const (
|
|
|
|
singleInstance engineMode = iota
|
|
|
|
threadSafe
|
|
|
|
)
|
|
|
|
|
|
|
|
// NucleiEngine is the Engine/Client for nuclei which
|
|
|
|
// runs scans using templates and returns results
|
|
|
|
type NucleiEngine struct {
|
|
|
|
// user options
|
|
|
|
resultCallbacks []func(event *output.ResultEvent)
|
|
|
|
onFailureCallback func(event *output.InternalEvent)
|
|
|
|
disableTemplatesAutoUpgrade bool
|
|
|
|
enableStats bool
|
|
|
|
onUpdateAvailableCallback func(newVersion string)
|
|
|
|
|
|
|
|
// ready-status fields
|
|
|
|
templatesLoaded bool
|
|
|
|
|
|
|
|
// unexported core fields
|
|
|
|
interactshClient *interactsh.Client
|
|
|
|
catalog *disk.DiskCatalog
|
|
|
|
rateLimiter *ratelimit.Limiter
|
|
|
|
store *loader.Store
|
2024-03-13 21:38:53 +00:00
|
|
|
httpxClient providerTypes.InputLivenessProbe
|
|
|
|
inputProvider provider.InputProvider
|
2023-09-02 09:04:05 +00:00
|
|
|
engine *core.Engine
|
|
|
|
mode engineMode
|
|
|
|
browserInstance *engine.Browser
|
|
|
|
httpClient *retryablehttp.Client
|
2024-03-13 01:27:15 +00:00
|
|
|
parser *templates.Parser
|
2024-03-13 21:38:53 +00:00
|
|
|
authprovider authprovider.AuthProvider
|
2023-09-02 09:04:05 +00:00
|
|
|
|
|
|
|
// unexported meta options
|
|
|
|
opts *types.Options
|
|
|
|
interactshOpts *interactsh.Options
|
|
|
|
hostErrCache *hosterrorscache.Cache
|
|
|
|
customWriter output.Writer
|
|
|
|
customProgress progress.Progress
|
|
|
|
rc reporting.Client
|
|
|
|
executerOpts protocols.ExecutorOptions
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoadAllTemplates loads all nuclei template based on given options
|
|
|
|
func (e *NucleiEngine) LoadAllTemplates() error {
|
2024-03-13 20:46:30 +00:00
|
|
|
workflowLoader, err := workflow.NewLoader(&e.executerOpts)
|
2023-09-02 09:04:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return errorutil.New("Could not create workflow loader: %s\n", err)
|
|
|
|
}
|
|
|
|
e.executerOpts.WorkflowLoader = workflowLoader
|
|
|
|
|
|
|
|
e.store, err = loader.New(loader.NewConfig(e.opts, e.catalog, e.executerOpts))
|
|
|
|
if err != nil {
|
|
|
|
return errorutil.New("Could not create loader client: %s\n", err)
|
|
|
|
}
|
|
|
|
e.store.Load()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetTemplates returns all nuclei templates that are loaded
|
|
|
|
func (e *NucleiEngine) GetTemplates() []*templates.Template {
|
|
|
|
if !e.templatesLoaded {
|
|
|
|
_ = e.LoadAllTemplates()
|
|
|
|
}
|
|
|
|
return e.store.Templates()
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoadTargets(urls/domains/ips only) adds targets to the nuclei engine
|
|
|
|
func (e *NucleiEngine) LoadTargets(targets []string, probeNonHttp bool) {
|
|
|
|
for _, target := range targets {
|
|
|
|
if probeNonHttp {
|
2024-03-13 21:38:53 +00:00
|
|
|
_ = e.inputProvider.SetWithProbe(target, e.httpxClient)
|
2023-09-02 09:04:05 +00:00
|
|
|
} else {
|
|
|
|
e.inputProvider.Set(target)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoadTargetsFromReader adds targets(urls/domains/ips only) from reader to the nuclei engine
|
|
|
|
func (e *NucleiEngine) LoadTargetsFromReader(reader io.Reader, probeNonHttp bool) {
|
|
|
|
buff := bufio.NewScanner(reader)
|
|
|
|
for buff.Scan() {
|
|
|
|
if probeNonHttp {
|
2024-03-13 21:38:53 +00:00
|
|
|
_ = e.inputProvider.SetWithProbe(buff.Text(), e.httpxClient)
|
2023-09-02 09:04:05 +00:00
|
|
|
} else {
|
|
|
|
e.inputProvider.Set(buff.Text())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-13 21:38:53 +00:00
|
|
|
// LoadTargetsWithHttpData loads targets that contain http data from file it currently supports
|
|
|
|
// multiple formats like burp xml,openapi,swagger,proxify json
|
|
|
|
// Note: this is mutually exclusive with LoadTargets and LoadTargetsFromReader
|
|
|
|
func (e *NucleiEngine) LoadTargetsWithHttpData(filePath string, filemode string) error {
|
|
|
|
e.opts.TargetsFilePath = filePath
|
|
|
|
e.opts.InputFileMode = filemode
|
|
|
|
httpProvider, err := provider.NewInputProvider(provider.InputOptions{Options: e.opts})
|
|
|
|
if err != nil {
|
|
|
|
e.opts.TargetsFilePath = ""
|
|
|
|
e.opts.InputFileMode = ""
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
e.inputProvider = httpProvider
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-10-30 13:32:06 +00:00
|
|
|
// GetExecuterOptions returns the nuclei executor options
|
|
|
|
func (e *NucleiEngine) GetExecuterOptions() *protocols.ExecutorOptions {
|
|
|
|
return &e.executerOpts
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParseTemplate parses a template from given data
|
|
|
|
// template verification status can be accessed from template.Verified
|
|
|
|
func (e *NucleiEngine) ParseTemplate(data []byte) (*templates.Template, error) {
|
|
|
|
return templates.ParseTemplateFromReader(bytes.NewReader(data), nil, e.executerOpts)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SignTemplate signs the tempalate using given signer
|
|
|
|
func (e *NucleiEngine) SignTemplate(tmplSigner *signer.TemplateSigner, data []byte) ([]byte, error) {
|
|
|
|
tmpl, err := e.ParseTemplate(data)
|
|
|
|
if err != nil {
|
|
|
|
return data, err
|
|
|
|
}
|
|
|
|
if tmpl.Verified {
|
|
|
|
// already signed
|
|
|
|
return data, nil
|
|
|
|
}
|
|
|
|
if len(tmpl.Workflows) > 0 {
|
|
|
|
return data, templates.ErrNotATemplate
|
|
|
|
}
|
|
|
|
signatureData, err := tmplSigner.Sign(data, tmpl)
|
|
|
|
if err != nil {
|
|
|
|
return data, err
|
|
|
|
}
|
|
|
|
buff := bytes.NewBuffer(signer.RemoveSignatureFromData(data))
|
|
|
|
buff.WriteString("\n" + signatureData)
|
|
|
|
return buff.Bytes(), err
|
|
|
|
}
|
|
|
|
|
2023-09-02 09:04:05 +00:00
|
|
|
// Close all resources used by nuclei engine
|
|
|
|
func (e *NucleiEngine) Close() {
|
|
|
|
e.interactshClient.Close()
|
|
|
|
e.rc.Close()
|
|
|
|
e.customWriter.Close()
|
|
|
|
e.hostErrCache.Close()
|
|
|
|
e.executerOpts.RateLimiter.Stop()
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExecuteWithCallback executes templates on targets and calls callback on each result(only if results are found)
|
|
|
|
func (e *NucleiEngine) ExecuteWithCallback(callback ...func(event *output.ResultEvent)) error {
|
|
|
|
if !e.templatesLoaded {
|
|
|
|
_ = e.LoadAllTemplates()
|
|
|
|
}
|
|
|
|
if len(e.store.Templates()) == 0 && len(e.store.Workflows()) == 0 {
|
|
|
|
return ErrNoTemplatesAvailable
|
|
|
|
}
|
|
|
|
if e.inputProvider.Count() == 0 {
|
|
|
|
return ErrNoTargetsAvailable
|
|
|
|
}
|
|
|
|
|
|
|
|
filtered := []func(event *output.ResultEvent){}
|
|
|
|
for _, callback := range callback {
|
|
|
|
if callback != nil {
|
|
|
|
filtered = append(filtered, callback)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
e.resultCallbacks = append(e.resultCallbacks, filtered...)
|
|
|
|
|
|
|
|
_ = e.engine.ExecuteScanWithOpts(e.store.Templates(), e.inputProvider, false)
|
|
|
|
defer e.engine.WorkPool().Wait()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewNucleiEngine creates a new nuclei engine instance
|
|
|
|
func NewNucleiEngine(options ...NucleiSDKOptions) (*NucleiEngine, error) {
|
|
|
|
// default options
|
|
|
|
e := &NucleiEngine{
|
|
|
|
opts: types.DefaultOptions(),
|
|
|
|
mode: singleInstance,
|
|
|
|
}
|
|
|
|
for _, option := range options {
|
|
|
|
if err := option(e); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err := e.init(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return e, nil
|
|
|
|
}
|