mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2026-01-31 15:53:10 +08:00
* fix(lib): segfault when init engine with `EnableHeadlessWithOpts` The panic was caused by attempting to log a sandbox warning before the logger was initialized. RCA: * SDK option funcs were exec'd before logger init. * `EnableHeadlessWithOpts()` attempted to create browser instance & log warnings during the config phase. * `Logger` was only init'd later in `init()` phase. * This caused nil pointer dereference when `MustDisableSandbox()` returned true (root on Linux/Unix or Windows). Changes: * Init `Logger` in `types.DefaultOptions()` to ensure it's always available before any option functions execute. * Init `Logger` field in both `NewNucleiEngineCtx()` and `NewThreadSafeNucleiEngineCtx()` from `defaultOptions.Logger`. * Move browser instance creation from `EnableHeadlessWithOpts()` to the `init()` phase where `Logger` is guaranteed to be available. * Simplify logger sync logic in `init()` to only update if changed by `WithLogger` option. * Add test case to verify headless initialization works without panic. The fix maintains backward compatibility while make sure the logger is always available when needed by any SDK option function. Fixes #6601. Signed-off-by: Dwi Siswanto <git@dw1.io> * build(make): adds `-timeout 30m -count 1` GOFLAGS in `test` cmd Signed-off-by: Dwi Siswanto <git@dw1.io> * Revert "fix(lib): segfault when init engine with `EnableHeadlessWithOpts`" let see if this pass flaky test. This reverts commit 63fcb6a1cbe7a4db7a78be766affc70eb237e57e. * test(engine): let see if this pass flaky test Signed-off-by: Dwi Siswanto <git@dw1.io> * Revert "Revert "fix(lib): segfault when init engine with `EnableHeadlessWithOpts`"" This reverts commit 62b4223803ccb1e93593e2e08e39923d76aa20b1. * test(engine): increase `TestActionNavigate` timeout Signed-off-by: Dwi Siswanto <git@dw1.io> * Revert "test(engine): let see if this pass flaky test" This reverts commit d27cd985cff1b06aa1965ea11f8aa32f00778ab5. --------- Signed-off-by: Dwi Siswanto <git@dw1.io>
186 lines
6.6 KiB
Go
186 lines
6.6 KiB
Go
package nuclei
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/logrusorgru/aurora"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/core"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/input/provider"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
|
|
"github.com/projectdiscovery/utils/errkit"
|
|
"github.com/rs/xid"
|
|
)
|
|
|
|
// unsafeOptions are those nuclei objects/instances/types
|
|
// that are required to run nuclei engine but are not thread safe
|
|
// hence they are ephemeral and are created on every ExecuteNucleiWithOpts invocation
|
|
// in ThreadSafeNucleiEngine
|
|
type unsafeOptions struct {
|
|
executerOpts *protocols.ExecutorOptions
|
|
engine *core.Engine
|
|
}
|
|
|
|
// createEphemeralObjects creates ephemeral nuclei objects/instances/types
|
|
func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types.Options) (*unsafeOptions, error) {
|
|
u := &unsafeOptions{}
|
|
u.executerOpts = &protocols.ExecutorOptions{
|
|
Output: base.customWriter,
|
|
Options: opts,
|
|
Progress: base.customProgress,
|
|
Catalog: base.catalog,
|
|
IssuesClient: base.rc,
|
|
RateLimiter: base.rateLimiter,
|
|
Interactsh: base.interactshClient,
|
|
HostErrorsCache: base.hostErrCache,
|
|
Colorizer: aurora.NewAurora(true),
|
|
ResumeCfg: types.NewResumeCfg(),
|
|
Parser: base.parser,
|
|
Browser: base.browserInstance,
|
|
}
|
|
if opts.ShouldUseHostError() && base.hostErrCache != nil {
|
|
u.executerOpts.HostErrorsCache = base.hostErrCache
|
|
}
|
|
if opts.RateLimitMinute > 0 {
|
|
opts.RateLimit = opts.RateLimitMinute
|
|
opts.RateLimitDuration = time.Minute
|
|
}
|
|
if opts.RateLimit > 0 && opts.RateLimitDuration == 0 {
|
|
opts.RateLimitDuration = time.Second
|
|
}
|
|
u.executerOpts.RateLimiter = utils.GetRateLimiter(ctx, opts.RateLimit, opts.RateLimitDuration)
|
|
u.engine = core.New(opts)
|
|
u.engine.SetExecuterOptions(u.executerOpts)
|
|
return u, nil
|
|
}
|
|
|
|
// closeEphemeralObjects closes all resources used by ephemeral nuclei objects/instances/types
|
|
func closeEphemeralObjects(u *unsafeOptions) {
|
|
if u.executerOpts.RateLimiter != nil {
|
|
u.executerOpts.RateLimiter.Stop()
|
|
}
|
|
// dereference all objects that were inherited from base nuclei engine
|
|
// since these are meant to be closed globally by base nuclei engine
|
|
u.executerOpts.Output = nil
|
|
u.executerOpts.IssuesClient = nil
|
|
u.executerOpts.Interactsh = nil
|
|
u.executerOpts.HostErrorsCache = nil
|
|
u.executerOpts.Progress = nil
|
|
u.executerOpts.Catalog = nil
|
|
u.executerOpts.Parser = nil
|
|
}
|
|
|
|
// ThreadSafeNucleiEngine is a tweaked version of nuclei.Engine whose methods are thread-safe
|
|
// and can be used concurrently. Non-thread-safe methods start with Global prefix
|
|
type ThreadSafeNucleiEngine struct {
|
|
eng *NucleiEngine
|
|
}
|
|
|
|
// NewThreadSafeNucleiEngine creates a new nuclei engine with given options
|
|
// whose methods are thread-safe and can be used concurrently
|
|
// Note: Non-thread-safe methods start with Global prefix
|
|
func NewThreadSafeNucleiEngineCtx(ctx context.Context, opts ...NucleiSDKOptions) (*ThreadSafeNucleiEngine, error) {
|
|
defaultOptions := types.DefaultOptions()
|
|
defaultOptions.ExecutionId = xid.New().String()
|
|
|
|
e := &NucleiEngine{
|
|
opts: defaultOptions,
|
|
mode: threadSafe,
|
|
ctx: ctx,
|
|
Logger: defaultOptions.Logger,
|
|
}
|
|
for _, option := range opts {
|
|
if err := option(e); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if err := e.init(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
return &ThreadSafeNucleiEngine{eng: e}, nil
|
|
}
|
|
|
|
// Deprecated: use NewThreadSafeNucleiEngineCtx instead
|
|
func NewThreadSafeNucleiEngine(opts ...NucleiSDKOptions) (*ThreadSafeNucleiEngine, error) {
|
|
return NewThreadSafeNucleiEngineCtx(context.Background(), opts...)
|
|
}
|
|
|
|
// GlobalLoadAllTemplates loads all templates from nuclei-templates repo
|
|
// This method will load all templates based on filters given at the time of nuclei engine creation in opts
|
|
func (e *ThreadSafeNucleiEngine) GlobalLoadAllTemplates() error {
|
|
return e.eng.LoadAllTemplates()
|
|
}
|
|
|
|
// GlobalResultCallback sets a callback function which will be called for each result
|
|
func (e *ThreadSafeNucleiEngine) GlobalResultCallback(callback func(event *output.ResultEvent)) {
|
|
e.eng.resultCallbacks = []func(*output.ResultEvent){callback}
|
|
}
|
|
|
|
// ExecuteNucleiWithOptsCtx executes templates on targets and calls callback on each result(only if results are found)
|
|
// This method can be called concurrently and it will use some global resources but can be run parallelly
|
|
// by invoking this method with different options and targets
|
|
// Note: Not all options are thread-safe. this method will throw error if you try to use non-thread-safe options
|
|
func (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOptsCtx(ctx context.Context, targets []string, opts ...NucleiSDKOptions) error {
|
|
baseOpts := e.eng.opts.Copy()
|
|
tmpEngine := &NucleiEngine{opts: baseOpts, mode: threadSafe}
|
|
for _, option := range opts {
|
|
if err := option(tmpEngine); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// create ephemeral nuclei objects/instances/types using base nuclei engine
|
|
unsafeOpts, err := createEphemeralObjects(ctx, e.eng, tmpEngine.opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// cleanup and stop all resources
|
|
defer closeEphemeralObjects(unsafeOpts)
|
|
|
|
// load templates
|
|
workflowLoader, err := workflow.NewLoader(unsafeOpts.executerOpts)
|
|
if err != nil {
|
|
return errkit.Wrapf(err, "Could not create workflow loader: %s", err)
|
|
}
|
|
unsafeOpts.executerOpts.WorkflowLoader = workflowLoader
|
|
|
|
store, err := loader.New(loader.NewConfig(tmpEngine.opts, e.eng.catalog, unsafeOpts.executerOpts))
|
|
if err != nil {
|
|
return errkit.Wrapf(err, "Could not create loader client: %s", err)
|
|
}
|
|
store.Load()
|
|
|
|
inputProvider := provider.NewSimpleInputProviderWithUrls(e.eng.opts.ExecutionId, targets...)
|
|
|
|
if len(store.Templates()) == 0 && len(store.Workflows()) == 0 {
|
|
return ErrNoTemplatesAvailable
|
|
}
|
|
if inputProvider.Count() == 0 {
|
|
return ErrNoTargetsAvailable
|
|
}
|
|
|
|
engine := core.New(tmpEngine.opts)
|
|
engine.SetExecuterOptions(unsafeOpts.executerOpts)
|
|
|
|
_ = engine.ExecuteScanWithOpts(ctx, store.Templates(), inputProvider, false)
|
|
|
|
engine.WorkPool().Wait()
|
|
return nil
|
|
}
|
|
|
|
// ExecuteNucleiWithOpts is same as ExecuteNucleiWithOptsCtx but with default context
|
|
// This is a placeholder and will be deprecated in future major release
|
|
func (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOpts(targets []string, opts ...NucleiSDKOptions) error {
|
|
return e.ExecuteNucleiWithOptsCtx(context.Background(), targets, opts...)
|
|
}
|
|
|
|
// Close all resources used by nuclei engine
|
|
func (e *ThreadSafeNucleiEngine) Close() {
|
|
e.eng.Close()
|
|
}
|