Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve error logging for unresolved variables #4447

Draft
wants to merge 4 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/nuclei/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ on extensive configurability, massive extensibility and ease of use.`)
flagSet.BoolVarP(&options.EnablePprof, "enable-pprof", "ep", false, "enable pprof debugging server"),
flagSet.CallbackVarP(printTemplateVersion, "templates-version", "tv", "shows the version of the installed nuclei-templates"),
flagSet.BoolVarP(&options.HealthCheck, "health-check", "hc", false, "run diagnostic check up"),
flagSet.DynamicVarP(&options.ErrorLabels, "error-label", "elabel", []string{"UNV"}, "enable logging for specific error labels (comma-separated) (labels: UNV"),
)

flagSet.CreateGroup("update", "Update",
Expand Down
22 changes: 22 additions & 0 deletions internal/runner/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/projectdiscovery/gologger/formatter"
"github.com/projectdiscovery/gologger/levels"
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
elabel "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/errors/label"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine"
Expand All @@ -26,6 +27,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown"
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/sarif"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/stats"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml"
fileutil "github.com/projectdiscovery/utils/file"
"github.com/projectdiscovery/utils/generic"
Expand Down Expand Up @@ -87,6 +89,8 @@ func ParseOptions(options *types.Options) {
// Load the resolvers if user asked for them
loadResolvers(options)

options.ErrorLabels = parseErrorLables(options.ErrorLabels)

err := protocolinit.Init(options)
if err != nil {
gologger.Fatal().Msgf("Could not initialize protocols: %s\n", err)
Expand Down Expand Up @@ -430,3 +434,21 @@ func getBoolEnvValue(key string) bool {
value := os.Getenv(key)
return strings.EqualFold(value, "true")
}

func parseErrorLables(errorLabels []string) []string {
// Make error label entries in stats
for _, v := range elabel.ErrorLableMap {
stats.NewEntry(v.Name, v.Description)
}
if len(errorLabels) == 0 {
return errorLabels
}
var errLabels []string
for _, label := range errorLabels {
label = strings.ToLower(label)
if v, ok := elabel.ErrorLableMap[label]; ok {
errLabels = append(errLabels, v.Name)
}
}
return errLabels
}
14 changes: 14 additions & 0 deletions internal/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
uncoverlib "github.com/projectdiscovery/uncover"
"github.com/projectdiscovery/utils/env"
permissionutil "github.com/projectdiscovery/utils/permission"
stringsutil "github.com/projectdiscovery/utils/strings"
updateutils "github.com/projectdiscovery/utils/update"

"github.com/logrusorgru/aurora"
Expand All @@ -40,6 +41,7 @@ import (
"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"
elabel "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/errors/label"
"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"
Expand Down Expand Up @@ -492,6 +494,7 @@ func (r *Runner) RunEnumeration() error {
}
}
r.progress.Stop()
r.displayErrorInfo()

if executorOpts.InputHelper != nil {
_ = executorOpts.InputHelper.Close()
Expand Down Expand Up @@ -615,6 +618,17 @@ func (r *Runner) displayExecutionInfo(store *loader.Store) {
}
}

func (r *Runner) displayErrorInfo() {
if !r.options.Verbose {
return
}
errLables := r.options.ErrorLabels
// if error label is not provided, display the stats for specific labels
if !stringsutil.ContainsAny(elabel.UnresolvedVariablesErrorLabel, errLables...) {
stats.Display(elabel.UnresolvedVariablesErrorLabel)
}
}

// SaveResumeConfig to file
func (r *Runner) SaveResumeConfig(path string) error {
resumeCfgClone := r.resumeCfg.Clone()
Expand Down
35 changes: 35 additions & 0 deletions pkg/protocols/common/errors/label/error_label.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package label

import (
"strings"
)

const (
UnresolvedVariablesErrorLabel = "unresolved-variables-error"
)

var (
ErrorLabels = []string{
UnresolvedVariablesErrorLabel,
}

ErrorLableMap = map[string]struct {
Name string
Description string
}{
"unv": {
Name: UnresolvedVariablesErrorLabel,
Description: "Failed %v requests due to unresolved variables. Use -elabel=UNV to enable unresolved variables logs.",
},
}
)

// Contains checks if the error string contains any of the provided labels, if yes returns matched label and true
func Contains(errStr string, lables []string) (string, bool) {
for _, label := range lables {
if strings.Contains(errStr, label) {
return label, true
}
}
return "", false
}
4 changes: 3 additions & 1 deletion pkg/protocols/common/expressions/variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (

"github.com/Knetic/govaluate"
"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl"
elabel "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/errors/label"
errorutil "github.com/projectdiscovery/utils/errors"
)

var (
Expand Down Expand Up @@ -38,7 +40,7 @@ func ContainsUnresolvedVariables(items ...string) error {
unresolvedVariables = append(unresolvedVariables, match[1])
}
if len(unresolvedVariables) > 0 {
return errors.New("unresolved variables found: " + strings.Join(unresolvedVariables, ","))
return errorutil.NewWithTag(elabel.UnresolvedVariablesErrorLabel, "unresolved variables found: "+strings.Join(unresolvedVariables, ","))
}
}

Expand Down
10 changes: 6 additions & 4 deletions pkg/protocols/common/expressions/variables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"errors"
"testing"

elabel "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/errors/label"
errorutil "github.com/projectdiscovery/utils/errors"
"github.com/stretchr/testify/require"
)

Expand All @@ -12,11 +14,11 @@ func TestUnresolvedVariablesCheck(t *testing.T) {
data string
err error
}{
{"{{test}}", errors.New("unresolved variables found: test")},
{"{{test}}/{{another}}", errors.New("unresolved variables found: test,another")},
{"{{test}}", errorutil.NewWithTag(elabel.UnresolvedVariablesErrorLabel, "unresolved variables found: test")},
{"{{test}}/{{another}}", errorutil.NewWithTag(elabel.UnresolvedVariablesErrorLabel, "unresolved variables found: test,another")},
{"test", nil},
{"%7b%7btest%7d%7d", errors.New("unresolved variables found: test")},
{"%7B%7Bfirst%2Asecond%7D%7D", errors.New("unresolved variables found: first%2Asecond")},
{"%7b%7btest%7d%7d", errorutil.NewWithTag(elabel.UnresolvedVariablesErrorLabel, "unresolved variables found: test")},
{"%7B%7Bfirst%2Asecond%7D%7D", errorutil.NewWithTag(elabel.UnresolvedVariablesErrorLabel, errors.New("unresolved variables found: first%2Asecond").Error())},
{"{{7*7}}", nil},
{"{{'a'+'b'}}", nil},
{"{{'a'}}", nil},
Expand Down
11 changes: 8 additions & 3 deletions pkg/protocols/http/build_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
elabel "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/errors/label"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
Expand All @@ -34,7 +35,7 @@ import (
// ErrEvalExpression
var (
ErrEvalExpression = errorutil.NewWithTag("expr", "could not evaluate helper expressions")
ErrUnresolvedVars = errorutil.NewWithFmt("unresolved variables `%v` found in request")
UnresolvedErrFmt = "unresolved variables `%v` found in request"
)

// generatedRequest is a single generated request wrapped for a template request
Expand Down Expand Up @@ -208,7 +209,7 @@ func (r *requestGenerator) makeSelfContainedRequest(ctx context.Context, data st
}

if err := expressions.ContainsUnresolvedVariables(parts[1]); err != nil {
return nil, ErrUnresolvedVars.Msgf(parts[1])
return nil, errorutil.NewWithTag(elabel.UnresolvedVariablesErrorLabel, UnresolvedErrFmt, parts[1])
}

parsed, err := urlutil.ParseURL(parts[1], true)
Expand All @@ -229,7 +230,7 @@ func (r *requestGenerator) makeSelfContainedRequest(ctx context.Context, data st
if err := expressions.ContainsUnresolvedVariables(data); err != nil {
// early exit: if there are any unresolved variables in `path` after evaluation
// then return early since this will definitely fail
return nil, ErrUnresolvedVars.Msgf(data)
return nil, errorutil.NewWithTag(elabel.UnresolvedVariablesErrorLabel, UnresolvedErrFmt, data)
}

urlx, err := urlutil.ParseURL(data, true)
Expand Down Expand Up @@ -289,6 +290,10 @@ func (r *requestGenerator) generateRawRequest(ctx context.Context, rawRequest st
return unsafeReq, nil
}

if err := expressions.ContainsUnresolvedVariables(rawRequestData.FullURL); err != nil {
return nil, errorutil.NewWithTag(elabel.UnresolvedVariablesErrorLabel, UnresolvedErrFmt, rawRequestData.FullURL)
}

urlx, err := urlutil.ParseURL(rawRequestData.FullURL, true)
if err != nil {
return nil, errorutil.NewWithErr(err).Msgf("failed to create request with url %v got %v", rawRequestData.FullURL, err).WithTag("raw")
Expand Down
13 changes: 11 additions & 2 deletions pkg/protocols/http/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
elabel "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/errors/label"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/fuzz"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
Expand All @@ -36,7 +37,9 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/signerpool"
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/stats"
"github.com/projectdiscovery/rawhttp"
errorutil "github.com/projectdiscovery/utils/errors"
"github.com/projectdiscovery/utils/reader"
sliceutil "github.com/projectdiscovery/utils/slice"
stringsutil "github.com/projectdiscovery/utils/strings"
Expand Down Expand Up @@ -368,6 +371,9 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa
return true, nil
}
request.options.Progress.IncrementFailedRequestsBy(int64(generator.Total()))
if lb, ok := elabel.Contains(err.Error(), elabel.ErrorLabels); ok {
stats.Increment(lb)
}
return true, err
}

Expand Down Expand Up @@ -462,7 +468,7 @@ func (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicVa

const drainReqSize = int64(8 * 1024)

var errStopExecution = errors.New("stop execution due to unresolved variables")
var errStopExecution = errorutil.NewWithTag(elabel.UnresolvedVariablesErrorLabel, "stop execution due to unresolved variables")

// executeRequest executes the actual generated request and returns error if occurred
func (request *Request) executeRequest(input *contextargs.Context, generatedRequest *generatedRequest, previousEvent output.InternalEvent, hasInteractMatchers bool, callback protocols.OutputEventCallback, requestCount int) error {
Expand Down Expand Up @@ -538,7 +544,10 @@ func (request *Request) executeRequest(input *contextargs.Context, generatedRequ
}
} else { // Check if are there any unresolved variables. If yes, skip unless overridden by user.
if varErr := expressions.ContainsUnresolvedVariables(dumpedRequestString); varErr != nil && !request.SkipVariablesCheck {
gologger.Warning().Msgf("[%s] Could not make http request for %s: %v\n", request.options.TemplateID, input.MetaInput.Input, varErr)
stats.Increment(elabel.UnresolvedVariablesErrorLabel)
if _, ok := elabel.Contains(varErr.Error(), request.options.Options.ErrorLabels); ok {
gologger.Warning().Msgf("[%s] Could not make http request for %s: %v\n", request.options.TemplateID, input.MetaInput.Input, varErr)
}
return errStopExecution
}
}
Expand Down
8 changes: 7 additions & 1 deletion pkg/protocols/network/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
elabel "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/errors/label"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator"
Expand All @@ -28,6 +29,7 @@ import (
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump"
protocolutils "github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils"
templateTypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/stats"
errorutil "github.com/projectdiscovery/utils/errors"
mapsutil "github.com/projectdiscovery/utils/maps"
"github.com/projectdiscovery/utils/reader"
Expand Down Expand Up @@ -245,7 +247,11 @@ func (request *Request) executeRequestWithPayloads(variables map[string]interfac
reqBuilder.Write(finalData)

if err := expressions.ContainsUnresolvedVariables(string(finalData)); err != nil {
gologger.Warning().Msgf("[%s] Could not make network request for %s: %v\n", request.options.TemplateID, actualAddress, err)
request.options.Progress.IncrementFailedRequestsBy(1)
stats.Increment(elabel.UnresolvedVariablesErrorLabel)
if _, ok := elabel.Contains(err.Error(), request.options.Options.ErrorLabels); ok {
gologger.Warning().Msgf("[%s] Could not make network request for %s: %v\n", request.options.TemplateID, actualAddress, err)
}
return nil
}

Expand Down
9 changes: 8 additions & 1 deletion pkg/tmplexec/generic/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/output"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
elabel "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/errors/label"
"github.com/projectdiscovery/nuclei/v3/pkg/scan"
)

Expand Down Expand Up @@ -79,7 +80,13 @@ func (g *Generic) ExecuteWithResults(ctx *scan.ScanContext) error {
if g.options.HostErrorsCache != nil {
g.options.HostErrorsCache.MarkFailed(ctx.Input.MetaInput.ID(), err)
}
gologger.Warning().Msgf("[%s] Could not execute request for %s: %s\n", g.options.TemplateID, ctx.Input.MetaInput.PrettyPrint(), err)
if _, ok := elabel.Contains(err.Error(), g.options.Options.ErrorLabels); ok {
gologger.Warning().Msgf("[%s] Could not execute request for %s: %s\n", g.options.TemplateID, ctx.Input.MetaInput.PrettyPrint(), err)
}
// TODO: remove logging for otherthan unresolved variables
if _, ok := elabel.Contains(err.Error(), []string{elabel.UnresolvedVariablesErrorLabel}); !ok {
gologger.Warning().Msgf("[%s] Could not execute request for %s: %s\n", g.options.TemplateID, ctx.Input.MetaInput.PrettyPrint(), err)
}
}
// If a match was found and stop at first match is set, break out of the loop and return
if g.results.Load() && (g.options.StopAtFirstMatch || g.options.Options.StopAtFirstMatch) {
Expand Down
2 changes: 2 additions & 0 deletions pkg/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ type Options struct {
TraceLogFile string
// ErrorLogFile specifies a file to write with the errors of all requests
ErrorLogFile string
// ErrorLabels is the list of error labels to match and enable logging
ErrorLabels []string
// ReportingDB is the db for report storage as well as deduplication
ReportingDB string
// ReportingConfig is the config file for nuclei reporting module
Expand Down
Loading