nuclei/internal/runner/runner.go

689 lines
24 KiB
Go
Raw Normal View History

package runner
import (
"context"
2021-11-29 14:38:45 +01:00
"encoding/json"
"fmt"
"net/http"
_ "net/http/pprof"
"os"
2023-12-20 14:29:20 +03:00
"path/filepath"
"reflect"
"strings"
"sync/atomic"
2021-04-16 16:56:41 +05:30
"time"
"github.com/projectdiscovery/nuclei/v3/internal/pdcp"
"github.com/projectdiscovery/nuclei/v3/pkg/installer"
uncoverlib "github.com/projectdiscovery/uncover"
2024-01-11 19:51:54 +05:30
pdcpauth "github.com/projectdiscovery/utils/auth/pdcp"
"github.com/projectdiscovery/utils/env"
2023-12-20 14:29:20 +03:00
fileutil "github.com/projectdiscovery/utils/file"
permissionutil "github.com/projectdiscovery/utils/permission"
updateutils "github.com/projectdiscovery/utils/update"
2020-08-24 00:16:18 +05:30
"github.com/logrusorgru/aurora"
"github.com/pkg/errors"
2022-10-12 22:04:37 -05:00
"github.com/projectdiscovery/ratelimit"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/internal/colorizer"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader"
"github.com/projectdiscovery/nuclei/v3/pkg/core"
"github.com/projectdiscovery/nuclei/v3/pkg/core/inputs/hybrid"
"github.com/projectdiscovery/nuclei/v3/pkg/external/customtemplates"
"github.com/projectdiscovery/nuclei/v3/pkg/input"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/parsers"
"github.com/projectdiscovery/nuclei/v3/pkg/progress"
"github.com/projectdiscovery/nuclei/v3/pkg/projectfile"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/automaticscan"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
"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/protocolinit"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/uncover"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/excludematchers"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting"
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/stats"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
"github.com/projectdiscovery/retryablehttp-go"
2023-06-12 12:30:46 +02:00
ptrutil "github.com/projectdiscovery/utils/ptr"
)
var (
// HideAutoSaveMsg is a global variable to hide the auto-save message
HideAutoSaveMsg = false
// EnableCloudUpload is global variable to enable cloud upload
EnableCloudUpload = false
)
// Runner is a client for running the enumeration process.
type Runner struct {
output output.Writer
interactsh *interactsh.Client
options *types.Options
projectFile *projectfile.ProjectFile
catalog catalog.Catalog
progress progress.Progress
colorizer aurora.Aurora
issuesClient reporting.Client
hmapInputProvider *hybrid.Input
browser *engine.Browser
rateLimiter *ratelimit.Limiter
hostErrors hosterrorscache.CacheInterface
2021-11-29 14:38:45 +01:00
resumeCfg *types.ResumeCfg
pprofServer *http.Server
// pdcp auto-save options
pdcpUploadErrMsg string
}
const pprofServerAddress = "127.0.0.1:8086"
// New creates a new client for running the enumeration process.
func New(options *types.Options) (*Runner, error) {
runner := &Runner{
options: options,
}
if options.HealthCheck {
gologger.Print().Msgf("%s\n", DoHealthCheck(options))
os.Exit(0)
}
// Version check by default
if config.DefaultConfig.CanCheckForUpdates() {
if err := installer.NucleiVersionCheck(); err != nil {
if options.Verbose || options.Debug {
gologger.Error().Msgf("nuclei version check failed got: %s\n", err)
}
}
// check for custom template updates and update if available
ctm, err := customtemplates.NewCustomTemplatesManager(options)
if err != nil {
gologger.Error().Label("custom-templates").Msgf("Failed to create custom templates manager: %s\n", err)
}
// Check for template updates and update if available.
// If the custom templates manager is not nil, we will install custom templates if there is a fresh installation
tm := &installer.TemplateManager{
CustomTemplates: ctm,
DisablePublicTemplates: options.PublicTemplateDisableDownload,
}
if err := tm.FreshInstallIfNotExists(); err != nil {
gologger.Warning().Msgf("failed to install nuclei templates: %s\n", err)
}
if err := tm.UpdateIfOutdated(); err != nil {
gologger.Warning().Msgf("failed to update nuclei templates: %s\n", err)
}
if config.DefaultConfig.NeedsIgnoreFileUpdate() {
if err := installer.UpdateIgnoreFile(); err != nil {
gologger.Warning().Msgf("failed to update nuclei ignore file: %s\n", err)
}
}
if options.UpdateTemplates {
// we automatically check for updates unless explicitly disabled
// this print statement is only to inform the user that there are no updates
if !config.DefaultConfig.NeedsTemplateUpdate() {
gologger.Info().Msgf("No new updates found for nuclei templates")
}
// manually trigger update of custom templates
if ctm != nil {
ctm.Update(context.TODO())
}
}
}
if options.Validate {
parsers.ShouldValidate = true
}
// TODO: refactor to pass options reference globally without cycles
parsers.NoStrictSyntax = options.NoStrictSyntax
yaml.StrictSyntax = !options.NoStrictSyntax
Issue 2613 custom template GitHub (#2630) * Add custom template download/update support from github - Accept the -gtr flag to accept the list of custom template repos(public/private) - Accept the -gt flag for github token. It internally sets os.Env variable - Update the flags from - -update to -nuclei-update for nuclei self update - -ut to -tup for template-update - -ud to -tud for custom template location - Add github.go file which has code related to download and update custom templates repos. * Reslove golint and test case error * Take default template from community directory - No need to give explicit community directory path. - Update the integration test to support the change in path * Update functional test script update template flag * Update the path from community to nuclei-template - Revert the code changes that were made to add community directory * remove the comment * Update the interactsh server url for testing * Update race condition command * update race condition cmd to download the templates * Debug integration test failure * update integration test to update templates * Refactor downloadCustomTemplate function. - Remove the log prining instead send the message. * Add test case for custom template repo download * move the download repo for loop into diff function * refactor updateTemplate function. * Create struct for github repos. - Create customtemplate struct for repo. - Add functions to customtemplate * update readme.md file * Refactor the downloadCustomTemplate function - create const variables for github & community as template type - Update gologger to INF - Validate templateUpdate to accept only github & community value. - Validate tempalteUpdate require githubTemplateRepo * Resolve requested changes * go mod update * misc option update * test update * Revert back update-template flag to boolean. - to update community templates `nuclei -ut` - to update custom templates `nuclei -ut -gtr ehsandeep/mobile-nuclei-templates` * Update readme to update flag documentation * Update go.mod Co-authored-by: Sandeep Singh <sandeep@projectdiscovery.io> Co-authored-by: sandeep <8293321+ehsandeep@users.noreply.github.com>
2022-11-03 20:27:18 +05:30
2021-07-25 03:13:46 +05:30
if options.Headless {
if engine.MustDisableSandbox() {
gologger.Warning().Msgf("The current platform and privileged user will run the browser without sandbox\n")
}
2021-07-25 03:13:46 +05:30
browser, err := engine.New(options)
if err != nil {
return nil, err
}
runner.browser = browser
}
2021-06-11 14:44:37 +05:30
runner.catalog = disk.NewCatalog(config.DefaultConfig.TemplatesDirectory)
var httpclient *retryablehttp.Client
if options.ProxyInternal && types.ProxyURL != "" || types.ProxySocksURL != "" {
var err error
httpclient, err = httpclientpool.Get(options, &httpclientpool.Configuration{})
if err != nil {
return nil, err
}
}
if err := reporting.CreateConfigIfNotExists(); err != nil {
return nil, err
}
reportingOptions, err := createReportingOptions(options)
if err != nil {
return nil, err
2021-06-05 18:01:08 +05:30
}
if reportingOptions != nil && httpclient != nil {
reportingOptions.HttpClient = httpclient
}
if reportingOptions != nil {
2021-07-07 19:23:25 +05:30
client, err := reporting.New(reportingOptions, options.ReportingDB)
if err != nil {
return nil, errors.Wrap(err, "could not create issue reporting client")
}
2021-07-07 19:23:25 +05:30
runner.issuesClient = client
}
// output coloring
useColor := !options.NoColor
runner.colorizer = aurora.NewAurora(useColor)
templates.Colorizer = runner.colorizer
templates.SeverityColorizer = colorizer.New(runner.colorizer)
if options.EnablePprof {
server := &http.Server{
Addr: pprofServerAddress,
Handler: http.DefaultServeMux,
}
gologger.Info().Msgf("Listening pprof debug server on: %s", pprofServerAddress)
runner.pprofServer = server
go func() {
2022-03-07 10:28:25 +05:30
_ = server.ListenAndServe()
}()
}
if (len(options.Templates) == 0 || !options.NewTemplates || (options.TargetsFilePath == "" && !options.Stdin && len(options.Targets) == 0)) && options.UpdateTemplates {
2020-06-25 03:53:37 +05:30
os.Exit(0)
}
// Initialize the input source
hmapInput, err := hybrid.New(&hybrid.Options{
Options: options,
})
if err != nil {
return nil, errors.Wrap(err, "could not create input provider")
2020-07-23 20:19:19 +02:00
}
runner.hmapInputProvider = hmapInput
2020-07-23 20:19:19 +02:00
2020-04-04 18:21:05 +05:30
// Create the output file if asked
outputWriter, err := output.NewStandardWriter(options)
2020-12-29 18:15:27 +05:30
if err != nil {
return nil, errors.Wrap(err, "could not create output file")
2020-04-04 18:21:05 +05:30
}
// setup a proxy writer to automatically upload results to PDCP
runner.output = runner.setupPDCPUpload(outputWriter)
2020-07-23 20:19:19 +02:00
JSON Export Handling Updates (#3466) * Switch -json to -jsonl * Add JSON output file * Update docs for EN and ID * Fix linting issue with error wrap * Add -j flag * Fix call for short flag * Correct typo "Ciper" to "Cipher" (#3468) * migrate dsl helper functions to dsl repo (#3461) * migrate dsl pkg code to dsl repo * fix lint error * upgrade dsl dependency * upgrade deps --------- Co-authored-by: Sandeep Singh <sandeep@projectdiscovery.io> * chore(deps): bump github.com/projectdiscovery/httpx in /v2 (#3469) Bumps [github.com/projectdiscovery/httpx](https://github.com/projectdiscovery/httpx) from 1.2.7 to 1.2.9. - [Release notes](https://github.com/projectdiscovery/httpx/releases) - [Changelog](https://github.com/projectdiscovery/httpx/blob/main/.goreleaser.yml) - [Commits](https://github.com/projectdiscovery/httpx/compare/v1.2.7...v1.2.9) --- updated-dependencies: - dependency-name: github.com/projectdiscovery/httpx dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump github.com/weppos/publicsuffix-go in /v2 (#3472) Bumps [github.com/weppos/publicsuffix-go](https://github.com/weppos/publicsuffix-go) from 0.20.0 to 0.30.0. - [Release notes](https://github.com/weppos/publicsuffix-go/releases) - [Changelog](https://github.com/weppos/publicsuffix-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/weppos/publicsuffix-go/compare/v0.20.0...v0.30.0) --- updated-dependencies: - dependency-name: github.com/weppos/publicsuffix-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump github.com/projectdiscovery/wappalyzergo in /v2 (#3473) Bumps [github.com/projectdiscovery/wappalyzergo](https://github.com/projectdiscovery/wappalyzergo) from 0.0.81 to 0.0.88. - [Release notes](https://github.com/projectdiscovery/wappalyzergo/releases) - [Commits](https://github.com/projectdiscovery/wappalyzergo/compare/v0.0.81...v0.0.88) --- updated-dependencies: - dependency-name: github.com/projectdiscovery/wappalyzergo dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump github.com/projectdiscovery/hmap in /v2 (#3470) Bumps [github.com/projectdiscovery/hmap](https://github.com/projectdiscovery/hmap) from 0.0.10 to 0.0.11. - [Release notes](https://github.com/projectdiscovery/hmap/releases) - [Commits](https://github.com/projectdiscovery/hmap/compare/v0.0.10...v0.0.11) --- updated-dependencies: - dependency-name: github.com/projectdiscovery/hmap dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * debug catalog path * use paths instead of filepath for aws path * deps update (#3477) * deps update * fixing gologger via callback * Moved `json-export` flag to the other exporters * Switch "json[-_]exporter to jsonexporter" --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Ramana Reddy <90540245+RamanaReddy0M@users.noreply.github.com> Co-authored-by: Sandeep Singh <sandeep@projectdiscovery.io> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Tarun Koyalwar <tarun@projectdiscovery.io> Co-authored-by: Mzack9999 <mzack9999@protonmail.com> Co-authored-by: shubhamrasal <shubhamdharmarasal@gmail.com>
2023-03-31 05:59:29 -04:00
if options.JSONL && options.EnableProgressBar {
options.StatsJSON = true
}
if options.StatsJSON {
options.EnableProgressBar = true
}
// Creates the progress tracking object
var progressErr error
statsInterval := options.StatsInterval
runner.progress, progressErr = progress.NewStatsTicker(statsInterval, options.EnableProgressBar, options.StatsJSON, false, options.MetricsPort)
if progressErr != nil {
return nil, progressErr
}
2020-07-23 20:19:19 +02:00
// create project file if requested or load the existing one
2020-10-17 02:10:47 +02:00
if options.Project {
2020-10-30 13:06:05 +01:00
var projectFileErr error
runner.projectFile, projectFileErr = projectfile.New(&projectfile.Options{Path: options.ProjectPath, Cleanup: utils.IsBlank(options.ProjectPath)})
2020-10-30 13:06:05 +01:00
if projectFileErr != nil {
return nil, projectFileErr
2020-10-15 23:39:00 +02:00
}
}
2021-11-29 14:38:45 +01:00
// create the resume configuration structure
resumeCfg := types.NewResumeCfg()
if runner.options.ShouldLoadResume() {
gologger.Info().Msg("Resuming from save checkpoint")
file, err := os.ReadFile(runner.options.Resume)
2021-11-29 14:38:45 +01:00
if err != nil {
return nil, err
}
err = json.Unmarshal(file, &resumeCfg)
2021-11-29 14:38:45 +01:00
if err != nil {
return nil, err
}
resumeCfg.Compile()
2021-11-29 14:38:45 +01:00
}
runner.resumeCfg = resumeCfg
opts := interactsh.DefaultOptions(runner.output, runner.issuesClient, runner.progress)
opts.Debug = runner.options.Debug
opts.NoColor = runner.options.NoColor
if options.InteractshURL != "" {
opts.ServerURL = options.InteractshURL
}
opts.Authorization = options.InteractshToken
opts.CacheSize = options.InteractionsCacheSize
opts.Eviction = time.Duration(options.InteractionsEviction) * time.Second
opts.CooldownPeriod = time.Duration(options.InteractionsCoolDownPeriod) * time.Second
opts.PollDuration = time.Duration(options.InteractionsPollDuration) * time.Second
opts.NoInteractsh = runner.options.NoInteractsh
opts.StopAtFirstMatch = runner.options.StopAtFirstMatch
opts.Debug = runner.options.Debug
opts.DebugRequest = runner.options.DebugRequests
opts.DebugResponse = runner.options.DebugResponse
if httpclient != nil {
opts.HTTPClient = httpclient
}
if opts.HTTPClient == nil {
httpOpts := retryablehttp.DefaultOptionsSingle
httpOpts.Timeout = 20 * time.Second // for stability reasons
if options.Timeout > 20 {
httpOpts.Timeout = time.Duration(options.Timeout) * time.Second
}
// in testing it was found most of times when interactsh failed, it was due to failure in registering /polling requests
opts.HTTPClient = retryablehttp.NewClient(retryablehttp.DefaultOptionsSingle)
}
interactshClient, err := interactsh.New(opts)
if err != nil {
gologger.Error().Msgf("Could not create interactsh client: %s", err)
} else {
runner.interactsh = interactshClient
2021-04-16 16:56:41 +05:30
}
if options.RateLimitMinute > 0 {
runner.rateLimiter = ratelimit.New(context.Background(), uint(options.RateLimitMinute), time.Minute)
} else if options.RateLimit > 0 {
runner.rateLimiter = ratelimit.New(context.Background(), uint(options.RateLimit), time.Second)
} else {
runner.rateLimiter = ratelimit.NewUnlimited(context.Background())
}
return runner, nil
}
// runStandardEnumeration runs standard enumeration
func (r *Runner) runStandardEnumeration(executerOpts protocols.ExecutorOptions, store *loader.Store, engine *core.Engine) (*atomic.Bool, error) {
if r.options.AutomaticScan {
return r.executeSmartWorkflowInput(executerOpts, store, engine)
}
return r.executeTemplatesInput(store, engine)
}
// Close releases all the resources and cleans up
2020-04-04 18:21:05 +05:30
func (r *Runner) Close() {
2020-09-11 21:35:29 +05:30
if r.output != nil {
r.output.Close()
}
if r.projectFile != nil {
r.projectFile.Close()
2020-10-15 23:39:00 +02:00
}
r.hmapInputProvider.Close()
protocolinit.Close()
if r.pprofServer != nil {
_ = r.pprofServer.Shutdown(context.Background())
}
if r.rateLimiter != nil {
r.rateLimiter.Stop()
}
2020-04-04 18:21:05 +05:30
}
// setupPDCPUpload sets up the PDCP upload writer
// by creating a new writer and returning it
func (r *Runner) setupPDCPUpload(writer output.Writer) output.Writer {
if !(r.options.EnableCloudUpload || EnableCloudUpload) {
r.pdcpUploadErrMsg = fmt.Sprintf("[%v] Scan results upload to cloud is disabled.", aurora.BrightYellow("WRN"))
return writer
}
color := aurora.NewAurora(!r.options.NoColor)
2024-01-11 19:51:54 +05:30
h := &pdcpauth.PDCPCredHandler{}
creds, err := h.GetCreds()
if err != nil {
2024-01-11 19:51:54 +05:30
if err != pdcpauth.ErrNoCreds && !HideAutoSaveMsg {
gologger.Verbose().Msgf("Could not get credentials for cloud upload: %s\n", err)
}
2024-01-11 19:51:54 +05:30
r.pdcpUploadErrMsg = fmt.Sprintf("[%v] To view results on Cloud Dashboard, Configure API key from %v", color.BrightYellow("WRN"), pdcpauth.DashBoardURL)
return writer
}
uploadWriter, err := pdcp.NewUploadWriter(creds)
if err != nil {
2024-01-11 19:51:54 +05:30
r.pdcpUploadErrMsg = fmt.Sprintf("[%v] PDCP (%v) Auto-Save Failed: %s\n", color.BrightYellow("WRN"), pdcpauth.DashBoardURL, err)
return writer
}
return output.NewMultiWriter(writer, uploadWriter)
}
// RunEnumeration sets up the input layer for giving input nuclei.
// binary and runs the actual enumeration
func (r *Runner) RunEnumeration() error {
// If user asked for new templates to be executed, collect the list from the templates' directory.
if r.options.NewTemplates {
if arr := config.DefaultConfig.GetNewAdditions(); len(arr) > 0 {
r.options.Templates = append(r.options.Templates, arr...)
}
}
if len(r.options.NewTemplatesWithVersion) > 0 {
if arr := installer.GetNewTemplatesInVersions(r.options.NewTemplatesWithVersion...); len(arr) > 0 {
r.options.Templates = append(r.options.Templates, arr...)
}
}
2021-12-01 10:35:18 -06:00
// Exclude ignored file for validation
if !r.options.Validate {
ignoreFile := config.ReadIgnoreFile()
r.options.ExcludeTags = append(r.options.ExcludeTags, ignoreFile.Tags...)
r.options.ExcludedTemplates = append(r.options.ExcludedTemplates, ignoreFile.Files...)
}
2021-10-27 16:50:36 +05:30
// Create the executor options which will be used throughout the execution
2021-10-27 16:50:36 +05:30
// stage by the nuclei engine modules.
executorOpts := protocols.ExecutorOptions{
Output: r.output,
Options: r.options,
Progress: r.progress,
Catalog: r.catalog,
IssuesClient: r.issuesClient,
RateLimiter: r.rateLimiter,
Interactsh: r.interactsh,
ProjectFile: r.projectFile,
Browser: r.browser,
Colorizer: r.colorizer,
2021-11-29 14:38:45 +01:00
ResumeCfg: r.resumeCfg,
ExcludeMatchers: excludematchers.New(r.options.ExcludeMatchers),
InputHelper: input.NewHelper(),
2021-07-01 14:36:40 +05:30
}
if r.options.ShouldUseHostError() {
cache := hosterrorscache.New(r.options.MaxHostError, hosterrorscache.DefaultMaxHostsCount, r.options.TrackError)
cache.SetVerbose(r.options.Verbose)
r.hostErrors = cache
executorOpts.HostErrorsCache = cache
}
executorEngine := core.New(r.options)
executorEngine.SetExecuterOptions(executorOpts)
workflowLoader, err := parsers.NewLoader(&executorOpts)
if err != nil {
return errors.Wrap(err, "Could not create loader.")
}
executorOpts.WorkflowLoader = workflowLoader
store, err := loader.New(loader.NewConfig(r.options, r.catalog, executorOpts))
if err != nil {
return errors.Wrap(err, "could not load templates from config")
2020-08-02 15:48:10 +02:00
}
if r.options.Validate {
if err := store.ValidateTemplates(); err != nil {
2021-08-27 17:06:06 +03:00
return err
}
if stats.GetValue(parsers.SyntaxErrorStats) == 0 && stats.GetValue(parsers.SyntaxWarningStats) == 0 && stats.GetValue(parsers.RuntimeWarningsStats) == 0 {
gologger.Info().Msgf("All templates validated successfully\n")
} else {
return errors.New("encountered errors while performing template validation")
}
2021-07-07 19:23:25 +05:30
return nil // exit
}
store.Load()
// TODO: remove below functions after v3 or update warning messages
disk.PrintDeprecatedPathsMsgIfApplicable(r.options.Silent)
templates.PrintDeprecatedProtocolNameMsgIfApplicable(r.options.Silent, r.options.Verbose)
// add the hosts from the metadata queries of loaded templates into input provider
if r.options.Uncover && len(r.options.UncoverQuery) == 0 {
uncoverOpts := &uncoverlib.Options{
Limit: r.options.UncoverLimit,
MaxRetry: r.options.Retries,
Timeout: r.options.Timeout,
RateLimit: uint(r.options.UncoverRateLimit),
RateLimitUnit: time.Minute, // default unit is minute
}
ret := uncover.GetUncoverTargetsFromMetadata(context.TODO(), store.Templates(), r.options.UncoverField, uncoverOpts)
for host := range ret {
r.hmapInputProvider.SetWithExclusions(host)
}
}
// list all templates
if r.options.TemplateList || r.options.TemplateDisplay {
r.listAvailableStoreTemplates(store)
os.Exit(0)
}
// display execution info like version , templates used etc
2021-10-28 23:17:05 +05:30
r.displayExecutionInfo(store)
// If not explicitly disabled, check if http based protocols
// are used, and if inputs are non-http to pre-perform probing
// of urls and storing them for execution.
if !r.options.DisableHTTPProbe && loader.IsHTTPBasedProtocolUsed(store) && r.isInputNonHTTP() {
inputHelpers, err := r.initializeTemplatesHTTPInput()
if err != nil {
return errors.Wrap(err, "could not probe http input")
}
executorOpts.InputHelper.InputsHTTP = inputHelpers
}
2022-04-18 17:21:33 -05:00
enumeration := false
var results *atomic.Bool
results, err = r.runStandardEnumeration(executorOpts, store, executorEngine)
enumeration = true
if !enumeration {
return err
}
if r.interactsh != nil {
matched := r.interactsh.Close()
if matched {
2022-09-19 08:38:52 +02:00
results.CompareAndSwap(false, true)
}
}
r.progress.Stop()
if executorOpts.InputHelper != nil {
_ = executorOpts.InputHelper.Close()
}
if r.issuesClient != nil {
r.issuesClient.Close()
}
2023-06-12 12:35:21 +02:00
// todo: error propagation without canonical straight error check is required by cloud?
// use safe dereferencing to avoid potential panics in case of previous unchecked errors
2023-06-12 12:30:46 +02:00
if v := ptrutil.Safe(results); !v.Load() {
gologger.Info().Msgf("No results found. Better luck next time!")
}
if r.browser != nil {
r.browser.Close()
}
// check if a passive scan was requested but no target was provided
if r.options.OfflineHTTP && len(r.options.Targets) == 0 && r.options.TargetsFilePath == "" {
return errors.Wrap(err, "missing required input (http response) to run passive templates")
}
return err
}
func (r *Runner) isInputNonHTTP() bool {
var nonURLInput bool
r.hmapInputProvider.Scan(func(value *contextargs.MetaInput) bool {
if !strings.Contains(value.Input, "://") {
nonURLInput = true
return false
}
return true
})
return nonURLInput
}
func (r *Runner) executeSmartWorkflowInput(executorOpts protocols.ExecutorOptions, store *loader.Store, engine *core.Engine) (*atomic.Bool, error) {
r.progress.Init(r.hmapInputProvider.Count(), 0, 0)
service, err := automaticscan.New(automaticscan.Options{
ExecuterOpts: executorOpts,
Store: store,
Engine: engine,
Target: r.hmapInputProvider,
})
if err != nil {
2022-04-19 16:14:49 +05:30
return nil, errors.Wrap(err, "could not create automatic scan service")
}
service.Execute()
result := &atomic.Bool{}
result.Store(service.Close())
return result, nil
}
func (r *Runner) executeTemplatesInput(store *loader.Store, engine *core.Engine) (*atomic.Bool, error) {
if r.options.VerboseVerbose {
2021-07-01 14:36:40 +05:30
for _, template := range store.Templates() {
r.logAvailableTemplate(template.Path)
}
for _, template := range store.Workflows() {
r.logAvailableTemplate(template.Path)
}
}
2021-01-15 14:17:34 +05:30
finalTemplates := []*templates.Template{}
finalTemplates = append(finalTemplates, store.Templates()...)
finalTemplates = append(finalTemplates, store.Workflows()...)
if len(finalTemplates) == 0 {
return nil, errors.New("no templates provided for scan")
}
results := engine.ExecuteScanWithOpts(finalTemplates, r.hmapInputProvider, r.options.DisableClustering)
return results, nil
}
2021-10-28 23:17:05 +05:30
// displayExecutionInfo displays misc info about the nuclei engine execution
func (r *Runner) displayExecutionInfo(store *loader.Store) {
// Display stats for any loaded templates' syntax warnings or errors
stats.Display(parsers.SyntaxWarningStats)
stats.Display(parsers.SyntaxErrorStats)
stats.Display(parsers.RuntimeWarningsStats)
if r.options.Verbose {
// only print these stats in verbose mode
stats.DisplayAsWarning(parsers.HeadlessFlagWarningStats)
stats.DisplayAsWarning(parsers.CodeFlagWarningStats)
stats.DisplayAsWarning(parsers.TemplatesExecutedStats)
}
stats.DisplayAsWarning(parsers.UnsignedWarning)
2021-10-28 23:17:05 +05:30
cfg := config.DefaultConfig
2021-10-28 23:17:05 +05:30
gologger.Info().Msgf("Current nuclei version: %v %v", config.Version, updateutils.GetVersionDescription(config.Version, cfg.LatestNucleiVersion))
gologger.Info().Msgf("Current nuclei-templates version: %v %v", cfg.TemplateVersion, updateutils.GetVersionDescription(cfg.TemplateVersion, cfg.LatestNucleiTemplatesVersion))
if !HideAutoSaveMsg {
if r.pdcpUploadErrMsg != "" {
gologger.Print().Msgf("%s", r.pdcpUploadErrMsg)
} else {
2024-01-11 19:51:54 +05:30
gologger.Info().Msgf("To view results on cloud dashboard, visit %v/scans upon scan completion.", pdcpauth.DashBoardURL)
}
}
2021-10-28 23:17:05 +05:30
if len(store.Templates()) > 0 {
gologger.Info().Msgf("New templates added in latest release: %d", len(config.DefaultConfig.GetNewAdditions()))
gologger.Info().Msgf("Templates loaded for current scan: %d", len(store.Templates()))
2021-10-28 23:17:05 +05:30
}
if len(store.Workflows()) > 0 {
gologger.Info().Msgf("Workflows loaded for current scan: %d", len(store.Workflows()))
2021-10-28 23:17:05 +05:30
}
for k, v := range templates.SignatureStats {
if v.Load() > 0 {
if k != templates.Unsigned {
gologger.Info().Msgf("Executing %d signed templates from %s", v.Load(), k)
} else if !r.options.Silent && !config.DefaultConfig.HideTemplateSigWarning {
gologger.Print().Msgf("[%v] Executing %d unsigned templates. Use with caution.", aurora.BrightYellow("WRN"), v.Load())
}
}
}
2022-12-09 14:55:51 +05:30
if r.hmapInputProvider.Count() > 0 {
gologger.Info().Msgf("Targets loaded for current scan: %d", r.hmapInputProvider.Count())
}
}
2021-11-29 14:38:45 +01:00
// SaveResumeConfig to file
func (r *Runner) SaveResumeConfig(path string) error {
2023-12-20 14:29:20 +03:00
dir := filepath.Dir(path)
if !fileutil.FolderExists(dir) {
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
return err
}
}
2022-07-26 15:00:15 +02:00
resumeCfgClone := r.resumeCfg.Clone()
resumeCfgClone.ResumeFrom = resumeCfgClone.Current
data, _ := json.MarshalIndent(resumeCfgClone, "", "\t")
2021-11-29 14:38:45 +01:00
return os.WriteFile(path, data, permissionutil.ConfigFilePermission)
2021-11-29 14:38:45 +01:00
}
type WalkFunc func(reflect.Value, reflect.StructField)
// Walk traverses a struct and executes a callback function on each value in the struct.
// The interface{} passed to the function should be a pointer to a struct or a struct.
// WalkFunc is the callback function used for each value in the struct. It is passed the
// reflect.Value and reflect.Type properties of the value in the struct.
func Walk(s interface{}, callback WalkFunc) {
structValue := reflect.ValueOf(s)
if structValue.Kind() == reflect.Ptr {
structValue = structValue.Elem()
}
if structValue.Kind() != reflect.Struct {
return
}
for i := 0; i < structValue.NumField(); i++ {
field := structValue.Field(i)
fieldType := structValue.Type().Field(i)
if !fieldType.IsExported() {
continue
}
if field.Kind() == reflect.Struct {
Walk(field.Addr().Interface(), callback)
} else if field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct {
Walk(field.Interface(), callback)
} else {
callback(field, fieldType)
}
}
}
// expandEndVars looks for values in a struct tagged with "yaml" and checks if they are prefixed with '$'.
// If they are, it will try to retrieve the value from the environment and if it exists, it will set the
// value of the field to that of the environment variable.
func expandEndVars(f reflect.Value, fieldType reflect.StructField) {
if _, ok := fieldType.Tag.Lookup("yaml"); !ok {
return
}
if f.Kind() == reflect.String {
str := f.String()
if strings.HasPrefix(str, "$") {
env := strings.TrimPrefix(str, "$")
retrievedEnv := os.Getenv(env)
if retrievedEnv != "" {
f.SetString(os.Getenv(env))
}
}
}
}
func init() {
HideAutoSaveMsg = env.GetEnvOrDefault("DISABLE_CLOUD_UPLOAD_WRN", false)
EnableCloudUpload = env.GetEnvOrDefault("ENABLE_CLOUD_UPLOAD", false)
}