mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2026-11-13 05:41:25 -10:00
Compare commits
59
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afb13e2c5b | ||
|
|
8ea5061f5e | ||
|
|
d2cf69aebb | ||
|
|
39e9286371 | ||
|
|
0ea42e5f66 | ||
|
|
f610ed4cab | ||
|
|
792998d8e2 | ||
|
|
c4fa2c74c1 | ||
|
|
d4f1a815ed | ||
|
|
a65841c034 | ||
|
|
876974f38b | ||
|
|
ca543d7885 | ||
|
|
fde6f72934 | ||
|
|
3af37362e3 | ||
|
|
99a9ce398d | ||
|
|
48af0b4f6c | ||
|
|
e1dfa1baa7 | ||
|
|
089e2a4ee0 | ||
|
|
46555bcd1e | ||
|
|
521a21c06a | ||
|
|
1acd40f97f | ||
|
|
09c2ca540a | ||
|
|
c863143771 | ||
|
|
5c8da8d88b | ||
|
|
4b22a3d53e | ||
|
|
94c77c1a28 | ||
|
|
ee1c847626 | ||
|
|
a21bfc4303 | ||
|
|
c487e59602 | ||
|
|
1f8dc4c358 | ||
|
|
608159bbbe | ||
|
|
b05359bc82 | ||
|
|
4916cf34f0 | ||
|
|
f460bf926d | ||
|
|
218a2f69a5 | ||
|
|
ff5734ba15 | ||
|
|
9c64a1cb9b | ||
|
|
1f0aef970c | ||
|
|
5b7debf349 | ||
|
|
e83382d4e4 | ||
|
|
b61321cd19 | ||
|
|
f20f95f67e | ||
|
|
efcef55681 | ||
|
|
5be258f948 | ||
|
|
6b358b39a3 | ||
|
|
b41f4d97d6 | ||
|
|
5c15c77777 | ||
|
|
d55ab2f827 | ||
|
|
cf8d067fea | ||
|
|
4baf46f080 | ||
|
|
8304462420 | ||
|
|
1f538bcac6 | ||
|
|
937fa1252b | ||
|
|
99914e1a32 | ||
|
|
1a9a7563c0 | ||
|
|
248548e075 | ||
|
|
3eb3f66897 | ||
|
|
5f501da063 | ||
|
|
a0bd3b854e |
No files matched your search
@@ -2,6 +2,7 @@ addReviewers: true
|
||||
reviewers:
|
||||
- dogancanbakir
|
||||
- dwisiswant0
|
||||
- mzack9999
|
||||
|
||||
numberOfReviewers: 1
|
||||
skipKeywords:
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.actor == 'dependabot[bot]'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
token: ${{ secrets.DEPENDABOT_PAT }}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: projectdiscovery/actions/setup/go/compat-checks@v1
|
||||
with:
|
||||
release-test: true
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
if: "${{ !endsWith(github.actor, '[bot]') }}"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/actions/setup/git@v1
|
||||
- run: make syntax-docs
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
LIST_FILE: "/tmp/targets-${{ matrix.targets }}.txt"
|
||||
PROFILE_MEM: "/tmp/nuclei-profile-${{ matrix.targets }}-targets"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: projectdiscovery/actions/setup/git@v1
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- name: Generate list
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
env:
|
||||
OUTPUT: "/tmp/results.sarif"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
- run: govulncheck -scan package -format sarif ./... > $OUTPUT
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
env:
|
||||
BENCH_OUT: "/tmp/bench.out"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- run: make build-test
|
||||
- run: ./bin/nuclei.test -test.run - -test.bench=. -test.benchmem ./cmd/nuclei/ | tee $BENCH_OUT
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
LIST_FILE: "/tmp/targets-${{ matrix.count }}.txt"
|
||||
PROFILE_MEM: "/tmp/nuclei-perf-test-${{ matrix.count }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- run: make verify
|
||||
- name: Generate list
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest-16-cores
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
- uses: actions/stale@v10
|
||||
with:
|
||||
days-before-stale: 90
|
||||
days-before-close: 7
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
if: "${{ !endsWith(github.actor, '[bot]') }}"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/actions/golangci-lint/v2@v1
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
runs-on: "${{ matrix.os }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- run: make vet
|
||||
- run: make build
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
needs: ["tests"]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- name: "Simple"
|
||||
run: go run .
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/actions/setup/python@v1
|
||||
- run: bash run.sh "${{ matrix.os }}"
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/actions/setup/python@v1
|
||||
- run: bash run.sh
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
needs: ["tests"]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- run: make template-validate
|
||||
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
contents: read
|
||||
security-events: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: 'go'
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
needs: ["tests"]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: projectdiscovery/actions/setup/go@v1
|
||||
- uses: projectdiscovery/actions/goreleaser@v1
|
||||
|
||||
@@ -143,7 +143,7 @@ jobs:
|
||||
TARGET_URL: "http://scanme.sh/a/?b=c"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- run: make build
|
||||
- name: "Setup environment (push)"
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
|
||||
@@ -356,6 +356,7 @@ CLOUD:
|
||||
AUTHENTICATION:
|
||||
-sf, -secret-file string[] path to config file containing secrets for nuclei authenticated scan
|
||||
-ps, -prefetch-secrets prefetch secrets from the secrets file
|
||||
# NOTE: Headers in secrets files preserve exact casing (useful for case-sensitive APIs)
|
||||
|
||||
|
||||
EXAMPLES:
|
||||
|
||||
@@ -15,13 +15,17 @@ var jsTestcases = []TestCaseInfo{
|
||||
{Path: "protocols/javascript/ssh-server-fingerprint.yaml", TestCase: &javascriptSSHServerFingerprint{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
|
||||
{Path: "protocols/javascript/net-multi-step.yaml", TestCase: &networkMultiStep{}},
|
||||
{Path: "protocols/javascript/net-https.yaml", TestCase: &javascriptNetHttps{}},
|
||||
{Path: "protocols/javascript/oracle-auth-test.yaml", TestCase: &javascriptOracleAuthTest{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
|
||||
{Path: "protocols/javascript/vnc-pass-brute.yaml", TestCase: &javascriptVncPassBrute{}},
|
||||
}
|
||||
|
||||
var (
|
||||
redisResource *dockertest.Resource
|
||||
sshResource *dockertest.Resource
|
||||
pool *dockertest.Pool
|
||||
defaultRetry = 3
|
||||
redisResource *dockertest.Resource
|
||||
sshResource *dockertest.Resource
|
||||
oracleResource *dockertest.Resource
|
||||
vncResource *dockertest.Resource
|
||||
pool *dockertest.Pool
|
||||
defaultRetry = 3
|
||||
)
|
||||
|
||||
type javascriptNetHttps struct{}
|
||||
@@ -98,6 +102,71 @@ func (j *javascriptSSHServerFingerprint) Execute(filePath string) error {
|
||||
return multierr.Combine(errs...)
|
||||
}
|
||||
|
||||
type javascriptOracleAuthTest struct{}
|
||||
|
||||
func (j *javascriptOracleAuthTest) Execute(filePath string) error {
|
||||
if oracleResource == nil || pool == nil {
|
||||
// skip test as oracle is not running
|
||||
return nil
|
||||
}
|
||||
tempPort := oracleResource.GetPort("1521/tcp")
|
||||
finalURL := "localhost:" + tempPort
|
||||
defer purge(oracleResource)
|
||||
|
||||
errs := []error{}
|
||||
for i := 0; i < defaultRetry; i++ {
|
||||
results := []string{}
|
||||
var err error
|
||||
_ = pool.Retry(func() error {
|
||||
//let ssh server start
|
||||
time.Sleep(3 * time.Second)
|
||||
results, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := expectResultsCount(results, 1); err == nil {
|
||||
return nil
|
||||
} else {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return multierr.Combine(errs...)
|
||||
}
|
||||
|
||||
type javascriptVncPassBrute struct{}
|
||||
|
||||
func (j *javascriptVncPassBrute) Execute(filePath string) error {
|
||||
if vncResource == nil || pool == nil {
|
||||
// skip test as vnc is not running
|
||||
return nil
|
||||
}
|
||||
tempPort := vncResource.GetPort("5900/tcp")
|
||||
finalURL := "localhost:" + tempPort
|
||||
defer purge(vncResource)
|
||||
errs := []error{}
|
||||
for i := 0; i < defaultRetry; i++ {
|
||||
results := []string{}
|
||||
var err error
|
||||
_ = pool.Retry(func() error {
|
||||
//let ssh server start
|
||||
time.Sleep(3 * time.Second)
|
||||
results, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := expectResultsCount(results, 1); err == nil {
|
||||
return nil
|
||||
} else {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return multierr.Combine(errs...)
|
||||
}
|
||||
|
||||
// purge any given resource if it is not nil
|
||||
func purge(resource *dockertest.Resource) {
|
||||
if resource != nil && pool != nil {
|
||||
@@ -163,4 +232,41 @@ func init() {
|
||||
if err := sshResource.Expire(30); err != nil {
|
||||
log.Printf("Could not expire resource: %s", err)
|
||||
}
|
||||
|
||||
// setup a temporary oracle instance
|
||||
oracleResource, err = pool.RunWithOptions(&dockertest.RunOptions{
|
||||
Repository: "gvenzl/oracle-xe",
|
||||
Tag: "latest",
|
||||
Env: []string{
|
||||
"ORACLE_PASSWORD=mysecret",
|
||||
},
|
||||
Platform: "linux/amd64",
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Could not start Oracle resource: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// by default expire after 30 sec
|
||||
if err := oracleResource.Expire(30); err != nil {
|
||||
log.Printf("Could not expire Oracle resource: %s", err)
|
||||
}
|
||||
|
||||
// setup a temporary vnc server
|
||||
vncResource, err = pool.RunWithOptions(&dockertest.RunOptions{
|
||||
Repository: "dorowu/ubuntu-desktop-lxde-vnc",
|
||||
Tag: "latest",
|
||||
Env: []string{
|
||||
"VNC_PASSWORD=mysecret",
|
||||
},
|
||||
Platform: "linux/amd64",
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Could not start resource: %s", err)
|
||||
return
|
||||
}
|
||||
// by default expire after 30 sec
|
||||
if err := vncResource.Expire(30); err != nil {
|
||||
log.Printf("Could not expire resource: %s", err)
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
_ "github.com/projectdiscovery/utils/pprof"
|
||||
stringsutil "github.com/projectdiscovery/utils/strings"
|
||||
"github.com/rs/xid"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/projectdiscovery/goflags"
|
||||
"github.com/projectdiscovery/gologger/levels"
|
||||
@@ -263,6 +264,8 @@ on extensive configurability, massive extensibility and ease of use.`)
|
||||
flagSet.StringVarP(&options.InputFileMode, "input-mode", "im", "list", fmt.Sprintf("mode of input file (%v)", provider.SupportedInputFormats())),
|
||||
flagSet.BoolVarP(&options.FormatUseRequiredOnly, "required-only", "ro", false, "use only required fields in input format when generating requests"),
|
||||
flagSet.BoolVarP(&options.SkipFormatValidation, "skip-format-validation", "sfv", false, "skip format validation (like missing vars) when parsing input file"),
|
||||
flagSet.BoolVarP(&options.VarsTextTemplating, "vars-text-templating", "vtt", false, "enable text templating for vars in input file (only for yaml input mode)"),
|
||||
flagSet.StringSliceVarP(&options.VarsFilePaths, "var-file-paths", "vfp", nil, "list of yaml file contained vars to inject into yaml input", goflags.CommaSeparatedStringSliceOptions),
|
||||
)
|
||||
|
||||
flagSet.CreateGroup("templates", "Templates",
|
||||
@@ -571,6 +574,7 @@ Additional documentation is available at: https://docs.nuclei.sh/getting-started
|
||||
config.DefaultConfig.SetConfigDir(customConfigDir)
|
||||
readFlagsConfig(flagSet)
|
||||
}
|
||||
|
||||
if cfgFile != "" {
|
||||
if !fileutil.FileExists(cfgFile) {
|
||||
options.Logger.Fatal().Msgf("given config file '%s' does not exist", cfgFile)
|
||||
@@ -579,6 +583,41 @@ Additional documentation is available at: https://docs.nuclei.sh/getting-started
|
||||
if err := flagSet.MergeConfigFile(cfgFile); err != nil {
|
||||
options.Logger.Fatal().Msgf("Could not read config: %s\n", err)
|
||||
}
|
||||
|
||||
if !options.Vars.IsEmpty() {
|
||||
// Maybe we should add vars to the config file as well even if they are set via flags?
|
||||
file, err := os.Open(cfgFile)
|
||||
if err != nil {
|
||||
gologger.Fatal().Msgf("Could not open config file: %s\n", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
data := make(map[string]interface{})
|
||||
err = yaml.NewDecoder(file).Decode(&data)
|
||||
if err != nil {
|
||||
gologger.Fatal().Msgf("Could not decode config file: %s\n", err)
|
||||
}
|
||||
|
||||
variables := data["var"]
|
||||
if variables != nil {
|
||||
if varSlice, ok := variables.([]interface{}); ok {
|
||||
for _, value := range varSlice {
|
||||
if strVal, ok := value.(string); ok {
|
||||
err = options.Vars.Set(strVal)
|
||||
if err != nil {
|
||||
gologger.Warning().Msgf("Could not set variable from config file: %s\n", err)
|
||||
}
|
||||
} else {
|
||||
gologger.Warning().Msgf("Skipping non-string variable in config: %#v", value)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
gologger.Warning().Msgf("No 'var' section found in config file: %s", cfgFile)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if options.NewTemplatesDirectory != "" {
|
||||
config.DefaultConfig.SetTemplatesDir(options.NewTemplatesDirectory)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
module github.com/projectdiscovery/nuclei/v3
|
||||
|
||||
go 1.24.1
|
||||
go 1.24.2
|
||||
|
||||
toolchain go1.24.4
|
||||
|
||||
require (
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible
|
||||
github.com/andygrunwald/go-jira v1.16.0
|
||||
github.com/andygrunwald/go-jira v1.16.1
|
||||
github.com/antchfx/htmlquery v1.3.4
|
||||
github.com/bluele/gcache v0.0.2
|
||||
github.com/go-playground/validator/v10 v10.26.0
|
||||
@@ -20,12 +22,12 @@ require (
|
||||
github.com/olekukonko/tablewriter v1.0.8
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/projectdiscovery/clistats v0.1.1
|
||||
github.com/projectdiscovery/fastdialer v0.4.7
|
||||
github.com/projectdiscovery/hmap v0.0.93
|
||||
github.com/projectdiscovery/fastdialer v0.4.11
|
||||
github.com/projectdiscovery/hmap v0.0.94
|
||||
github.com/projectdiscovery/interactsh v1.2.4
|
||||
github.com/projectdiscovery/rawhttp v0.1.90
|
||||
github.com/projectdiscovery/retryabledns v1.0.106
|
||||
github.com/projectdiscovery/retryablehttp-go v1.0.121
|
||||
github.com/projectdiscovery/retryabledns v1.0.107
|
||||
github.com/projectdiscovery/retryablehttp-go v1.0.125
|
||||
github.com/projectdiscovery/yamldoc-go v1.0.6
|
||||
github.com/remeh/sizedwaitgroup v1.0.0
|
||||
github.com/rs/xid v1.6.0
|
||||
@@ -37,24 +39,26 @@ require (
|
||||
github.com/valyala/fasttemplate v1.2.2
|
||||
github.com/weppos/publicsuffix-go v0.50.0
|
||||
go.uber.org/multierr v1.11.0
|
||||
golang.org/x/net v0.43.0
|
||||
golang.org/x/net v0.44.0
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/text v0.28.0
|
||||
golang.org/x/text v0.29.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
code.gitea.io/sdk/gitea v0.21.0
|
||||
carvel.dev/ytt v0.52.0
|
||||
code.gitea.io/sdk/gitea v0.17.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0
|
||||
github.com/DataDog/gostackparse v0.7.0
|
||||
github.com/Masterminds/semver/v3 v3.4.0
|
||||
github.com/Masterminds/semver/v3 v3.2.1
|
||||
github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057
|
||||
github.com/Mzack9999/goja v0.0.0-20250507184235-e46100e9c697
|
||||
github.com/Mzack9999/goja_nodejs v0.0.0-20250507184139-66bcbf65c883
|
||||
github.com/alexsnet/go-vnc v0.1.0
|
||||
github.com/alitto/pond v1.9.2
|
||||
github.com/antchfx/xmlquery v1.4.4
|
||||
github.com/antchfx/xpath v1.3.4
|
||||
github.com/antchfx/xpath v1.3.3
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.5
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.17
|
||||
@@ -87,30 +91,31 @@ require (
|
||||
github.com/microsoft/go-mssqldb v1.9.2
|
||||
github.com/ory/dockertest/v3 v3.12.0
|
||||
github.com/praetorian-inc/fingerprintx v1.1.15
|
||||
github.com/projectdiscovery/dsl v0.5.1
|
||||
github.com/projectdiscovery/dsl v0.7.0
|
||||
github.com/projectdiscovery/fasttemplate v0.0.2
|
||||
github.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c
|
||||
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb
|
||||
github.com/projectdiscovery/goflags v0.1.74
|
||||
github.com/projectdiscovery/gologger v1.1.54
|
||||
github.com/projectdiscovery/gologger v1.1.55
|
||||
github.com/projectdiscovery/gostruct v0.0.2
|
||||
github.com/projectdiscovery/gozero v0.1.0
|
||||
github.com/projectdiscovery/httpx v1.7.1-0.20250901093029-3909667cbaa6
|
||||
github.com/projectdiscovery/httpx v1.7.2-0.20250911192144-fc425deb041a
|
||||
github.com/projectdiscovery/mapcidr v1.1.34
|
||||
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5
|
||||
github.com/projectdiscovery/networkpolicy v0.1.21
|
||||
github.com/projectdiscovery/networkpolicy v0.1.25
|
||||
github.com/projectdiscovery/ratelimit v0.0.82
|
||||
github.com/projectdiscovery/rdap v0.9.0
|
||||
github.com/projectdiscovery/sarif v0.0.1
|
||||
github.com/projectdiscovery/tlsx v1.2.1
|
||||
github.com/projectdiscovery/uncover v1.1.0
|
||||
github.com/projectdiscovery/useragent v0.0.101
|
||||
github.com/projectdiscovery/utils v0.4.24-0.20250823123502-bd7f2849ddb4
|
||||
github.com/projectdiscovery/wappalyzergo v0.2.43
|
||||
github.com/projectdiscovery/utils v0.5.0
|
||||
github.com/projectdiscovery/wappalyzergo v0.2.47
|
||||
github.com/redis/go-redis/v9 v9.11.0
|
||||
github.com/seh-msft/burpxml v1.0.1
|
||||
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466
|
||||
github.com/stretchr/testify v1.11.0
|
||||
github.com/sijms/go-ora/v2 v2.9.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9
|
||||
github.com/testcontainers/testcontainers-go v0.38.0
|
||||
github.com/testcontainers/testcontainers-go/modules/mongodb v0.37.0
|
||||
@@ -118,7 +123,7 @@ require (
|
||||
github.com/zmap/zgrab2 v0.1.8
|
||||
gitlab.com/gitlab-org/api/client-go v0.130.1
|
||||
go.mongodb.org/mongo-driver v1.17.4
|
||||
golang.org/x/term v0.34.0
|
||||
golang.org/x/term v0.35.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
moul.io/http2curl v1.0.0
|
||||
)
|
||||
@@ -128,7 +133,6 @@ require (
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a // indirect
|
||||
github.com/42wim/httpsig v1.2.2 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
@@ -209,7 +213,7 @@ require (
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gaissmai/bart v0.24.0 // indirect
|
||||
github.com/gaissmai/bart v0.25.0 // indirect
|
||||
github.com/geoffgarside/ber v1.1.0 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||
@@ -246,6 +250,7 @@ require (
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf // indirect
|
||||
github.com/hdm/jarm-go v0.0.7 // indirect
|
||||
github.com/iangcarroll/cookiemonster v1.6.0 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/itchyny/timefmt-go v0.1.6 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
@@ -255,6 +260,7 @@ require (
|
||||
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368 // indirect
|
||||
github.com/kataras/jwt v0.1.10 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
@@ -312,7 +318,7 @@ require (
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/projectdiscovery/asnmap v1.1.1 // indirect
|
||||
github.com/projectdiscovery/blackrock v0.0.1 // indirect
|
||||
github.com/projectdiscovery/cdncheck v1.1.33 // indirect
|
||||
github.com/projectdiscovery/cdncheck v1.2.0 // indirect
|
||||
github.com/projectdiscovery/freeport v0.0.7 // indirect
|
||||
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb // indirect
|
||||
github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983 // indirect
|
||||
@@ -338,7 +344,7 @@ require (
|
||||
github.com/tklauser/numcpus v0.10.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
@@ -352,7 +358,7 @@ require (
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
github.com/ysmood/fetchup v0.2.3 // indirect
|
||||
github.com/ysmood/got v0.40.0 // indirect
|
||||
github.com/yuin/goldmark v1.7.8 // indirect
|
||||
github.com/yuin/goldmark v1.7.13 // indirect
|
||||
github.com/yuin/goldmark-emoji v1.0.5 // indirect
|
||||
github.com/zcalusic/sysinfo v1.0.2 // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
@@ -363,7 +369,7 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
gopkg.in/djherbis/times.v1 v1.3.0 // indirect
|
||||
mellium.im/sasl v0.3.2 // indirect
|
||||
)
|
||||
@@ -387,12 +393,12 @@ require (
|
||||
go.etcd.io/bbolt v1.4.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
goftp.io/server/v2 v2.0.1 // indirect
|
||||
golang.org/x/crypto v0.41.0 // indirect
|
||||
golang.org/x/crypto v0.42.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b
|
||||
golang.org/x/mod v0.26.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.35.0
|
||||
golang.org/x/tools v0.36.0
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
|
||||
gopkg.in/corvus-ch/zbase32.v1 v1.0.0 // indirect
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk=
|
||||
aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ=
|
||||
carvel.dev/ytt v0.52.0 h1:tkJPL8Gun5snVfypNXbmMKwnbwMyspcTi3Ypyso3nRY=
|
||||
carvel.dev/ytt v0.52.0/go.mod h1:QgmuU7E15EXW1r2wxTt7zExVz14IHwEG4WNMmaFBkJo=
|
||||
cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
@@ -270,8 +272,8 @@ cloud.google.com/go/vpcaccess v1.7.11/go.mod h1:a2cuAiSCI4TVK0Dt6/dRjf22qQvfY+po
|
||||
cloud.google.com/go/webrisk v1.9.11/go.mod h1:mK6M8KEO0ZI7VkrjCq3Tjzw4vYq+3c4DzlMUDVaiswE=
|
||||
cloud.google.com/go/websecurityscanner v1.6.11/go.mod h1:vhAZjksELSg58EZfUQ1BMExD+hxqpn0G0DuyCZQjiTg=
|
||||
cloud.google.com/go/workflows v1.12.10/go.mod h1:RcKqCiOmKs8wFUEf3EwWZPH5eHc7Oq0kamIyOUCk0IE=
|
||||
code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4=
|
||||
code.gitea.io/sdk/gitea v0.21.0/go.mod h1:tnBjVhuKJCn8ibdyyhvUyxrR1Ca2KHEoTWoukNhXQPA=
|
||||
code.gitea.io/sdk/gitea v0.17.0 h1:8JPBss4+Jf7AE1YcfyiGrngTXE8dFSG3si/bypsTH34=
|
||||
code.gitea.io/sdk/gitea v0.17.0/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
@@ -281,8 +283,6 @@ gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zum
|
||||
git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a h1:3i+FJ7IpSZHL+VAjtpQeZCRhrpP0odl5XfoLBY4fxJ8=
|
||||
git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a/go.mod h1:C7hXLmFmPYPjIDGfQl1clsmQ5TMEQfmzWTrJk475bUs=
|
||||
git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
|
||||
github.com/42wim/httpsig v1.2.2 h1:ofAYoHUNs/MJOLqQ8hIxeyz2QxOz8qdSVvp3PX/oPgA=
|
||||
github.com/42wim/httpsig v1.2.2/go.mod h1:P/UYo7ytNBFwc+dg35IubuAUIs8zj5zzFIgUCEl55WY=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
|
||||
@@ -293,14 +293,14 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.0 h1:LR0kAX9ykz8G4YgLCaRDVJ3+n43R8MneB5dTy2konZo=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.0/go.mod h1:DWAciXemNf++PQJLeXUB4HHH5OpsAh12HZnu2wXE1jA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0 h1:Ma67P/GGprNwsslzEH6+Kb8nybI8jpDTm4Wmzu2ReK8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0/go.mod h1:c+Lifp3EDEamAkPVzMooRNOK6CZjNSdEnf1A7jsI9u4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 h1:Wgf5rZba3YZqeTNJPtvqZoBu1sBN/L4sry+u2U3Y75w=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1/go.mod h1:xxCBG/f/4Vbmh2XQJBsOmNdxWUY5j/s27jujKPbQf14=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1 h1:lhZdRq7TIx0GJQvSyX2Si406vrYsov2FXGp/RnSEtcs=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1/go.mod h1:8cl44BDmi+effbARHMQjgOKA2AYvcohNm7KEt42mSV8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 h1:nVocQV40OQne5613EeLayJiRAJuKlBGy+m22qWG+WRg=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0/go.mod h1:7QJP7dr2wznCMeqIrhMgWGf7XpAQnVrJqDm9nvV3Cu4=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
|
||||
@@ -310,6 +310,8 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mo
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4=
|
||||
github.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
|
||||
@@ -317,8 +319,8 @@ github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0/go.mod h1:dppbR7CwXD4p
|
||||
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
|
||||
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
@@ -376,6 +378,8 @@ github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAu
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
|
||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
|
||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/alexsnet/go-vnc v0.1.0 h1:vBCwPNy79WEL8V/Z5A0ngEFCvTWBAjmS048lkR2rdmY=
|
||||
github.com/alexsnet/go-vnc v0.1.0/go.mod h1:bbRsg41Sh3zvrnWsw+REKJVGZd8Of2+S0V1G0ZaBhlU=
|
||||
github.com/alitto/pond v1.9.2 h1:9Qb75z/scEZVCoSU+osVmQ0I0JOeLfdTDafrbcJ8CLs=
|
||||
github.com/alitto/pond v1.9.2/go.mod h1:xQn3P/sHTYcU/1BR3i86IGIrilcrGC2LiS+E2+CJWsI=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
@@ -383,17 +387,16 @@ github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 h1:8PmGpDEZl9
|
||||
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/andygrunwald/go-jira v1.16.0 h1:PU7C7Fkk5L96JvPc6vDVIrd99vdPnYudHu4ju2c2ikQ=
|
||||
github.com/andygrunwald/go-jira v1.16.0/go.mod h1:UQH4IBVxIYWbgagc0LF/k9FRs9xjIiQ8hIcC6HfLwFU=
|
||||
github.com/andygrunwald/go-jira v1.16.1 h1:WoQEar5XoDRAibOgKzTFELlPNlKAtnfWr296R9zdFLA=
|
||||
github.com/andygrunwald/go-jira v1.16.1/go.mod h1:UQH4IBVxIYWbgagc0LF/k9FRs9xjIiQ8hIcC6HfLwFU=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/antchfx/htmlquery v1.3.4 h1:Isd0srPkni2iNTWCwVj/72t7uCphFeor5Q8nCzj1jdQ=
|
||||
github.com/antchfx/htmlquery v1.3.4/go.mod h1:K9os0BwIEmLAvTqaNSua8tXLWRWZpocZIH73OzWQbwM=
|
||||
github.com/antchfx/xmlquery v1.4.4 h1:mxMEkdYP3pjKSftxss4nUHfjBhnMk4imGoR96FRY2dg=
|
||||
github.com/antchfx/xmlquery v1.4.4/go.mod h1:AEPEEPYE9GnA2mj5Ur2L5Q5/2PycJ0N9Fusrx9b12fc=
|
||||
github.com/antchfx/xpath v1.3.3 h1:tmuPQa1Uye0Ym1Zn65vxPgfltWb/Lxu2jeqIGteJSRs=
|
||||
github.com/antchfx/xpath v1.3.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
|
||||
github.com/antchfx/xpath v1.3.4 h1:1ixrW1VnXd4HurCj7qnqnR0jo14g8JMe20Fshg1Vgz4=
|
||||
github.com/antchfx/xpath v1.3.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA=
|
||||
github.com/apache/thrift v0.17.0/go.mod h1:OLxhMRJxomX+1I/KUw03qoV3mMz16BwaKI+d4fPBx7Q=
|
||||
@@ -654,8 +657,8 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gaissmai/bart v0.24.0 h1:HOq5aXDBa4d376KkuxD+xnS9DQWWJtD4zgDNoGV0KrQ=
|
||||
github.com/gaissmai/bart v0.24.0/go.mod h1:RpLtt3lWq1BoRz3AAyDAJ7jhLWBkYhVCfi+ximB2t68=
|
||||
github.com/gaissmai/bart v0.25.0 h1:eqiokVPqM3F94vJ0bTHXHtH91S8zkKL+bKh+BsGOsJM=
|
||||
github.com/gaissmai/bart v0.25.0/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c=
|
||||
github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w=
|
||||
github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc=
|
||||
github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk=
|
||||
@@ -849,6 +852,8 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
@@ -952,6 +957,7 @@ github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
||||
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
@@ -967,6 +973,8 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/iangcarroll/cookiemonster v1.6.0 h1:NPFkn/ZZYZgzXhJ1awRnYhZ3fJK3hKWgbctfTW21kew=
|
||||
github.com/iangcarroll/cookiemonster v1.6.0/go.mod h1:n3MvoAq56NkNyCEyhcYs3ZJMzTc9rL3w7IaITI0apMg=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||
@@ -1018,6 +1026,10 @@ github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4d
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/k14s/difflib v0.0.0-20201117154628-0c031775bf57 h1:CwBRArr+BWBopnUJhDjJw86rPL/jGbEjfHWKzTasSqE=
|
||||
github.com/k14s/difflib v0.0.0-20201117154628-0c031775bf57/go.mod h1:B0xN2MiNBGWOWi9CcfAo9LBI8IU4J1utlbOIJCsmKr4=
|
||||
github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368 h1:4bcRTTSx+LKSxMWibIwzHnDNmaN1x52oEpvnjCy+8vk=
|
||||
github.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368/go.mod h1:lKGj1op99m4GtQISxoD2t+K+WO/q2NzEPKvfXFQfbCA=
|
||||
github.com/kataras/jwt v0.1.10 h1:GBXOF9RVInDPhCFBiDumRG9Tt27l7ugLeLo8HL5SeKQ=
|
||||
github.com/kataras/jwt v0.1.10/go.mod h1:xkimAtDhU/aGlQqjwvgtg+VyuPwMiyZHaY8LJRh0mYo=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
@@ -1237,14 +1249,14 @@ github.com/projectdiscovery/asnmap v1.1.1 h1:ImJiKIaACOT7HPx4Pabb5dksolzaFYsD1kI
|
||||
github.com/projectdiscovery/asnmap v1.1.1/go.mod h1:QT7jt9nQanj+Ucjr9BqGr1Q2veCCKSAVyUzLXfEcQ60=
|
||||
github.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k0VbGJyft6LQ=
|
||||
github.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss=
|
||||
github.com/projectdiscovery/cdncheck v1.1.33 h1:xQYq+ms0yaLaiK1BbfNc9pKsrnYVLhwRMF4yJWNfMn4=
|
||||
github.com/projectdiscovery/cdncheck v1.1.33/go.mod h1:dFEGsG0qAJY0AaRr2N1BY0OtZiTxS4kYeT5+OkF8t1U=
|
||||
github.com/projectdiscovery/cdncheck v1.2.0 h1:KzBk+U7w7vQ5y//pCfynQq8qxnFbf2XFmjGltmPJogs=
|
||||
github.com/projectdiscovery/cdncheck v1.2.0/go.mod h1:dSyW7D7ItsBp9dRoBh2JrL/s16lZkToUN2XvODNSGjA=
|
||||
github.com/projectdiscovery/clistats v0.1.1 h1:8mwbdbwTU4aT88TJvwIzTpiNeow3XnAB72JIg66c8wE=
|
||||
github.com/projectdiscovery/clistats v0.1.1/go.mod h1:4LtTC9Oy//RiuT1+76MfTg8Hqs7FQp1JIGBM3nHK6a0=
|
||||
github.com/projectdiscovery/dsl v0.5.1 h1:Nm6ToWtNKrhrF880qWykbub60Lw4m96ZkB43OGOsVLg=
|
||||
github.com/projectdiscovery/dsl v0.5.1/go.mod h1:m+tLImvmlEnFTuwb0Bm3DqCL6e7sWOunJyxFi27AduI=
|
||||
github.com/projectdiscovery/fastdialer v0.4.7 h1:fErLAppGZtZMGLLyeInMh4Kw/N0FXlTVh35v07dpS7I=
|
||||
github.com/projectdiscovery/fastdialer v0.4.7/go.mod h1:0Nm8GgU3Ds2Ac3/Fg59mVG40eMFYNA426PAcYqeNioM=
|
||||
github.com/projectdiscovery/dsl v0.7.0 h1:tfZcsVCoujXvJq2AtplMUWdSvYcQt81uDcNCfHzTLC8=
|
||||
github.com/projectdiscovery/dsl v0.7.0/go.mod h1:tRkJQglLwBjaH9Z5wcf/zZOafo+G1TyrhKV6CtQnD4k=
|
||||
github.com/projectdiscovery/fastdialer v0.4.11 h1:6JYoORInQiENFPTqGMI+1GaL5hOTD/sjWrSbzM8d+Z0=
|
||||
github.com/projectdiscovery/fastdialer v0.4.11/go.mod h1:i2dB26BHw0BMrx7ccbHcb/1LE5q9gy0ojvBytATl38E=
|
||||
github.com/projectdiscovery/fasttemplate v0.0.2 h1:h2cISk5xDhlJEinlBQS6RRx0vOlOirB2y3Yu4PJzpiA=
|
||||
github.com/projectdiscovery/fasttemplate v0.0.2/go.mod h1:XYWWVMxnItd+r0GbjA1GCsUopMw1/XusuQxdyAIHMCw=
|
||||
github.com/projectdiscovery/freeport v0.0.7 h1:Q6uXo/j8SaV/GlAHkEYQi8WQoPXyJWxyspx+aFmz9Qk=
|
||||
@@ -1255,16 +1267,16 @@ github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb h1:rutG90
|
||||
github.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb/go.mod h1:FLjF1DmZ+POoGEiIQdWuYVwS++C/GwpX8YaCsTSm1RY=
|
||||
github.com/projectdiscovery/goflags v0.1.74 h1:n85uTRj5qMosm0PFBfsvOL24I7TdWRcWq/1GynhXS7c=
|
||||
github.com/projectdiscovery/goflags v0.1.74/go.mod h1:UMc9/7dFz2oln+10tv6cy+7WZKTHf9UGhaNkF95emh4=
|
||||
github.com/projectdiscovery/gologger v1.1.54 h1:WMzvJ8j/4gGfPKpCttSTaYCVDU1MWQSJnk3wU8/U6Ws=
|
||||
github.com/projectdiscovery/gologger v1.1.54/go.mod h1:vza/8pe2OKOt+ujFWncngknad1XWr8EnLKlbcejOyUE=
|
||||
github.com/projectdiscovery/gologger v1.1.55 h1:ASHPXrDyyvFSRMpWXK/UptujU/KwDGgVGbn9BhF8azo=
|
||||
github.com/projectdiscovery/gologger v1.1.55/go.mod h1:RtAcBG7KzTWmAEVEVWiBbKGKsYpqzxKXzn3yhAFkfic=
|
||||
github.com/projectdiscovery/gostruct v0.0.2 h1:s8gP8ApugGM4go1pA+sVlPDXaWqNP5BBDDSv7VEdG1M=
|
||||
github.com/projectdiscovery/gostruct v0.0.2/go.mod h1:H86peL4HKwMXcQQtEa6lmC8FuD9XFt6gkNR0B/Mu5PE=
|
||||
github.com/projectdiscovery/gozero v0.1.0 h1:QC+WPEsEVFtPmAm9FiIVT/obv9rF/pS2mnag8zXIAQI=
|
||||
github.com/projectdiscovery/gozero v0.1.0/go.mod h1:gJUNa8eQgMxLaa0UiLChPTV71/BLLrlPAaUp1C2mrhs=
|
||||
github.com/projectdiscovery/hmap v0.0.93 h1:iIRdioT4byGJ4Hz5cOjo1fd3HFFi1MUFPv+EGYc1yng=
|
||||
github.com/projectdiscovery/hmap v0.0.93/go.mod h1:oKgtWo2QMD7BkW25ezYbFCqKO3IctZ2ByEaG2XWW0t0=
|
||||
github.com/projectdiscovery/httpx v1.7.1-0.20250901093029-3909667cbaa6 h1:LrO3ryyJikuyZmHk/vWlQGnio65gVIqkgQK1+2L80+Q=
|
||||
github.com/projectdiscovery/httpx v1.7.1-0.20250901093029-3909667cbaa6/go.mod h1:lV4GZJb6TArwq/VT+AdZbysG+Ukneyfr9cXsYTrLbbE=
|
||||
github.com/projectdiscovery/hmap v0.0.94 h1:Q2/89U2rkDVz59j/WBzaRPmpRrYamDWnzQgauk+K03E=
|
||||
github.com/projectdiscovery/hmap v0.0.94/go.mod h1:QtqZlGUwq3/SNiwEfArayfwrZU4g0/tpw6lJlxP1XEI=
|
||||
github.com/projectdiscovery/httpx v1.7.2-0.20250911192144-fc425deb041a h1:5NBp4BegAQuT3QSnbBKt05LH1nOyEeFAXYh1+aE3Nlo=
|
||||
github.com/projectdiscovery/httpx v1.7.2-0.20250911192144-fc425deb041a/go.mod h1:SQl92RiEuBnv1QQ8aQLC3b1lfgGHttoqUV0cTTvlzxQ=
|
||||
github.com/projectdiscovery/interactsh v1.2.4 h1:WUSj+fxbcV53J64oIAhbYzCKD1w/IyenyRBhkI5jiqI=
|
||||
github.com/projectdiscovery/interactsh v1.2.4/go.mod h1:E/IVNZ80/WKz8zTwGJWQygxIbhlRmuzZFsZwcGSZTdc=
|
||||
github.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb h1:MGtI4oE12ruWv11ZlPXXd7hl/uAaQZrFvrIDYDeVMd8=
|
||||
@@ -1275,18 +1287,18 @@ github.com/projectdiscovery/mapcidr v1.1.34 h1:udr83vQ7oz3kEOwlsU6NC6o08leJzSDQt
|
||||
github.com/projectdiscovery/mapcidr v1.1.34/go.mod h1:1+1R6OkKSAKtWDXE9RvxXtXPoajXTYX0eiEdkqlhQqQ=
|
||||
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5 h1:L/e8z8yw1pfT6bg35NiN7yd1XKtJap5Nk6lMwQ0RNi8=
|
||||
github.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5/go.mod h1:pGW2ncnTxTxHtP9wzcIJAB+3/NMp6IiuQWd2NK7K+oc=
|
||||
github.com/projectdiscovery/networkpolicy v0.1.21 h1:BOlYxPyrfqb5DneyuokZkRpeX5MCXLjCH4sI2hLudII=
|
||||
github.com/projectdiscovery/networkpolicy v0.1.21/go.mod h1:/DI9DZANBQ20x09mvcHA0J/3dCLBgCkEW5r5/L1b87U=
|
||||
github.com/projectdiscovery/networkpolicy v0.1.25 h1:2f3Nxp4ncz/95hm6PkIDLcjMJI6ei2YsgkQ29NZn7b0=
|
||||
github.com/projectdiscovery/networkpolicy v0.1.25/go.mod h1:6zQPoZRyGeNq0WvWnL5cbah3mCg9KJiOn5tgVQWGIn4=
|
||||
github.com/projectdiscovery/ratelimit v0.0.82 h1:rtO5SQf5uQFu5zTahTaTcO06OxmG8EIF1qhdFPIyTak=
|
||||
github.com/projectdiscovery/ratelimit v0.0.82/go.mod h1:z076BrLkBb5yS7uhHNoCTf8X/BvFSGRxwQ8EzEL9afM=
|
||||
github.com/projectdiscovery/rawhttp v0.1.90 h1:LOSZ6PUH08tnKmWsIwvwv1Z/4zkiYKYOSZ6n+8RFKtw=
|
||||
github.com/projectdiscovery/rawhttp v0.1.90/go.mod h1:VZYAM25UI/wVB3URZ95ZaftgOnsbphxyAw/XnQRRz4Y=
|
||||
github.com/projectdiscovery/rdap v0.9.0 h1:wPhHx5pQ2QI+WGhyNb2PjhTl0NtB39Nk7YFZ9cp8ZGA=
|
||||
github.com/projectdiscovery/rdap v0.9.0/go.mod h1:zk4yrJFQ2Hy36Aqk+DvotYQxYAeALaCJ5ORySkff36Q=
|
||||
github.com/projectdiscovery/retryabledns v1.0.106 h1:repm5aGq5ge6fNJQbPNux7vyUhtUju7aIMEXZD9FX0A=
|
||||
github.com/projectdiscovery/retryabledns v1.0.106/go.mod h1:1W3ogENvt3Pb+Ju4AN0aE2lZy+D76Yzxsz51DqFCIk8=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.0.121 h1:KQcMS7z5k3xnrTnD5G+HOVp1YRIbaB/iA+moc2lkR4c=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.0.121/go.mod h1:15Usp6eLzNM/mI/jd+ucYRiyUuvm7vg2ymWRylG/Zss=
|
||||
github.com/projectdiscovery/retryabledns v1.0.107 h1:Rd1JK7hfL68xRg3RzXmw9Q6B6WSNv9bdPhUoGvvebyg=
|
||||
github.com/projectdiscovery/retryabledns v1.0.107/go.mod h1:XddeOhDpwS0qR8/T4GXRGgDQsLhtRHw3TRdMsgJs28o=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.0.125 h1:/00K0xHXIRLn2awpn6RD66rzBUyGfweHmtrGeDyKWok=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.0.125/go.mod h1:69TkYApUaekghXT1TJxiwKLdEY1xO+H7RCDsHx5eKWg=
|
||||
github.com/projectdiscovery/sarif v0.0.1 h1:C2Tyj0SGOKbCLgHrx83vaE6YkzXEVrMXYRGLkKCr/us=
|
||||
github.com/projectdiscovery/sarif v0.0.1/go.mod h1:cEYlDu8amcPf6b9dSakcz2nNnJsoz4aR6peERwV+wuQ=
|
||||
github.com/projectdiscovery/stringsutil v0.0.2 h1:uzmw3IVLJSMW1kEg8eCStG/cGbYYZAja8BH3LqqJXMA=
|
||||
@@ -1297,10 +1309,10 @@ github.com/projectdiscovery/uncover v1.1.0 h1:UDp/qLZn78YZb6VPoOrfyP1vz+ojEx8VrT
|
||||
github.com/projectdiscovery/uncover v1.1.0/go.mod h1:2rXINmMe/lmVAt2jn9CpAOs9An57/JEeLZobY3Z9kUs=
|
||||
github.com/projectdiscovery/useragent v0.0.101 h1:8A+XOJ/nIH+WqW8ogLxJ/psemGp8ATQ2/GuKroJ/81E=
|
||||
github.com/projectdiscovery/useragent v0.0.101/go.mod h1:RGoRw1BQ/lJnhYMbMpEKjyAAgCaDCr/+GsULo5yEJ2I=
|
||||
github.com/projectdiscovery/utils v0.4.24-0.20250823123502-bd7f2849ddb4 h1:qQMEhfxDsiZ+Ay3dj93FuMAa7yt1XE2bxDpPSTEz/P0=
|
||||
github.com/projectdiscovery/utils v0.4.24-0.20250823123502-bd7f2849ddb4/go.mod h1:ipzU2PHYP71MaMn4jllPOpdYpdMkFD0jE3Tjak4b4eM=
|
||||
github.com/projectdiscovery/wappalyzergo v0.2.43 h1:gmjBJv2xHFBl48B0yFe2OL6v+mXe4KMLNdEmuMpqfhk=
|
||||
github.com/projectdiscovery/wappalyzergo v0.2.43/go.mod h1:N2ZzJYLz1FEyLO4oIL0scz8Z/r2eRRylKFrt3Zy25tc=
|
||||
github.com/projectdiscovery/utils v0.5.0 h1:DN7mg2DpyObLByuObXzAFEkdNRDoPUnqE5N2szd3b3c=
|
||||
github.com/projectdiscovery/utils v0.5.0/go.mod h1:eCAWMmyaNxyPWbiKv1oeYJLIKpxceHE2+NWx3Jodhqk=
|
||||
github.com/projectdiscovery/wappalyzergo v0.2.47 h1:8cqpgVfby3AlnYDDDFUkuEZRvYCmVZvrKLxSnf09j1Q=
|
||||
github.com/projectdiscovery/wappalyzergo v0.2.47/go.mod h1:kPzB+L3ISGO5m8u5fukjlF5FJFJ1NLV86emOx+k3AVU=
|
||||
github.com/projectdiscovery/yamldoc-go v1.0.6 h1:GCEdIRlQjDux28xTXKszM7n3jlMf152d5nqVpVoetas=
|
||||
github.com/projectdiscovery/yamldoc-go v1.0.6/go.mod h1:R5lWrNzP+7Oyn77NDVPnBsxx2/FyQZBBkIAaSaCQFxw=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
@@ -1373,6 +1385,8 @@ github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0=
|
||||
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE=
|
||||
github.com/sijms/go-ora/v2 v2.9.0 h1:+iQbUeTeCOFMb5BsOMgUhV8KWyrv9yjKpcK4x7+MFrg=
|
||||
github.com/sijms/go-ora/v2 v2.9.0/go.mod h1:QgFInVi3ZWyqAiJwzBQA+nbKYKH77tdp1PYoCqhR2dU=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
@@ -1419,8 +1433,8 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
|
||||
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/substrait-io/substrait-go v0.4.2/go.mod h1:qhpnLmrcvAnlZsUyPXZRqldiHapPTXC3t7xFgDi3aQg=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
@@ -1469,8 +1483,8 @@ github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
||||
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
@@ -1535,8 +1549,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
|
||||
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk=
|
||||
github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
@@ -1659,6 +1673,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
@@ -1681,8 +1696,8 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -1750,8 +1765,8 @@ golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -1834,8 +1849,8 @@ golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1901,8 +1916,8 @@ golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1919,6 +1934,7 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -2017,8 +2033,8 @@ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
@@ -2048,8 +2064,8 @@ golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -2074,8 +2090,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -2153,8 +2169,8 @@ golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58
|
||||
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
id: fuzz-body
|
||||
|
||||
info:
|
||||
name: fuzzing error sqli payloads in http req body
|
||||
author: pdteam
|
||||
severity: info
|
||||
description: |
|
||||
This template attempts to find SQL injection vulnerabilities by fuzzing http body
|
||||
It automatically handles and parses json,xml,multipart form and x-www-form-urlencoded data
|
||||
and performs fuzzing on the value of every key
|
||||
|
||||
http:
|
||||
- pre-condition:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- method != "GET"
|
||||
- method != "HEAD"
|
||||
condition: and
|
||||
|
||||
payloads:
|
||||
injection:
|
||||
- "'"
|
||||
- "\""
|
||||
- ";"
|
||||
|
||||
fuzzing:
|
||||
- part: body
|
||||
type: postfix
|
||||
mode: single
|
||||
fuzz:
|
||||
- '{{injection}}'
|
||||
|
||||
stop-at-first-match: true
|
||||
matchers:
|
||||
- type: word
|
||||
words:
|
||||
- "unrecognized token:"
|
||||
- "null"
|
||||
@@ -0,0 +1,38 @@
|
||||
id: vnc-password-test
|
||||
|
||||
info:
|
||||
name: VNC Password Authentication Test
|
||||
author: pdteam
|
||||
severity: high
|
||||
description: |
|
||||
Tests VNC authentication with correct and incorrect passwords.
|
||||
metadata:
|
||||
shodan-query: product:"vnc"
|
||||
tags: js,network,vnc,authentication
|
||||
|
||||
javascript:
|
||||
- pre-condition: |
|
||||
isPortOpen(Host,Port)
|
||||
|
||||
code: |
|
||||
let vnc = require('nuclei/vnc');
|
||||
let client = new vnc.VNCClient();
|
||||
client.Connect(Host, Port, Password);
|
||||
|
||||
args:
|
||||
Host: "{{Host}}"
|
||||
Port: "5900"
|
||||
Password: "{{passwords}}"
|
||||
payloads:
|
||||
passwords:
|
||||
- ""
|
||||
- root
|
||||
- password
|
||||
- admin
|
||||
- mysecret
|
||||
stop-at-first-match: true
|
||||
|
||||
matchers:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- "success == true"
|
||||
@@ -384,11 +384,7 @@ func New(options *types.Options) (*Runner, error) {
|
||||
if options.RateLimit > 0 && options.RateLimitDuration == 0 {
|
||||
options.RateLimitDuration = time.Second
|
||||
}
|
||||
if options.RateLimit == 0 && options.RateLimitDuration == 0 {
|
||||
runner.rateLimiter = ratelimit.NewUnlimited(context.Background())
|
||||
} else {
|
||||
runner.rateLimiter = ratelimit.New(context.Background(), uint(options.RateLimit), options.RateLimitDuration)
|
||||
}
|
||||
runner.rateLimiter = utils.GetRateLimiter(context.Background(), options.RateLimit, options.RateLimitDuration)
|
||||
|
||||
if tmpDir, err := os.MkdirTemp("", "nuclei-tmp-*"); err == nil {
|
||||
runner.tmpDir = tmpDir
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/projectdiscovery/goflags"
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/ratelimit"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
|
||||
"github.com/projectdiscovery/utils/errkit"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/authprovider"
|
||||
@@ -181,7 +181,7 @@ func WithGlobalRateLimitCtx(ctx context.Context, maxTokens int, duration time.Du
|
||||
return func(e *NucleiEngine) error {
|
||||
e.opts.RateLimit = maxTokens
|
||||
e.opts.RateLimitDuration = duration
|
||||
e.rateLimiter = ratelimit.New(ctx, uint(e.opts.RateLimit), e.opts.RateLimitDuration)
|
||||
e.rateLimiter = utils.GetRateLimiter(ctx, e.opts.RateLimit, e.opts.RateLimitDuration)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
+2
-6
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
"github.com/projectdiscovery/ratelimit"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
|
||||
"github.com/projectdiscovery/utils/errkit"
|
||||
"github.com/rs/xid"
|
||||
)
|
||||
@@ -53,11 +53,7 @@ func createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types
|
||||
if opts.RateLimit > 0 && opts.RateLimitDuration == 0 {
|
||||
opts.RateLimitDuration = time.Second
|
||||
}
|
||||
if opts.RateLimit == 0 && opts.RateLimitDuration == 0 {
|
||||
u.executerOpts.RateLimiter = ratelimit.NewUnlimited(ctx)
|
||||
} else {
|
||||
u.executerOpts.RateLimiter = ratelimit.New(ctx, uint(opts.RateLimit), opts.RateLimitDuration)
|
||||
}
|
||||
u.executerOpts.RateLimiter = utils.GetRateLimiter(ctx, opts.RateLimit, opts.RateLimitDuration)
|
||||
u.engine = core.New(opts)
|
||||
u.engine.SetExecuterOptions(u.executerOpts)
|
||||
return u, nil
|
||||
|
||||
@@ -3,7 +3,7 @@ package authx
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/replacer"
|
||||
@@ -30,8 +30,8 @@ type Dynamic struct {
|
||||
Input string `json:"input" yaml:"input"` // (optional) target for the dynamic secret
|
||||
Extracted map[string]interface{} `json:"-" yaml:"-"` // extracted values from the dynamic secret
|
||||
fetchCallback LazyFetchSecret `json:"-" yaml:"-"`
|
||||
m *sync.Mutex `json:"-" yaml:"-"` // mutex for lazy fetch
|
||||
fetched bool `json:"-" yaml:"-"` // flag to check if the secret has been fetched
|
||||
fetched *atomic.Bool `json:"-" yaml:"-"` // atomic flag to check if the secret has been fetched
|
||||
fetching *atomic.Bool `json:"-" yaml:"-"` // atomic flag to prevent recursive fetch calls
|
||||
error error `json:"-" yaml:"-"` // error if any
|
||||
}
|
||||
|
||||
@@ -70,7 +70,8 @@ func (d *Dynamic) UnmarshalJSON(data []byte) error {
|
||||
|
||||
// Validate validates the dynamic secret
|
||||
func (d *Dynamic) Validate() error {
|
||||
d.m = &sync.Mutex{}
|
||||
d.fetched = &atomic.Bool{}
|
||||
d.fetching = &atomic.Bool{}
|
||||
if d.TemplatePath == "" {
|
||||
return errkit.New(" template-path is required for dynamic secret")
|
||||
}
|
||||
@@ -97,9 +98,7 @@ func (d *Dynamic) Validate() error {
|
||||
func (d *Dynamic) SetLazyFetchCallback(callback LazyFetchSecret) {
|
||||
d.fetchCallback = func(d *Dynamic) error {
|
||||
err := callback(d)
|
||||
d.fetched = true
|
||||
if err != nil {
|
||||
d.error = err
|
||||
return err
|
||||
}
|
||||
if len(d.Extracted) == 0 {
|
||||
@@ -184,9 +183,15 @@ func (d *Dynamic) applyValuesToSecret(secret *Secret) error {
|
||||
|
||||
// GetStrategy returns the auth strategies for the dynamic secret
|
||||
func (d *Dynamic) GetStrategies() []AuthStrategy {
|
||||
if !d.fetched {
|
||||
if d.fetched.Load() {
|
||||
if d.error != nil {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
// Try to fetch if not already fetched
|
||||
_ = d.Fetch(true)
|
||||
}
|
||||
|
||||
if d.error != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -203,12 +208,23 @@ func (d *Dynamic) GetStrategies() []AuthStrategy {
|
||||
// Fetch fetches the dynamic secret
|
||||
// if isFatal is true, it will stop the execution if the secret could not be fetched
|
||||
func (d *Dynamic) Fetch(isFatal bool) error {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
if d.fetched {
|
||||
return nil
|
||||
if d.fetched.Load() {
|
||||
return d.error
|
||||
}
|
||||
|
||||
// Try to set fetching flag atomically
|
||||
if !d.fetching.CompareAndSwap(false, true) {
|
||||
// Already fetching, return current error
|
||||
return d.error
|
||||
}
|
||||
|
||||
// We're the only one fetching, call the callback
|
||||
d.error = d.fetchCallback(d)
|
||||
|
||||
// Mark as fetched and clear fetching flag
|
||||
d.fetched.Store(true)
|
||||
d.fetching.Store(false)
|
||||
|
||||
if d.error != nil && isFatal {
|
||||
gologger.Fatal().Msgf("Could not fetch dynamic secret: %s\n", d.error)
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ type Secret struct {
|
||||
Type string `json:"type" yaml:"type"`
|
||||
Domains []string `json:"domains" yaml:"domains"`
|
||||
DomainsRegex []string `json:"domains-regex" yaml:"domains-regex"`
|
||||
Headers []KV `json:"headers" yaml:"headers"`
|
||||
Headers []KV `json:"headers" yaml:"headers"` // Headers preserve exact casing (useful for case-sensitive APIs)
|
||||
Cookies []Cookie `json:"cookies" yaml:"cookies"`
|
||||
Params []KV `json:"params" yaml:"params"`
|
||||
Username string `json:"username" yaml:"username"` // can be either email or username
|
||||
@@ -148,7 +148,7 @@ func (s *Secret) Validate() error {
|
||||
}
|
||||
|
||||
type KV struct {
|
||||
Key string `json:"key" yaml:"key"`
|
||||
Key string `json:"key" yaml:"key"` // Header key (preserves exact casing)
|
||||
Value string `json:"value" yaml:"value"`
|
||||
}
|
||||
|
||||
|
||||
@@ -21,15 +21,19 @@ func NewHeadersAuthStrategy(data *Secret) *HeadersAuthStrategy {
|
||||
}
|
||||
|
||||
// Apply applies the headers auth strategy to the request
|
||||
// NOTE: This preserves exact header casing (e.g., barAuthToken stays as barAuthToken)
|
||||
// This is useful for APIs that require case-sensitive header names
|
||||
func (s *HeadersAuthStrategy) Apply(req *http.Request) {
|
||||
for _, header := range s.Data.Headers {
|
||||
req.Header.Set(header.Key, header.Value)
|
||||
req.Header[header.Key] = []string{header.Value}
|
||||
}
|
||||
}
|
||||
|
||||
// ApplyOnRR applies the headers auth strategy to the retryable request
|
||||
// NOTE: This preserves exact header casing (e.g., barAuthToken stays as barAuthToken)
|
||||
// This is useful for APIs that require case-sensitive header names
|
||||
func (s *HeadersAuthStrategy) ApplyOnRR(req *retryablehttp.Request) {
|
||||
for _, header := range s.Data.Headers {
|
||||
req.Header.Set(header.Key, header.Value)
|
||||
req.Header[header.Key] = []string{header.Value}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ info:
|
||||
# static secrets
|
||||
static:
|
||||
# for header based auth session
|
||||
# NOTE: Headers preserve exact casing (e.g., x-pdcp-key stays as x-pdcp-key)
|
||||
# This is useful for APIs that require case-sensitive header names
|
||||
- type: header
|
||||
domains:
|
||||
- api.projectdiscovery.io
|
||||
@@ -20,6 +22,8 @@ static:
|
||||
headers:
|
||||
- key: x-pdcp-key
|
||||
value: <api-key-here>
|
||||
- key: barAuthToken
|
||||
value: <auth-token-here>
|
||||
|
||||
# for query based auth session
|
||||
- type: Query
|
||||
|
||||
@@ -2,7 +2,6 @@ package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"gopkg.in/yaml.v2"
|
||||
@@ -18,7 +17,7 @@ type IgnoreFile struct {
|
||||
func ReadIgnoreFile() IgnoreFile {
|
||||
file, err := os.Open(DefaultConfig.GetIgnoreFilePath())
|
||||
if err != nil {
|
||||
gologger.Error().Msgf("Could not read nuclei-ignore file: %s\n%s\n", err, string(debug.Stack()))
|
||||
gologger.Error().Msgf("Could not read nuclei-ignore file: %s\n", err)
|
||||
return IgnoreFile{}
|
||||
}
|
||||
defer func() {
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/workflows"
|
||||
"github.com/projectdiscovery/retryablehttp-go"
|
||||
"github.com/projectdiscovery/utils/errkit"
|
||||
mapsutil "github.com/projectdiscovery/utils/maps"
|
||||
sliceutil "github.com/projectdiscovery/utils/slice"
|
||||
stringsutil "github.com/projectdiscovery/utils/strings"
|
||||
syncutil "github.com/projectdiscovery/utils/sync"
|
||||
@@ -315,6 +316,8 @@ func (store *Store) LoadTemplatesOnlyMetadata() error {
|
||||
}
|
||||
templatesCache := parserItem.Cache()
|
||||
|
||||
loadedTemplateIDs := mapsutil.NewSyncLockMap[string, struct{}]()
|
||||
|
||||
for templatePath := range validPaths {
|
||||
template, _, _ := templatesCache.Has(templatePath)
|
||||
|
||||
@@ -339,6 +342,12 @@ func (store *Store) LoadTemplatesOnlyMetadata() error {
|
||||
}
|
||||
|
||||
if template != nil {
|
||||
if loadedTemplateIDs.Has(template.ID) {
|
||||
store.logger.Debug().Msgf("Skipping duplicate template ID '%s' from path '%s'", template.ID, templatePath)
|
||||
continue
|
||||
}
|
||||
|
||||
_ = loadedTemplateIDs.Set(template.ID, struct{}{})
|
||||
template.Path = templatePath
|
||||
store.templates = append(store.templates, template)
|
||||
}
|
||||
@@ -492,8 +501,16 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) []*templ
|
||||
templatePathMap := store.pathFilter.Match(includedTemplates)
|
||||
|
||||
loadedTemplates := sliceutil.NewSyncSlice[*templates.Template]()
|
||||
loadedTemplateIDs := mapsutil.NewSyncLockMap[string, struct{}]()
|
||||
|
||||
loadTemplate := func(tmpl *templates.Template) {
|
||||
if loadedTemplateIDs.Has(tmpl.ID) {
|
||||
store.logger.Debug().Msgf("Skipping duplicate template ID '%s' from path '%s'", tmpl.ID, tmpl.Path)
|
||||
return
|
||||
}
|
||||
|
||||
_ = loadedTemplateIDs.Set(tmpl.ID, struct{}{})
|
||||
|
||||
loadedTemplates.Append(tmpl)
|
||||
// increment signed/unsigned counters
|
||||
if tmpl.Verified {
|
||||
|
||||
+73
-56
@@ -48,8 +48,15 @@ func (e *Engine) executeAllSelfContained(ctx context.Context, alltemplates []*te
|
||||
|
||||
// executeTemplateWithTargets executes a given template on x targets (with a internal targetpool(i.e concurrency))
|
||||
func (e *Engine) executeTemplateWithTargets(ctx context.Context, template *templates.Template, target provider.InputProvider, results *atomic.Bool) {
|
||||
// this is target pool i.e max target to execute
|
||||
wg := e.workPool.InputPool(template.Type())
|
||||
if e.workPool == nil {
|
||||
e.workPool = e.GetWorkPool()
|
||||
}
|
||||
// Bounded worker pool using input concurrency
|
||||
pool := e.workPool.InputPool(template.Type())
|
||||
workerCount := 1
|
||||
if pool != nil && pool.Size > 0 {
|
||||
workerCount = pool.Size
|
||||
}
|
||||
|
||||
var (
|
||||
index uint32
|
||||
@@ -78,6 +85,41 @@ func (e *Engine) executeTemplateWithTargets(ctx context.Context, template *templ
|
||||
currentInfo.Unlock()
|
||||
}
|
||||
|
||||
// task represents a single target execution unit
|
||||
type task struct {
|
||||
index uint32
|
||||
skip bool
|
||||
value *contextargs.MetaInput
|
||||
}
|
||||
|
||||
tasks := make(chan task)
|
||||
var workersWg sync.WaitGroup
|
||||
workersWg.Add(workerCount)
|
||||
for i := 0; i < workerCount; i++ {
|
||||
go func() {
|
||||
defer workersWg.Done()
|
||||
for t := range tasks {
|
||||
func() {
|
||||
defer cleanupInFlight(t.index)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
if t.skip {
|
||||
return
|
||||
}
|
||||
|
||||
match, err := e.executeTemplateOnInput(ctx, template, t.value)
|
||||
if err != nil {
|
||||
e.options.Logger.Warning().Msgf("[%s] Could not execute step on %s: %s\n", e.executerOpts.Colorizer.BrightBlue(template.ID), t.value.Input, err)
|
||||
}
|
||||
results.CompareAndSwap(false, match)
|
||||
}()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
target.Iterate(func(scannedValue *contextargs.MetaInput) bool {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -128,43 +170,13 @@ func (e *Engine) executeTemplateWithTargets(ctx context.Context, template *templ
|
||||
return true
|
||||
}
|
||||
|
||||
wg.Add()
|
||||
go func(index uint32, skip bool, value *contextargs.MetaInput) {
|
||||
defer wg.Done()
|
||||
defer cleanupInFlight(index)
|
||||
if skip {
|
||||
return
|
||||
}
|
||||
|
||||
var match bool
|
||||
var err error
|
||||
ctxArgs := contextargs.New(ctx)
|
||||
ctxArgs.MetaInput = value
|
||||
ctx := scan.NewScanContext(ctx, ctxArgs)
|
||||
switch template.Type() {
|
||||
case types.WorkflowProtocol:
|
||||
match = e.executeWorkflow(ctx, template.CompiledWorkflow)
|
||||
default:
|
||||
if e.Callback != nil {
|
||||
if results, err := template.Executer.ExecuteWithResults(ctx); err == nil {
|
||||
for _, result := range results {
|
||||
e.Callback(result)
|
||||
}
|
||||
}
|
||||
match = true
|
||||
} else {
|
||||
match, err = template.Executer.Execute(ctx)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
e.options.Logger.Warning().Msgf("[%s] Could not execute step on %s: %s\n", e.executerOpts.Colorizer.BrightBlue(template.ID), value.Input, err)
|
||||
}
|
||||
results.CompareAndSwap(false, match)
|
||||
}(index, skip, scannedValue)
|
||||
tasks <- task{index: index, skip: skip, value: scannedValue}
|
||||
index++
|
||||
return true
|
||||
})
|
||||
wg.Wait()
|
||||
|
||||
close(tasks)
|
||||
workersWg.Wait()
|
||||
|
||||
// on completion marks the template as completed
|
||||
currentInfo.Lock()
|
||||
@@ -202,26 +214,7 @@ func (e *Engine) executeTemplatesOnTarget(ctx context.Context, alltemplates []*t
|
||||
go func(template *templates.Template, value *contextargs.MetaInput, wg *syncutil.AdaptiveWaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
var match bool
|
||||
var err error
|
||||
ctxArgs := contextargs.New(ctx)
|
||||
ctxArgs.MetaInput = value
|
||||
ctx := scan.NewScanContext(ctx, ctxArgs)
|
||||
switch template.Type() {
|
||||
case types.WorkflowProtocol:
|
||||
match = e.executeWorkflow(ctx, template.CompiledWorkflow)
|
||||
default:
|
||||
if e.Callback != nil {
|
||||
if results, err := template.Executer.ExecuteWithResults(ctx); err == nil {
|
||||
for _, result := range results {
|
||||
e.Callback(result)
|
||||
}
|
||||
}
|
||||
match = true
|
||||
} else {
|
||||
match, err = template.Executer.Execute(ctx)
|
||||
}
|
||||
}
|
||||
match, err := e.executeTemplateOnInput(ctx, template, value)
|
||||
if err != nil {
|
||||
e.options.Logger.Warning().Msgf("[%s] Could not execute step on %s: %s\n", e.executerOpts.Colorizer.BrightBlue(template.ID), value.Input, err)
|
||||
}
|
||||
@@ -229,3 +222,27 @@ func (e *Engine) executeTemplatesOnTarget(ctx context.Context, alltemplates []*t
|
||||
}(tpl, target, sg)
|
||||
}
|
||||
}
|
||||
|
||||
// executeTemplateOnInput performs template execution for a single input and returns match status and error
|
||||
func (e *Engine) executeTemplateOnInput(ctx context.Context, template *templates.Template, value *contextargs.MetaInput) (bool, error) {
|
||||
ctxArgs := contextargs.New(ctx)
|
||||
ctxArgs.MetaInput = value
|
||||
scanCtx := scan.NewScanContext(ctx, ctxArgs)
|
||||
|
||||
switch template.Type() {
|
||||
case types.WorkflowProtocol:
|
||||
return e.executeWorkflow(scanCtx, template.CompiledWorkflow), nil
|
||||
default:
|
||||
if e.Callback != nil {
|
||||
results, err := template.Executer.ExecuteWithResults(scanCtx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, result := range results {
|
||||
e.Callback(result)
|
||||
}
|
||||
return len(results) > 0, nil
|
||||
}
|
||||
return template.Executer.Execute(scanCtx)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
inputtypes "github.com/projectdiscovery/nuclei/v3/pkg/input/types"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/scan"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/templates"
|
||||
tmpltypes "github.com/projectdiscovery/nuclei/v3/pkg/templates/types"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
)
|
||||
|
||||
// fakeExecuter is a simple stub for protocols.Executer used to test executeTemplateOnInput
|
||||
type fakeExecuter struct {
|
||||
withResults bool
|
||||
}
|
||||
|
||||
func (f *fakeExecuter) Compile() error { return nil }
|
||||
func (f *fakeExecuter) Requests() int { return 1 }
|
||||
func (f *fakeExecuter) Execute(ctx *scan.ScanContext) (bool, error) { return !f.withResults, nil }
|
||||
func (f *fakeExecuter) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error) {
|
||||
if !f.withResults {
|
||||
return nil, nil
|
||||
}
|
||||
return []*output.ResultEvent{{Host: "h"}}, nil
|
||||
}
|
||||
|
||||
// newTestEngine creates a minimal Engine for tests
|
||||
func newTestEngine() *Engine {
|
||||
return New(&types.Options{})
|
||||
}
|
||||
|
||||
func Test_executeTemplateOnInput_CallbackPath(t *testing.T) {
|
||||
e := newTestEngine()
|
||||
called := 0
|
||||
e.Callback = func(*output.ResultEvent) { called++ }
|
||||
|
||||
tpl := &templates.Template{}
|
||||
tpl.Executer = &fakeExecuter{withResults: true}
|
||||
|
||||
ok, err := e.executeTemplateOnInput(context.Background(), tpl, &contextargs.MetaInput{Input: "x"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatalf("expected match true")
|
||||
}
|
||||
if called == 0 {
|
||||
t.Fatalf("expected callback to be called")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_executeTemplateOnInput_ExecutePath(t *testing.T) {
|
||||
e := newTestEngine()
|
||||
tpl := &templates.Template{}
|
||||
tpl.Executer = &fakeExecuter{withResults: false}
|
||||
|
||||
ok, err := e.executeTemplateOnInput(context.Background(), tpl, &contextargs.MetaInput{Input: "x"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatalf("expected match true from Execute path")
|
||||
}
|
||||
}
|
||||
|
||||
type fakeExecuterErr struct{}
|
||||
|
||||
func (f *fakeExecuterErr) Compile() error { return nil }
|
||||
func (f *fakeExecuterErr) Requests() int { return 1 }
|
||||
func (f *fakeExecuterErr) Execute(ctx *scan.ScanContext) (bool, error) { return false, nil }
|
||||
func (f *fakeExecuterErr) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error) {
|
||||
return nil, fmt.Errorf("boom")
|
||||
}
|
||||
|
||||
func Test_executeTemplateOnInput_CallbackErrorPropagates(t *testing.T) {
|
||||
e := newTestEngine()
|
||||
e.Callback = func(*output.ResultEvent) {}
|
||||
tpl := &templates.Template{}
|
||||
tpl.Executer = &fakeExecuterErr{}
|
||||
|
||||
ok, err := e.executeTemplateOnInput(context.Background(), tpl, &contextargs.MetaInput{Input: "x"})
|
||||
if err == nil {
|
||||
t.Fatalf("expected error to propagate")
|
||||
}
|
||||
if ok {
|
||||
t.Fatalf("expected match to be false on error")
|
||||
}
|
||||
}
|
||||
|
||||
type fakeTargetProvider struct {
|
||||
values []*contextargs.MetaInput
|
||||
}
|
||||
|
||||
func (f *fakeTargetProvider) Count() int64 { return int64(len(f.values)) }
|
||||
func (f *fakeTargetProvider) Iterate(cb func(value *contextargs.MetaInput) bool) {
|
||||
for _, v := range f.values {
|
||||
if !cb(v) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
func (f *fakeTargetProvider) Set(string, string) {}
|
||||
func (f *fakeTargetProvider) SetWithProbe(string, string, inputtypes.InputLivenessProbe) error {
|
||||
return nil
|
||||
}
|
||||
func (f *fakeTargetProvider) SetWithExclusions(string, string) error { return nil }
|
||||
func (f *fakeTargetProvider) InputType() string { return "test" }
|
||||
func (f *fakeTargetProvider) Close() {}
|
||||
|
||||
type slowExecuter struct{}
|
||||
|
||||
func (s *slowExecuter) Compile() error { return nil }
|
||||
func (s *slowExecuter) Requests() int { return 1 }
|
||||
func (s *slowExecuter) Execute(ctx *scan.ScanContext) (bool, error) {
|
||||
select {
|
||||
case <-ctx.Context().Done():
|
||||
return false, ctx.Context().Err()
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
func (s *slowExecuter) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func Test_executeTemplateWithTargets_RespectsCancellation(t *testing.T) {
|
||||
e := newTestEngine()
|
||||
e.SetExecuterOptions(&protocols.ExecutorOptions{Logger: e.Logger, ResumeCfg: types.NewResumeCfg(), ProtocolType: tmpltypes.HTTPProtocol})
|
||||
|
||||
tpl := &templates.Template{}
|
||||
tpl.Executer = &slowExecuter{}
|
||||
|
||||
targets := &fakeTargetProvider{values: []*contextargs.MetaInput{{Input: "a"}, {Input: "b"}, {Input: "c"}}}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
var matched atomic.Bool
|
||||
e.executeTemplateWithTargets(ctx, tpl, targets, &matched)
|
||||
}
|
||||
+33
-6
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
"github.com/projectdiscovery/utils/errkit"
|
||||
fileutil "github.com/projectdiscovery/utils/file"
|
||||
folderutil "github.com/projectdiscovery/utils/folder"
|
||||
"golang.org/x/oauth2"
|
||||
@@ -46,19 +47,45 @@ func (customTemplate *customTemplateGitHubRepo) Update(ctx context.Context) {
|
||||
downloadPath := config.DefaultConfig.CustomGitHubTemplatesDirectory
|
||||
clonePath := customTemplate.getLocalRepoClonePath(downloadPath)
|
||||
|
||||
// If folder does not exits then clone/download the repo
|
||||
// If folder does not exist then clone/download the repo
|
||||
if !fileutil.FolderExists(clonePath) {
|
||||
customTemplate.Download(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
// Attempt to pull changes and handle the result
|
||||
customTemplate.handlePullChanges(clonePath)
|
||||
}
|
||||
|
||||
// handlePullChanges attempts to pull changes and logs the appropriate message
|
||||
func (customTemplate *customTemplateGitHubRepo) handlePullChanges(clonePath string) {
|
||||
err := customTemplate.pullChanges(clonePath, customTemplate.githubToken)
|
||||
if err != nil {
|
||||
gologger.Error().Msgf("%s", err)
|
||||
} else {
|
||||
gologger.Info().Msgf("Repo %s/%s successfully pulled the changes.\n", customTemplate.owner, customTemplate.reponame)
|
||||
|
||||
switch {
|
||||
case err == nil:
|
||||
customTemplate.logPullSuccess()
|
||||
case errors.Is(err, git.NoErrAlreadyUpToDate):
|
||||
customTemplate.logAlreadyUpToDate(err)
|
||||
default:
|
||||
customTemplate.logPullError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// logPullSuccess logs a success message when changes are pulled
|
||||
func (customTemplate *customTemplateGitHubRepo) logPullSuccess() {
|
||||
gologger.Info().Msgf("Repo %s/%s successfully pulled the changes.\n", customTemplate.owner, customTemplate.reponame)
|
||||
}
|
||||
|
||||
// logAlreadyUpToDate logs an info message when repo is already up to date
|
||||
func (customTemplate *customTemplateGitHubRepo) logAlreadyUpToDate(err error) {
|
||||
gologger.Info().Msgf("%s", err)
|
||||
}
|
||||
|
||||
// logPullError logs an error message when pull fails
|
||||
func (customTemplate *customTemplateGitHubRepo) logPullError(err error) {
|
||||
gologger.Error().Msgf("%s", err)
|
||||
}
|
||||
|
||||
// NewGitHubProviders returns new instance of GitHub providers for downloading custom templates
|
||||
func NewGitHubProviders(options *types.Options) ([]*customTemplateGitHubRepo, error) {
|
||||
providers := []*customTemplateGitHubRepo{}
|
||||
@@ -187,7 +214,7 @@ func (ctr *customTemplateGitHubRepo) pullChanges(repoPath, githubToken string) e
|
||||
|
||||
err = w.Pull(pullOpts)
|
||||
if err != nil {
|
||||
return errors.Errorf("%s/%s: %s", ctr.owner, ctr.reponame, err.Error())
|
||||
return errkit.Wrapf(err, "%s/%s", ctr.owner, ctr.reponame)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -27,7 +27,29 @@ var (
|
||||
|
||||
// NewMultiPartForm returns a new MultiPartForm encoder
|
||||
func NewMultiPartForm() *MultiPartForm {
|
||||
return &MultiPartForm{}
|
||||
return &MultiPartForm{
|
||||
filesMetadata: make(map[string]FileMetadata),
|
||||
}
|
||||
}
|
||||
|
||||
// SetFileMetadata sets the file metadata for a given field name
|
||||
func (m *MultiPartForm) SetFileMetadata(fieldName string, metadata FileMetadata) {
|
||||
if m.filesMetadata == nil {
|
||||
m.filesMetadata = make(map[string]FileMetadata)
|
||||
}
|
||||
|
||||
m.filesMetadata[fieldName] = metadata
|
||||
}
|
||||
|
||||
// GetFileMetadata gets the file metadata for a given field name
|
||||
func (m *MultiPartForm) GetFileMetadata(fieldName string) (FileMetadata, bool) {
|
||||
if m.filesMetadata == nil {
|
||||
return FileMetadata{}, false
|
||||
}
|
||||
|
||||
metadata, exists := m.filesMetadata[fieldName]
|
||||
|
||||
return metadata, exists
|
||||
}
|
||||
|
||||
// IsType returns true if the data is MultiPartForm encoded
|
||||
@@ -125,16 +147,24 @@ func (m *MultiPartForm) ParseBoundary(contentType string) error {
|
||||
if m.boundary == "" {
|
||||
return fmt.Errorf("no boundary found in the content type")
|
||||
}
|
||||
|
||||
// NOTE(dwisiswant0): boundary cannot exceed 70 characters according to
|
||||
// RFC-2046.
|
||||
if len(m.boundary) > 70 {
|
||||
return fmt.Errorf("boundary exceeds maximum length of 70 characters")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode decodes the data from MultiPartForm format
|
||||
func (m *MultiPartForm) Decode(data string) (KV, error) {
|
||||
if m.boundary == "" {
|
||||
return KV{}, fmt.Errorf("boundary not set, call ParseBoundary first")
|
||||
}
|
||||
|
||||
// Create a buffer from the string data
|
||||
b := bytes.NewBufferString(data)
|
||||
// The boundary parameter should be extracted from the Content-Type header of the HTTP request
|
||||
// which is not available in this context, so this is a placeholder for demonstration.
|
||||
// You will need to pass the actual boundary value to this function.
|
||||
r := multipart.NewReader(b, m.boundary)
|
||||
|
||||
form, err := r.ReadForm(32 << 20) // 32MB is the max memory used to parse the form
|
||||
@@ -153,30 +183,44 @@ func (m *MultiPartForm) Decode(data string) (KV, error) {
|
||||
result.Set(key, values[0])
|
||||
}
|
||||
}
|
||||
m.filesMetadata = make(map[string]FileMetadata)
|
||||
|
||||
if m.filesMetadata == nil {
|
||||
m.filesMetadata = make(map[string]FileMetadata)
|
||||
}
|
||||
|
||||
for key, files := range form.File {
|
||||
fileContents := []interface{}{}
|
||||
var fileMetadataList []FileMetadata
|
||||
|
||||
for _, fileHeader := range files {
|
||||
file, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
return KV{}, err
|
||||
}
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
if _, err := buffer.ReadFrom(file); err != nil {
|
||||
_ = file.Close()
|
||||
|
||||
return KV{}, err
|
||||
}
|
||||
_ = file.Close()
|
||||
|
||||
fileContents = append(fileContents, buffer.String())
|
||||
|
||||
m.filesMetadata[key] = FileMetadata{
|
||||
fileMetadataList = append(fileMetadataList, FileMetadata{
|
||||
ContentType: fileHeader.Header.Get("Content-Type"),
|
||||
Filename: fileHeader.Filename,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
result.Set(key, fileContents)
|
||||
|
||||
// NOTE(dwisiswant0): store the first file's metadata instead of the
|
||||
// last one
|
||||
if len(fileMetadataList) > 0 {
|
||||
m.filesMetadata[key] = fileMetadataList[0]
|
||||
}
|
||||
}
|
||||
return KVOrderedMap(&result), nil
|
||||
}
|
||||
|
||||
@@ -240,3 +240,131 @@ Python
|
||||
assert.Contains(t, documentsArray, "fake_pdf_content_1")
|
||||
assert.Contains(t, documentsArray, "fake_pdf_content_2")
|
||||
}
|
||||
|
||||
func TestMultiPartForm_SetGetFileMetadata(t *testing.T) {
|
||||
form := NewMultiPartForm()
|
||||
metadata := FileMetadata{
|
||||
ContentType: "image/jpeg",
|
||||
Filename: "test.jpg",
|
||||
}
|
||||
form.SetFileMetadata("avatar", metadata)
|
||||
|
||||
// Test GetFileMetadata for existing field
|
||||
retrievedMetadata, exists := form.GetFileMetadata("avatar")
|
||||
assert.True(t, exists)
|
||||
assert.Equal(t, metadata.ContentType, retrievedMetadata.ContentType)
|
||||
assert.Equal(t, metadata.Filename, retrievedMetadata.Filename)
|
||||
|
||||
// Test GetFileMetadata for non-existing field
|
||||
_, exists = form.GetFileMetadata("nonexistent")
|
||||
assert.False(t, exists)
|
||||
}
|
||||
|
||||
func TestMultiPartForm_FilesMetadataInitialization(t *testing.T) {
|
||||
form := NewMultiPartForm()
|
||||
assert.NotNil(t, form.filesMetadata)
|
||||
|
||||
metadata := FileMetadata{
|
||||
ContentType: "text/plain",
|
||||
Filename: "test.txt",
|
||||
}
|
||||
form.SetFileMetadata("file", metadata)
|
||||
|
||||
retrievedMetadata, exists := form.GetFileMetadata("file")
|
||||
assert.True(t, exists)
|
||||
assert.Equal(t, metadata, retrievedMetadata)
|
||||
}
|
||||
|
||||
func TestMultiPartForm_BoundaryValidation(t *testing.T) {
|
||||
form := NewMultiPartForm()
|
||||
|
||||
// Test valid boundary
|
||||
err := form.ParseBoundary("multipart/form-data; boundary=testboundary")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "testboundary", form.boundary)
|
||||
|
||||
// Test missing boundary
|
||||
err = form.ParseBoundary("multipart/form-data")
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "no boundary found")
|
||||
|
||||
// Test boundary too long (over 70 characters)
|
||||
longBoundary := "multipart/form-data; boundary=" + string(make([]byte, 71))
|
||||
for i := range longBoundary[len("multipart/form-data; boundary="):] {
|
||||
longBoundary = longBoundary[:len("multipart/form-data; boundary=")+i] + "a" + longBoundary[len("multipart/form-data; boundary=")+i+1:]
|
||||
}
|
||||
|
||||
err = form.ParseBoundary(longBoundary)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "boundary exceeds maximum length")
|
||||
}
|
||||
|
||||
func TestMultiPartForm_DecodeRequiresBoundary(t *testing.T) {
|
||||
form := NewMultiPartForm()
|
||||
|
||||
// Decode should fail if boundary is not set
|
||||
_, err := form.Decode("some data")
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "boundary not set")
|
||||
}
|
||||
|
||||
func TestMultiPartForm_MultipleFilesMetadata(t *testing.T) {
|
||||
form := NewMultiPartForm()
|
||||
form.boundary = "----WebKitFormBoundaryMultiFileTest"
|
||||
|
||||
// Test with multiple files having the same field name
|
||||
multipartData := `------WebKitFormBoundaryMultiFileTest
|
||||
Content-Disposition: form-data; name="documents"; filename="file1.txt"
|
||||
Content-Type: text/plain
|
||||
|
||||
content1
|
||||
------WebKitFormBoundaryMultiFileTest
|
||||
Content-Disposition: form-data; name="documents"; filename="file2.txt"
|
||||
Content-Type: text/plain
|
||||
|
||||
content2
|
||||
------WebKitFormBoundaryMultiFileTest--
|
||||
`
|
||||
|
||||
decoded, err := form.Decode(multipartData)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify files are decoded correctly
|
||||
documents := decoded.Get("documents")
|
||||
require.NotNil(t, documents)
|
||||
documentsArray, ok := documents.([]interface{})
|
||||
require.True(t, ok)
|
||||
require.Len(t, documentsArray, 2)
|
||||
assert.Contains(t, documentsArray, "content1")
|
||||
assert.Contains(t, documentsArray, "content2")
|
||||
|
||||
// Verify metadata for the field exists (should be from the first file)
|
||||
metadata, exists := form.GetFileMetadata("documents")
|
||||
assert.True(t, exists)
|
||||
assert.Equal(t, "text/plain", metadata.ContentType)
|
||||
assert.Equal(t, "file1.txt", metadata.Filename) // Should be from first file, not last
|
||||
}
|
||||
|
||||
func TestMultiPartForm_SetFileMetadataWithNilMap(t *testing.T) {
|
||||
form := &MultiPartForm{}
|
||||
|
||||
// SetFileMetadata should handle nil filesMetadata
|
||||
metadata := FileMetadata{
|
||||
ContentType: "application/pdf",
|
||||
Filename: "document.pdf",
|
||||
}
|
||||
form.SetFileMetadata("doc", metadata)
|
||||
|
||||
// Should be able to retrieve the metadata
|
||||
retrievedMetadata, exists := form.GetFileMetadata("doc")
|
||||
assert.True(t, exists)
|
||||
assert.Equal(t, metadata, retrievedMetadata)
|
||||
}
|
||||
|
||||
func TestMultiPartForm_GetFileMetadataWithNilMap(t *testing.T) {
|
||||
form := &MultiPartForm{}
|
||||
|
||||
// GetFileMetadata should handle nil filesMetadata gracefully
|
||||
_, exists := form.GetFileMetadata("anything")
|
||||
assert.False(t, exists)
|
||||
}
|
||||
@@ -28,6 +28,12 @@ type InputFormatOptions struct {
|
||||
// RequiredOnly only uses required fields when generating requests
|
||||
// instead of all fields
|
||||
RequiredOnly bool
|
||||
// VarsTextTemplating uses Variables and inject it into the input
|
||||
// this is used for text templating of variables based on carvel ytt
|
||||
// Only available for Yaml formats
|
||||
VarsTextTemplating bool
|
||||
// VarsFilePaths is the path to the file containing variables
|
||||
VarsFilePaths []string
|
||||
}
|
||||
|
||||
// Format is an interface implemented by all input formats
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#@ load("@ytt:data", "data")
|
||||
#@ load("@ytt:json", "json")
|
||||
|
||||
#@ def get_value(key, default=""):
|
||||
#@ if hasattr(data.values, key):
|
||||
#@ return str(getattr(data.values, key))
|
||||
#@ else:
|
||||
#@ return default
|
||||
#@ end
|
||||
#@ end
|
||||
|
||||
timestamp: 2024-02-20T19:24:13+05:32
|
||||
url: https://ginandjuice.shop/users/3
|
||||
request:
|
||||
#@yaml/text-templated-strings
|
||||
raw: |+
|
||||
POST /users/3 HTTP/1.1
|
||||
Host: ginandjuice.shop
|
||||
Authorization: Bearer (@= get_value("token", "3x4mpl3t0k3n") @)
|
||||
Accept-Encoding: gzip
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Connection: close
|
||||
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
|
||||
|
||||
foo=(@= json.encode(data.values.foo) @)&bar=(@= get_value("bar") @)&debug=(@= get_value("debug", "false") @)
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
list: pkg/input/formats/testdata/ytt/ginandjuice.ytt.yaml
|
||||
input-mode: yaml
|
||||
templates:
|
||||
- integration_tests/fuzz/fuzz-body.yaml
|
||||
var:
|
||||
- debug=true
|
||||
- bar=bar
|
||||
vars-text-templating: true
|
||||
var-file-paths:
|
||||
- pkg/input/formats/testdata/ytt/ytt-vars.yaml
|
||||
dast: true
|
||||
@@ -0,0 +1,3 @@
|
||||
token: foobar
|
||||
foo:
|
||||
bar: baz
|
||||
@@ -1,8 +1,8 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/projectdiscovery/gologger"
|
||||
@@ -46,23 +46,41 @@ func (j *YamlMultiDocFormat) SetOptions(options formats.InputFormatOptions) {
|
||||
// Parse parses the input and calls the provided callback
|
||||
// function for each RawRequest it discovers.
|
||||
func (j *YamlMultiDocFormat) Parse(input io.Reader, resultsCb formats.ParseReqRespCallback, filePath string) error {
|
||||
decoder := YamlUtil.NewDecoder(input)
|
||||
finalInput := input
|
||||
|
||||
// Apply text templating if enabled
|
||||
if j.opts.VarsTextTemplating {
|
||||
data, err := io.ReadAll(input)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not read input")
|
||||
}
|
||||
tpl := []string{string(data)}
|
||||
dvs := mapToKeyValueSlice(j.opts.Variables)
|
||||
finalData, err := ytt(tpl, dvs, j.opts.VarsFilePaths)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not apply ytt templating")
|
||||
}
|
||||
finalInput = bytes.NewReader(finalData)
|
||||
}
|
||||
|
||||
decoder := YamlUtil.NewDecoder(finalInput)
|
||||
for {
|
||||
var request proxifyRequest
|
||||
err := decoder.Decode(&request)
|
||||
if err == io.EOF {
|
||||
break
|
||||
if err := decoder.Decode(&request); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return errors.Wrap(err, "could not decode yaml file")
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not decode json file")
|
||||
}
|
||||
if strings.TrimSpace(request.Request.Raw) == "" {
|
||||
|
||||
raw := request.Request.Raw
|
||||
if raw == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
rawRequest, err := types.ParseRawRequestWithURL(request.Request.Raw, request.URL)
|
||||
rawRequest, err := types.ParseRawRequestWithURL(raw, request.URL)
|
||||
if err != nil {
|
||||
gologger.Warning().Msgf("multidoc-yaml: Could not parse raw request %s: %s\n", request.URL, err)
|
||||
gologger.Warning().Msgf("multidoc-yaml: Could not parse raw request %s: %s", request.URL, err)
|
||||
continue
|
||||
}
|
||||
resultsCb(rawRequest)
|
||||
|
||||
@@ -2,8 +2,10 @@ package yaml
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/input/formats"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/input/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -33,3 +35,48 @@ func TestYamlFormatterParse(t *testing.T) {
|
||||
require.Len(t, urls, len(expectedUrls), "invalid number of urls")
|
||||
require.ElementsMatch(t, urls, expectedUrls, "invalid urls")
|
||||
}
|
||||
|
||||
func TestYamlFormatterParseWithVariables(t *testing.T) {
|
||||
format := New()
|
||||
proxifyYttFile := "../testdata/ytt/ginandjuice.ytt.yaml"
|
||||
|
||||
expectedUrls := []string{
|
||||
"https://ginandjuice.shop/users/3",
|
||||
}
|
||||
|
||||
format.SetOptions(formats.InputFormatOptions{
|
||||
VarsTextTemplating: true,
|
||||
Variables: map[string]interface{}{
|
||||
"foo": "catalog",
|
||||
"bar": "product",
|
||||
},
|
||||
})
|
||||
file, err := os.Open(proxifyYttFile)
|
||||
require.Nilf(t, err, "error opening proxify ytt input file: %v", err)
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
var urls []string
|
||||
err = format.Parse(file, func(request *types.RequestResponse) bool {
|
||||
urls = append(urls, request.URL.String())
|
||||
expectedRaw := `POST /users/3 HTTP/1.1
|
||||
Host: ginandjuice.shop
|
||||
Authorization: Bearer 3x4mpl3t0k3n
|
||||
Accept-Encoding: gzip
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
Connection: close
|
||||
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
|
||||
|
||||
foo="catalog"&bar=product&debug=false`
|
||||
normalised := strings.ReplaceAll(request.Request.Raw, "\r\n", "\n")
|
||||
require.Equal(t, expectedRaw, strings.TrimSuffix(normalised, "\n"), "request raw does not match expected value")
|
||||
|
||||
return false
|
||||
}, proxifyYttFile)
|
||||
|
||||
require.Nilf(t, err, "error parsing yaml file: %v", err)
|
||||
require.Len(t, urls, len(expectedUrls), "invalid number of urls")
|
||||
require.ElementsMatch(t, urls, expectedUrls, "invalid urls")
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
yttcmd "carvel.dev/ytt/pkg/cmd/template"
|
||||
yttui "carvel.dev/ytt/pkg/cmd/ui"
|
||||
yttfiles "carvel.dev/ytt/pkg/files"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func ytt(tpl, dvs []string, varFiles []string) ([]byte, error) {
|
||||
// create and invoke ytt "template" command
|
||||
templatingOptions := yttcmd.NewOptions()
|
||||
|
||||
input, err := templatesAsInput(tpl...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(varFiles) > 0 {
|
||||
// Load vaarFiles into the templating options.
|
||||
templatingOptions.DataValuesFlags.FromFiles = varFiles
|
||||
}
|
||||
|
||||
// equivalent to `--data-value-yaml`
|
||||
templatingOptions.DataValuesFlags.KVsFromYAML = dvs
|
||||
|
||||
// for in-memory use, pipe output to "/dev/null"
|
||||
noopUI := yttui.NewCustomWriterTTY(false, noopWriter{}, noopWriter{})
|
||||
|
||||
// Evaluate the template given the configured data values...
|
||||
output := templatingOptions.RunWithFiles(input, noopUI)
|
||||
if output.Err != nil {
|
||||
return nil, output.Err
|
||||
}
|
||||
|
||||
return output.DocSet.AsBytes()
|
||||
}
|
||||
|
||||
// templatesAsInput conveniently wraps one or more strings, each in a files.File, into a template.Input.
|
||||
func templatesAsInput(tpl ...string) (yttcmd.Input, error) {
|
||||
var files []*yttfiles.File
|
||||
for i, t := range tpl {
|
||||
// to make this less brittle, you'll probably want to use well-defined names for `path`, here, for each input.
|
||||
// this matters when you're processing errors which report based on these paths.
|
||||
file, err := yttfiles.NewFileFromSource(yttfiles.NewBytesSource(fmt.Sprintf("tpl%d.yml", i), []byte(t)))
|
||||
if err != nil {
|
||||
return yttcmd.Input{}, err
|
||||
}
|
||||
|
||||
files = append(files, file)
|
||||
}
|
||||
|
||||
return yttcmd.Input{Files: files}, nil
|
||||
}
|
||||
|
||||
func mapToKeyValueSlice(m map[string]interface{}) []string {
|
||||
var result []string
|
||||
for k, v := range m {
|
||||
y, _ := yaml.Marshal(v)
|
||||
result = append(result, fmt.Sprintf("%s=%s", k, strings.TrimSpace(string(y))))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type noopWriter struct{}
|
||||
|
||||
func (w noopWriter) Write(data []byte) (int, error) { return len(data), nil }
|
||||
@@ -116,6 +116,8 @@ func NewInputProvider(opts InputOptions) (InputProvider, error) {
|
||||
Variables: generators.MergeMaps(extraVars, opts.Options.Vars.AsMap()),
|
||||
SkipFormatValidation: opts.Options.SkipFormatValidation,
|
||||
RequiredOnly: opts.Options.FormatUseRequiredOnly,
|
||||
VarsTextTemplating: opts.Options.VarsTextTemplating,
|
||||
VarsFilePaths: opts.Options.VarsFilePaths,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,12 +15,12 @@ func init() {
|
||||
module.Set(
|
||||
gojs.Objects{
|
||||
// Functions
|
||||
"IsOracle": lib_oracle.IsOracle,
|
||||
|
||||
// Var and consts
|
||||
|
||||
// Objects / Classes
|
||||
"IsOracleResponse": gojs.GetClassConstructor[lib_oracle.IsOracleResponse](&lib_oracle.IsOracleResponse{}),
|
||||
"OracleClient": gojs.GetClassConstructor[lib_oracle.OracleClient](&lib_oracle.OracleClient{}),
|
||||
},
|
||||
).Register()
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ func init() {
|
||||
|
||||
// Objects / Classes
|
||||
"IsVNCResponse": gojs.GetClassConstructor[lib_vnc.IsVNCResponse](&lib_vnc.IsVNCResponse{}),
|
||||
"VNCClient": gojs.GetClassConstructor[lib_vnc.VNCClient](&lib_vnc.VNCClient{}),
|
||||
},
|
||||
).Register()
|
||||
}
|
||||
|
||||
@@ -1,33 +1,106 @@
|
||||
|
||||
|
||||
/**
|
||||
* IsOracle checks if a host is running an Oracle server
|
||||
* @example
|
||||
* ```javascript
|
||||
* const oracle = require('nuclei/oracle');
|
||||
* const isOracle = oracle.IsOracle('acme.com', 1521);
|
||||
* log(toJSON(isOracle));
|
||||
* ```
|
||||
*/
|
||||
export function IsOracle(host: string, port: number): IsOracleResponse | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* IsOracleResponse is the response from the IsOracle function.
|
||||
* this is returned by IsOracle function.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const oracle = require('nuclei/oracle');
|
||||
* const isOracle = oracle.IsOracle('acme.com', 1521);
|
||||
* const client = new oracle.OracleClient();
|
||||
* const isOracle = client.IsOracle('acme.com', 1521);
|
||||
* ```
|
||||
*/
|
||||
export interface IsOracleResponse {
|
||||
|
||||
IsOracle?: boolean,
|
||||
|
||||
Banner?: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* Client is a client for Oracle database.
|
||||
* Internally client uses go-ora driver.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const oracle = require('nuclei/oracle');
|
||||
* const client = new oracle.OracleClient();
|
||||
* ```
|
||||
*/
|
||||
export class OracleClient {
|
||||
// Constructor of OracleClient
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Connect connects to an Oracle database
|
||||
* @example
|
||||
* ```javascript
|
||||
* const oracle = require('nuclei/oracle');
|
||||
* const client = new oracle.OracleClient();
|
||||
* client.Connect('acme.com', 1521, 'XE', 'user', 'password');
|
||||
* ```
|
||||
*/
|
||||
public Connect(host: string, port: number, serviceName: string, username: string, password: string): boolean | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ConnectWithDSN connects to an Oracle database using a DSN string
|
||||
* @example
|
||||
* ```javascript
|
||||
* const oracle = require('nuclei/oracle');
|
||||
* const client = new oracle.OracleClient();
|
||||
* client.ConnectWithDSN('oracle://user:password@host:port/service', 'SELECT @@version');
|
||||
* ```
|
||||
*/
|
||||
public ConnectWithDSN(dsn: string): boolean | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* IsOracle checks if a host is running an Oracle server
|
||||
* @example
|
||||
* ```javascript
|
||||
* const oracle = require('nuclei/oracle');
|
||||
* const isOracle = oracle.IsOracle('acme.com', 1521);
|
||||
* ```
|
||||
*/
|
||||
public IsOracle(host: string, port: number): IsOracleResponse | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ExecuteQuery connects to Oracle database using given credentials and executes a query.
|
||||
* It returns the results of the query or an error if something goes wrong.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const oracle = require('nuclei/oracle');
|
||||
* const client = new oracle.OracleClient();
|
||||
* const result = client.ExecuteQuery('acme.com', 1521, 'username', 'password', 'XE', 'SELECT * FROM dual');
|
||||
* log(to_json(result));
|
||||
* ```
|
||||
*/
|
||||
public ExecuteQuery(host: string, port: number, username: string, password: string, dbName: string, query: string): SQLResult | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ExecuteQueryWithDSN executes a query on an Oracle database using a DSN
|
||||
* @example
|
||||
* ```javascript
|
||||
* const oracle = require('nuclei/oracle');
|
||||
* const client = new oracle.OracleClient();
|
||||
* const result = client.ExecuteQueryWithDSN('oracle://user:password@host:port/service', 'SELECT * FROM dual');
|
||||
* log(to_json(result));
|
||||
* ```
|
||||
*/
|
||||
public ExecuteQueryWithDSN(dsn: string, query: string): SQLResult | null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SQLResult Interface
|
||||
*/
|
||||
export interface SQLResult {
|
||||
Count?: number,
|
||||
Columns?: string[],
|
||||
Rows?: any[],
|
||||
}
|
||||
@@ -33,3 +33,34 @@ export interface IsVNCResponse {
|
||||
Banner?: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* VNCClient is a client for VNC servers.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const vnc = require('nuclei/vnc');
|
||||
* const client = new vnc.VNCClient();
|
||||
* ```
|
||||
*/
|
||||
export class VNCClient {
|
||||
|
||||
|
||||
// Constructor of VNCClient
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Connect connects to VNC server using given password.
|
||||
* If connection and authentication is successful, it returns true.
|
||||
* If connection or authentication is unsuccessful, it returns false and error.
|
||||
* The connection is closed after the function returns.
|
||||
* @example
|
||||
* ```javascript
|
||||
* const vnc = require('nuclei/vnc');
|
||||
* const client = new vnc.VNCClient();
|
||||
* const connected = client.Connect('acme.com', 5900, 'password');
|
||||
* ```
|
||||
*/
|
||||
public Connect(host: string, port: number, password: string): boolean | null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package oracle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
@@ -9,7 +10,9 @@ import (
|
||||
|
||||
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
|
||||
"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/oracledb"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/js/utils"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
|
||||
goora "github.com/sijms/go-ora/v2"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -24,6 +27,16 @@ type (
|
||||
IsOracle bool
|
||||
Banner string
|
||||
}
|
||||
// Client is a client for Oracle database.
|
||||
// Internally client uses oracle/godror driver.
|
||||
// @example
|
||||
// ```javascript
|
||||
// const oracle = require('nuclei/oracle');
|
||||
// const client = new oracle.OracleClient();
|
||||
// ```
|
||||
OracleClient struct {
|
||||
connector *goora.OracleConnector
|
||||
}
|
||||
)
|
||||
|
||||
// IsOracle checks if a host is running an Oracle server
|
||||
@@ -33,7 +46,7 @@ type (
|
||||
// const isOracle = oracle.IsOracle('acme.com', 1521);
|
||||
// log(toJSON(isOracle));
|
||||
// ```
|
||||
func IsOracle(ctx context.Context, host string, port int) (IsOracleResponse, error) {
|
||||
func (c *OracleClient) IsOracle(ctx context.Context, host string, port int) (IsOracleResponse, error) {
|
||||
executionId := ctx.Value("executionId").(string)
|
||||
return memoizedisOracle(executionId, host, port)
|
||||
}
|
||||
@@ -69,3 +82,129 @@ func isOracle(executionId string, host string, port int) (IsOracleResponse, erro
|
||||
resp.IsOracle = true
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *OracleClient) oracleDbInstance(connStr string, executionId string) (*goora.OracleConnector, error) {
|
||||
if c.connector != nil {
|
||||
return c.connector, nil
|
||||
}
|
||||
|
||||
connector := goora.NewConnector(connStr)
|
||||
oraConnector, ok := connector.(*goora.OracleConnector)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to cast connector to OracleConnector")
|
||||
}
|
||||
|
||||
// Create custom dialer wrapper
|
||||
customDialer := &oracleCustomDialer{
|
||||
executionId: executionId,
|
||||
}
|
||||
|
||||
oraConnector.Dialer(customDialer)
|
||||
|
||||
c.connector = oraConnector
|
||||
|
||||
return oraConnector, nil
|
||||
}
|
||||
|
||||
// Connect connects to an Oracle database
|
||||
// @example
|
||||
// ```javascript
|
||||
// const oracle = require('nuclei/oracle');
|
||||
// const client = new oracle.OracleClient;
|
||||
// client.Connect('acme.com', 1521, 'XE', 'user', 'password');
|
||||
// ```
|
||||
func (c *OracleClient) Connect(ctx context.Context, host string, port int, serviceName string, username string, password string) (bool, error) {
|
||||
connStr := goora.BuildUrl(host, port, serviceName, username, password, nil)
|
||||
|
||||
return c.ConnectWithDSN(ctx, connStr)
|
||||
}
|
||||
|
||||
func (c *OracleClient) ConnectWithDSN(ctx context.Context, dsn string) (bool, error) {
|
||||
executionId := ctx.Value("executionId").(string)
|
||||
|
||||
connector, err := c.oracleDbInstance(dsn, executionId)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
db := sql.OpenDB(connector)
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
db.SetMaxOpenConns(1)
|
||||
db.SetMaxIdleConns(0)
|
||||
|
||||
// Test the connection
|
||||
err = db.Ping()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// ExecuteQuery connects to MS SQL database using given credentials and executes a query.
|
||||
// It returns the results of the query or an error if something goes wrong.
|
||||
// @example
|
||||
// ```javascript
|
||||
// const oracle = require('nuclei/oracle');
|
||||
// const client = new oracle.OracleClient;
|
||||
// const result = client.ExecuteQuery('acme.com', 1521, 'username', 'password', 'XE', 'SELECT @@version');
|
||||
// log(to_json(result));
|
||||
// ```
|
||||
func (c *OracleClient) ExecuteQuery(ctx context.Context, host string, port int, username, password, dbName, query string) (*utils.SQLResult, error) {
|
||||
if host == "" || port <= 0 {
|
||||
return nil, fmt.Errorf("invalid host or port")
|
||||
}
|
||||
|
||||
isOracleResp, err := c.IsOracle(ctx, host, port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isOracleResp.IsOracle {
|
||||
return nil, fmt.Errorf("not a oracle service")
|
||||
}
|
||||
|
||||
connStr := goora.BuildUrl(host, port, dbName, username, password, nil)
|
||||
|
||||
return c.ExecuteQueryWithDSN(ctx, connStr, query)
|
||||
}
|
||||
|
||||
// ExecuteQueryWithDSN executes a query on an Oracle database using a DSN
|
||||
// @example
|
||||
// ```javascript
|
||||
// const oracle = require('nuclei/oracle');
|
||||
// const client = new oracle.OracleClient;
|
||||
// const result = client.ExecuteQueryWithDSN('oracle://user:password@host:port/service', 'SELECT @@version');
|
||||
// log(to_json(result));
|
||||
// ```
|
||||
func (c *OracleClient) ExecuteQueryWithDSN(ctx context.Context, dsn string, query string) (*utils.SQLResult, error) {
|
||||
executionId := ctx.Value("executionId").(string)
|
||||
|
||||
connector, err := c.oracleDbInstance(dsn, executionId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db := sql.OpenDB(connector)
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
db.SetMaxOpenConns(1)
|
||||
db.SetMaxIdleConns(0)
|
||||
|
||||
rows, err := db.Query(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := utils.UnmarshalSQLRows(rows)
|
||||
if err != nil {
|
||||
if data != nil && len(data.Rows) > 0 {
|
||||
return data, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
|
||||
)
|
||||
|
||||
// oracleCustomDialer implements the dialer interface expected by go-ora
|
||||
type oracleCustomDialer struct {
|
||||
executionId string
|
||||
}
|
||||
|
||||
func (o *oracleCustomDialer) dialWithCtx(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
dialers := protocolstate.GetDialersWithId(o.executionId)
|
||||
if dialers == nil {
|
||||
return nil, fmt.Errorf("dialers not initialized for %s", o.executionId)
|
||||
}
|
||||
if !protocolstate.IsHostAllowed(o.executionId, address) {
|
||||
// host is not valid according to network policy
|
||||
return nil, protocolstate.ErrHostDenied.Msgf(address)
|
||||
}
|
||||
return dialers.Fastdialer.Dial(ctx, network, address)
|
||||
}
|
||||
|
||||
func (o *oracleCustomDialer) Dial(network, address string) (net.Conn, error) {
|
||||
return o.dialWithCtx(context.TODO(), network, address)
|
||||
}
|
||||
|
||||
func (o *oracleCustomDialer) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
return o.dialWithCtx(ctx, network, address)
|
||||
}
|
||||
|
||||
func (o *oracleCustomDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
return o.dialWithCtx(ctx, network, address)
|
||||
}
|
||||
+85
-2
@@ -7,9 +7,11 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
vnclib "github.com/alexsnet/go-vnc"
|
||||
"github.com/praetorian-inc/fingerprintx/pkg/plugins"
|
||||
"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/vnc"
|
||||
vncplugin "github.com/praetorian-inc/fingerprintx/pkg/plugins/services/vnc"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
|
||||
stringsutil "github.com/projectdiscovery/utils/strings"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -24,8 +26,89 @@ type (
|
||||
IsVNC bool
|
||||
Banner string
|
||||
}
|
||||
|
||||
// VNCClient is a client for VNC servers.
|
||||
// @example
|
||||
// ```javascript
|
||||
// const vnc = require('nuclei/vnc');
|
||||
// const client = new vnc.VNCClient();
|
||||
// const connected = client.Connect('acme.com', 5900, 'password');
|
||||
// log(toJSON(connected));
|
||||
// ```
|
||||
VNCClient struct{}
|
||||
)
|
||||
|
||||
// Connect connects to VNC server using given password.
|
||||
// If connection and authentication is successful, it returns true.
|
||||
// If connection or authentication is unsuccessful, it returns false and error.
|
||||
// The connection is closed after the function returns.
|
||||
// @example
|
||||
// ```javascript
|
||||
// const vnc = require('nuclei/vnc');
|
||||
// const client = new vnc.VNCClient();
|
||||
// const connected = client.Connect('acme.com', 5900, 'password');
|
||||
// ```
|
||||
func (c *VNCClient) Connect(ctx context.Context, host string, port int, password string) (bool, error) {
|
||||
executionId := ctx.Value("executionId").(string)
|
||||
return connect(executionId, host, port, password)
|
||||
}
|
||||
|
||||
// connect attempts to authenticate with a VNC server using the given password
|
||||
func connect(executionId string, host string, port int, password string) (bool, error) {
|
||||
if host == "" || port <= 0 {
|
||||
return false, fmt.Errorf("invalid host or port")
|
||||
}
|
||||
if !protocolstate.IsHostAllowed(executionId, host) {
|
||||
// host is not valid according to network policy
|
||||
return false, protocolstate.ErrHostDenied.Msgf(host)
|
||||
}
|
||||
|
||||
dialer := protocolstate.GetDialersWithId(executionId)
|
||||
if dialer == nil {
|
||||
return false, fmt.Errorf("dialers not initialized for %s", executionId)
|
||||
}
|
||||
|
||||
conn, err := dialer.Fastdialer.Dial(context.TODO(), "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer func() {
|
||||
_ = conn.Close()
|
||||
}()
|
||||
|
||||
// Set connection timeout
|
||||
_ = conn.SetDeadline(time.Now().Add(10 * time.Second))
|
||||
|
||||
// Create VNC client config with password
|
||||
vncConfig := vnclib.NewClientConfig(password)
|
||||
|
||||
// Attempt to connect and authenticate
|
||||
c, err := vnclib.Connect(context.TODO(), conn, vncConfig)
|
||||
if err != nil {
|
||||
// Check for specific authentication errors
|
||||
if isAuthError(err) {
|
||||
return false, nil // Authentication failed, but connection succeeded
|
||||
}
|
||||
return false, err // Connection or other error
|
||||
}
|
||||
if c != nil {
|
||||
_ = c.Close()
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// isAuthError checks if the error is an authentication failure
|
||||
func isAuthError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for common VNC authentication error messages
|
||||
errStr := err.Error()
|
||||
return stringsutil.ContainsAnyI(errStr, "authentication", "auth", "password", "invalid", "failed")
|
||||
}
|
||||
|
||||
// IsVNC checks if a host is running a VNC server.
|
||||
// It returns a boolean indicating if the host is running a VNC server
|
||||
// and the banner of the VNC server.
|
||||
@@ -57,7 +140,7 @@ func isVNC(executionId string, host string, port int) (IsVNCResponse, error) {
|
||||
_ = conn.Close()
|
||||
}()
|
||||
|
||||
vncPlugin := vnc.VNCPlugin{}
|
||||
vncPlugin := vncplugin.VNCPlugin{}
|
||||
service, err := vncPlugin.Run(conn, timeout, plugins.Target{Host: host})
|
||||
if err != nil {
|
||||
return resp, err
|
||||
|
||||
Vendored
+62
@@ -0,0 +1,62 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"sync"
|
||||
|
||||
"github.com/Knetic/govaluate"
|
||||
"github.com/projectdiscovery/gcache"
|
||||
)
|
||||
|
||||
var (
|
||||
initOnce sync.Once
|
||||
mu sync.RWMutex
|
||||
|
||||
regexCap = 4096
|
||||
dslCap = 4096
|
||||
|
||||
regexCache gcache.Cache[string, *regexp.Regexp]
|
||||
dslCache gcache.Cache[string, *govaluate.EvaluableExpression]
|
||||
)
|
||||
|
||||
func initCaches() {
|
||||
initOnce.Do(func() {
|
||||
regexCache = gcache.New[string, *regexp.Regexp](regexCap).LRU().Build()
|
||||
dslCache = gcache.New[string, *govaluate.EvaluableExpression](dslCap).LRU().Build()
|
||||
})
|
||||
}
|
||||
|
||||
func SetCapacities(regexCapacity, dslCapacity int) {
|
||||
// ensure caches are initialized under initOnce, so later Regex()/DSL() won't re-init
|
||||
initCaches()
|
||||
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
if regexCapacity > 0 {
|
||||
regexCap = regexCapacity
|
||||
}
|
||||
if dslCapacity > 0 {
|
||||
dslCap = dslCapacity
|
||||
}
|
||||
if regexCapacity <= 0 && dslCapacity <= 0 {
|
||||
return
|
||||
}
|
||||
// rebuild caches with new capacities
|
||||
regexCache = gcache.New[string, *regexp.Regexp](regexCap).LRU().Build()
|
||||
dslCache = gcache.New[string, *govaluate.EvaluableExpression](dslCap).LRU().Build()
|
||||
}
|
||||
|
||||
func Regex() gcache.Cache[string, *regexp.Regexp] {
|
||||
initCaches()
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
return regexCache
|
||||
}
|
||||
|
||||
func DSL() gcache.Cache[string, *govaluate.EvaluableExpression] {
|
||||
initCaches()
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
return dslCache
|
||||
}
|
||||
Vendored
+114
@@ -0,0 +1,114 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/Knetic/govaluate"
|
||||
)
|
||||
|
||||
func TestRegexCache_SetGet(t *testing.T) {
|
||||
// ensure init
|
||||
c := Regex()
|
||||
pattern := "abc(\n)?123"
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
t.Fatalf("compile: %v", err)
|
||||
}
|
||||
if err := c.Set(pattern, re); err != nil {
|
||||
t.Fatalf("set: %v", err)
|
||||
}
|
||||
got, err := c.GetIFPresent(pattern)
|
||||
if err != nil || got == nil {
|
||||
t.Fatalf("get: %v got=%v", err, got)
|
||||
}
|
||||
if got.String() != re.String() {
|
||||
t.Fatalf("mismatch: %s != %s", got.String(), re.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDSLCache_SetGet(t *testing.T) {
|
||||
c := DSL()
|
||||
expr := "1 + 2 == 3"
|
||||
ast, err := govaluate.NewEvaluableExpression(expr)
|
||||
if err != nil {
|
||||
t.Fatalf("dsl compile: %v", err)
|
||||
}
|
||||
if err := c.Set(expr, ast); err != nil {
|
||||
t.Fatalf("set: %v", err)
|
||||
}
|
||||
got, err := c.GetIFPresent(expr)
|
||||
if err != nil || got == nil {
|
||||
t.Fatalf("get: %v got=%v", err, got)
|
||||
}
|
||||
if got.String() != ast.String() {
|
||||
t.Fatalf("mismatch: %s != %s", got.String(), ast.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegexCache_EvictionByCapacity(t *testing.T) {
|
||||
SetCapacities(3, 3)
|
||||
c := Regex()
|
||||
for i := 0; i < 5; i++ {
|
||||
k := string(rune('a' + i))
|
||||
re := regexp.MustCompile(k)
|
||||
_ = c.Set(k, re)
|
||||
}
|
||||
// last 3 keys expected to remain under LRU: 'c','d','e'
|
||||
if _, err := c.GetIFPresent("a"); err == nil {
|
||||
t.Fatalf("expected 'a' to be evicted")
|
||||
}
|
||||
if _, err := c.GetIFPresent("b"); err == nil {
|
||||
t.Fatalf("expected 'b' to be evicted")
|
||||
}
|
||||
if _, err := c.GetIFPresent("c"); err != nil {
|
||||
t.Fatalf("expected 'c' present")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetCapacities_NoRebuildOnZero(t *testing.T) {
|
||||
// init
|
||||
SetCapacities(4, 4)
|
||||
c1 := Regex()
|
||||
_ = c1.Set("k", regexp.MustCompile("k"))
|
||||
if _, err := c1.GetIFPresent("k"); err != nil {
|
||||
t.Fatalf("expected key present: %v", err)
|
||||
}
|
||||
// zero changes should not rebuild/clear caches
|
||||
SetCapacities(0, 0)
|
||||
c2 := Regex()
|
||||
if _, err := c2.GetIFPresent("k"); err != nil {
|
||||
t.Fatalf("key lost after zero-capacity SetCapacities: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetCapacities_BeforeFirstUse(t *testing.T) {
|
||||
// This should not be overridden by later initCaches
|
||||
SetCapacities(2, 0)
|
||||
c := Regex()
|
||||
_ = c.Set("a", regexp.MustCompile("a"))
|
||||
_ = c.Set("b", regexp.MustCompile("b"))
|
||||
_ = c.Set("c", regexp.MustCompile("c"))
|
||||
if _, err := c.GetIFPresent("a"); err == nil {
|
||||
t.Fatalf("expected 'a' to be evicted under cap=2")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetCapacities_ConcurrentAccess(t *testing.T) {
|
||||
SetCapacities(64, 64)
|
||||
stop := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
for i := 0; i < 5000; i++ {
|
||||
_ = Regex().Set("k"+string(rune('a'+(i%26))), regexp.MustCompile("a"))
|
||||
_, _ = Regex().GetIFPresent("k" + string(rune('a'+(i%26))))
|
||||
_, _ = DSL().GetIFPresent("1+2==3")
|
||||
}
|
||||
close(stop)
|
||||
}()
|
||||
|
||||
for i := 0; i < 200; i++ {
|
||||
SetCapacities(64+(i%5), 64+((i+1)%5))
|
||||
}
|
||||
<-stop
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/Knetic/govaluate"
|
||||
"github.com/itchyny/gojq"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/operators/cache"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl"
|
||||
)
|
||||
|
||||
@@ -20,10 +21,15 @@ func (e *Extractor) CompileExtractors() error {
|
||||
e.extractorType = computedType
|
||||
// Compile the regexes
|
||||
for _, regex := range e.Regex {
|
||||
if cached, err := cache.Regex().GetIFPresent(regex); err == nil && cached != nil {
|
||||
e.regexCompiled = append(e.regexCompiled, cached)
|
||||
continue
|
||||
}
|
||||
compiled, err := regexp.Compile(regex)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not compile regex: %s", regex)
|
||||
}
|
||||
_ = cache.Regex().Set(regex, compiled)
|
||||
e.regexCompiled = append(e.regexCompiled, compiled)
|
||||
}
|
||||
for i, kval := range e.KVal {
|
||||
@@ -43,10 +49,15 @@ func (e *Extractor) CompileExtractors() error {
|
||||
}
|
||||
|
||||
for _, dslExp := range e.DSL {
|
||||
if cached, err := cache.DSL().GetIFPresent(dslExp); err == nil && cached != nil {
|
||||
e.dslCompiled = append(e.dslCompiled, cached)
|
||||
continue
|
||||
}
|
||||
compiled, err := govaluate.NewEvaluableExpressionWithFunctions(dslExp, dsl.HelperFunctions)
|
||||
if err != nil {
|
||||
return &dsl.CompilationError{DslSignature: dslExp, WrappedError: err}
|
||||
}
|
||||
_ = cache.DSL().Set(dslExp, compiled)
|
||||
e.dslCompiled = append(e.dslCompiled, compiled)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,19 @@ func (e *Extractor) ExtractRegex(corpus string) map[string]struct{} {
|
||||
|
||||
groupPlusOne := e.RegexGroup + 1
|
||||
for _, regex := range e.regexCompiled {
|
||||
matches := regex.FindAllStringSubmatch(corpus, -1)
|
||||
// skip prefix short-circuit for case-insensitive patterns
|
||||
rstr := regex.String()
|
||||
if !strings.Contains(rstr, "(?i") {
|
||||
if prefix, ok := regex.LiteralPrefix(); ok && prefix != "" {
|
||||
if !strings.Contains(corpus, prefix) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, match := range matches {
|
||||
submatches := regex.FindAllStringSubmatch(corpus, -1)
|
||||
|
||||
for _, match := range submatches {
|
||||
if len(match) < groupPlusOne {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/Knetic/govaluate"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/operators/cache"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl"
|
||||
)
|
||||
|
||||
@@ -42,12 +42,17 @@ func (matcher *Matcher) CompileMatchers() error {
|
||||
matcher.Part = "body"
|
||||
}
|
||||
|
||||
// Compile the regexes
|
||||
// Compile the regexes (with shared cache)
|
||||
for _, regex := range matcher.Regex {
|
||||
if cached, err := cache.Regex().GetIFPresent(regex); err == nil && cached != nil {
|
||||
matcher.regexCompiled = append(matcher.regexCompiled, cached)
|
||||
continue
|
||||
}
|
||||
compiled, err := regexp.Compile(regex)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not compile regex: %s", regex)
|
||||
}
|
||||
_ = cache.Regex().Set(regex, compiled)
|
||||
matcher.regexCompiled = append(matcher.regexCompiled, compiled)
|
||||
}
|
||||
|
||||
@@ -60,12 +65,17 @@ func (matcher *Matcher) CompileMatchers() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Compile the dsl expressions
|
||||
// Compile the dsl expressions (with shared cache)
|
||||
for _, dslExpression := range matcher.DSL {
|
||||
if cached, err := cache.DSL().GetIFPresent(dslExpression); err == nil && cached != nil {
|
||||
matcher.dslCompiled = append(matcher.dslCompiled, cached)
|
||||
continue
|
||||
}
|
||||
compiledExpression, err := govaluate.NewEvaluableExpressionWithFunctions(dslExpression, dsl.HelperFunctions)
|
||||
if err != nil {
|
||||
return &dsl.CompilationError{DslSignature: dslExpression, WrappedError: err}
|
||||
}
|
||||
_ = cache.DSL().Set(dslExpression, compiledExpression)
|
||||
matcher.dslCompiled = append(matcher.dslCompiled, compiledExpression)
|
||||
}
|
||||
|
||||
|
||||
@@ -106,10 +106,33 @@ func (matcher *Matcher) MatchRegex(corpus string) (bool, []string) {
|
||||
var matchedRegexes []string
|
||||
// Iterate over all the regexes accepted as valid
|
||||
for i, regex := range matcher.regexCompiled {
|
||||
// Continue if the regex doesn't match
|
||||
if !regex.MatchString(corpus) {
|
||||
// If we are in an AND request and a match failed,
|
||||
// return false as the AND condition fails on any single mismatch.
|
||||
// Literal prefix short-circuit
|
||||
rstr := regex.String()
|
||||
if !strings.Contains(rstr, "(?i") { // covers (?i) and (?i:
|
||||
if prefix, ok := regex.LiteralPrefix(); ok && prefix != "" {
|
||||
if !strings.Contains(corpus, prefix) {
|
||||
switch matcher.condition {
|
||||
case ANDCondition:
|
||||
return false, []string{}
|
||||
case ORCondition:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fast OR-path: return first match without full scan
|
||||
if matcher.condition == ORCondition && !matcher.MatchAll {
|
||||
m := regex.FindAllString(corpus, 1)
|
||||
if len(m) == 0 {
|
||||
continue
|
||||
}
|
||||
return true, m
|
||||
}
|
||||
|
||||
// Single scan: get all matches directly
|
||||
currentMatches := regex.FindAllString(corpus, -1)
|
||||
if len(currentMatches) == 0 {
|
||||
switch matcher.condition {
|
||||
case ANDCondition:
|
||||
return false, []string{}
|
||||
@@ -118,12 +141,7 @@ func (matcher *Matcher) MatchRegex(corpus string) (bool, []string) {
|
||||
}
|
||||
}
|
||||
|
||||
currentMatches := regex.FindAllString(corpus, -1)
|
||||
// If the condition was an OR, return on the first match.
|
||||
if matcher.condition == ORCondition && !matcher.MatchAll {
|
||||
return true, currentMatches
|
||||
}
|
||||
|
||||
// If the condition was an OR (and MatchAll true), we still need to gather all
|
||||
matchedRegexes = append(matchedRegexes, currentMatches...)
|
||||
|
||||
// If we are at the end of the regex, return with true
|
||||
|
||||
@@ -84,7 +84,7 @@ func TestMatcher_MatchDSL(t *testing.T) {
|
||||
|
||||
values := []string{"PING", "pong"}
|
||||
|
||||
for value := range values {
|
||||
for _, value := range values {
|
||||
isMatched := m.MatchDSL(map[string]interface{}{"body": value, "VARIABLE": value})
|
||||
require.True(t, isMatched)
|
||||
}
|
||||
@@ -209,3 +209,66 @@ func TestMatcher_MatchXPath_XML(t *testing.T) {
|
||||
isMatched = m.MatchXPath("<h1> not right <q id=2/>notvalid")
|
||||
require.False(t, isMatched, "Invalid xpath did not return false")
|
||||
}
|
||||
|
||||
func TestMatchRegex_CaseInsensitivePrefixSkip(t *testing.T) {
|
||||
m := &Matcher{Type: MatcherTypeHolder{MatcherType: RegexMatcher}, Condition: "or", Regex: []string{"(?i)abc"}}
|
||||
err := m.CompileMatchers()
|
||||
require.NoError(t, err)
|
||||
ok, got := m.MatchRegex("zzz AbC yyy")
|
||||
require.True(t, ok)
|
||||
require.NotEmpty(t, got)
|
||||
}
|
||||
|
||||
func TestMatchStatusCodeAndSize(t *testing.T) {
|
||||
mStatus := &Matcher{Status: []int{200, 302}}
|
||||
require.True(t, mStatus.MatchStatusCode(200))
|
||||
require.True(t, mStatus.MatchStatusCode(302))
|
||||
require.False(t, mStatus.MatchStatusCode(404))
|
||||
|
||||
mSize := &Matcher{Size: []int{5, 10}}
|
||||
require.True(t, mSize.MatchSize(5))
|
||||
require.False(t, mSize.MatchSize(7))
|
||||
}
|
||||
|
||||
func TestMatchBinary_AND_OR(t *testing.T) {
|
||||
// AND should fail if any binary not present
|
||||
mAnd := &Matcher{Type: MatcherTypeHolder{MatcherType: BinaryMatcher}, Condition: "and", Binary: []string{"50494e47", "414141"}} // "PING", "AAA"
|
||||
require.NoError(t, mAnd.CompileMatchers())
|
||||
ok, _ := mAnd.MatchBinary("PING")
|
||||
require.False(t, ok)
|
||||
// OR should succeed if any present
|
||||
mOr := &Matcher{Type: MatcherTypeHolder{MatcherType: BinaryMatcher}, Condition: "or", Binary: []string{"414141", "50494e47"}} // "AAA", "PING"
|
||||
require.NoError(t, mOr.CompileMatchers())
|
||||
ok, got := mOr.MatchBinary("xxPINGyy")
|
||||
require.True(t, ok)
|
||||
require.NotEmpty(t, got)
|
||||
}
|
||||
|
||||
func TestMatchRegex_LiteralPrefixShortCircuit(t *testing.T) {
|
||||
// AND: first regex has literal prefix "abc"; corpus lacks it => early false
|
||||
mAnd := &Matcher{Type: MatcherTypeHolder{MatcherType: RegexMatcher}, Condition: "and", Regex: []string{"abc[0-9]*", "[0-9]{2}"}}
|
||||
require.NoError(t, mAnd.CompileMatchers())
|
||||
ok, matches := mAnd.MatchRegex("zzz 12 yyy")
|
||||
require.False(t, ok)
|
||||
require.Empty(t, matches)
|
||||
|
||||
// OR: first regex skipped due to missing prefix, second matches => true
|
||||
mOr := &Matcher{Type: MatcherTypeHolder{MatcherType: RegexMatcher}, Condition: "or", Regex: []string{"abc[0-9]*", "[0-9]{2}"}}
|
||||
require.NoError(t, mOr.CompileMatchers())
|
||||
ok, matches = mOr.MatchRegex("zzz 12 yyy")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, []string{"12"}, matches)
|
||||
}
|
||||
|
||||
func TestMatcher_MatchDSL_ErrorHandling(t *testing.T) {
|
||||
// First expression errors (division by zero), second is true
|
||||
bad, err := govaluate.NewEvaluableExpression("1 / 0")
|
||||
require.NoError(t, err)
|
||||
good, err := govaluate.NewEvaluableExpression("1 == 1")
|
||||
require.NoError(t, err)
|
||||
|
||||
m := &Matcher{Type: MatcherTypeHolder{MatcherType: DSLMatcher}, Condition: "or", dslCompiled: []*govaluate.EvaluableExpression{bad, good}}
|
||||
require.NoError(t, m.CompileMatchers())
|
||||
ok := m.MatchDSL(map[string]interface{}{})
|
||||
require.True(t, ok)
|
||||
}
|
||||
@@ -5,11 +5,15 @@ import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type WafDetector struct {
|
||||
wafs map[string]waf
|
||||
regexCache map[string]*regexp.Regexp
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// waf represents a web application firewall definition
|
||||
@@ -53,19 +57,55 @@ func NewWafDetector() *WafDetector {
|
||||
}
|
||||
|
||||
func (d *WafDetector) DetectWAF(content string) (string, bool) {
|
||||
if d == nil || d.regexCache == nil {
|
||||
if d == nil || d.regexCache == nil || len(content) == 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
|
||||
// Limit content size to prevent regex catastrophic backtracking
|
||||
maxContentSize := 50000 // 50KB limit
|
||||
if len(content) > maxContentSize {
|
||||
content = content[:maxContentSize]
|
||||
}
|
||||
|
||||
for id, regex := range d.regexCache {
|
||||
if regex != nil && regex.MatchString(content) {
|
||||
if regex == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Safely test each regex with panic recovery
|
||||
matched := func() bool {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// Get stack trace and format in one line
|
||||
buf := make([]byte, 4096)
|
||||
n := runtime.Stack(buf, false)
|
||||
stack := strings.ReplaceAll(string(buf[:n]), "\n", " | ")
|
||||
|
||||
log.Printf("regex panic for WAF %s: %v: %v", id, r, stack)
|
||||
}
|
||||
}()
|
||||
return regex.MatchString(content)
|
||||
}()
|
||||
|
||||
if matched {
|
||||
return id, true
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (d *WafDetector) GetWAF(id string) (waf, bool) {
|
||||
if d == nil {
|
||||
return waf{}, false
|
||||
}
|
||||
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
|
||||
waf, ok := d.wafs[id]
|
||||
return waf, ok
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package generators
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestAttackTypeHelpers(t *testing.T) {
|
||||
// GetSupportedAttackTypes should include three values
|
||||
types := GetSupportedAttackTypes()
|
||||
if len(types) != 3 {
|
||||
t.Fatalf("expected 3 types, got %d", len(types))
|
||||
}
|
||||
// toAttackType valid
|
||||
if got, err := toAttackType("pitchfork"); err != nil || got != PitchForkAttack {
|
||||
t.Fatalf("toAttackType failed: %v %v", got, err)
|
||||
}
|
||||
// toAttackType invalid
|
||||
if _, err := toAttackType("nope"); err == nil {
|
||||
t.Fatalf("expected error for invalid attack type")
|
||||
}
|
||||
// normalizeValue and String
|
||||
if normalizeValue(" ClusterBomb ") != "clusterbomb" {
|
||||
t.Fatalf("normalizeValue failed")
|
||||
}
|
||||
if ClusterBombAttack.String() != "clusterbomb" {
|
||||
t.Fatalf("String failed")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package generators
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseEnvVars(t *testing.T) {
|
||||
old := os.Environ()
|
||||
// set a scoped env var
|
||||
_ = os.Setenv("NUCLEI_TEST_K", "V1")
|
||||
t.Cleanup(func() {
|
||||
// restore
|
||||
for _, kv := range old {
|
||||
parts := kv
|
||||
_ = parts // nothing, environment already has superset; best-effort cleanup below
|
||||
}
|
||||
_ = os.Unsetenv("NUCLEI_TEST_K")
|
||||
})
|
||||
vars := parseEnvVars()
|
||||
if vars["NUCLEI_TEST_K"] != "V1" {
|
||||
t.Fatalf("expected V1, got %v", vars["NUCLEI_TEST_K"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvVarsMemoization(t *testing.T) {
|
||||
// reset memoized map
|
||||
envVars = nil
|
||||
_ = os.Setenv("NUCLEI_TEST_MEMO", "A")
|
||||
t.Cleanup(func() { _ = os.Unsetenv("NUCLEI_TEST_MEMO") })
|
||||
v1 := EnvVars()["NUCLEI_TEST_MEMO"]
|
||||
// change env after memoization
|
||||
_ = os.Setenv("NUCLEI_TEST_MEMO", "B")
|
||||
v2 := EnvVars()["NUCLEI_TEST_MEMO"]
|
||||
if v1 != "A" || v2 != "A" {
|
||||
t.Fatalf("memoization failed: %v %v", v1, v2)
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,20 @@ func (generator *PayloadGenerator) loadPayloads(payloads map[string]interface{},
|
||||
for name, payload := range payloads {
|
||||
switch pt := payload.(type) {
|
||||
case string:
|
||||
// Fast path: if no newline, treat as file path
|
||||
if !strings.ContainsRune(pt, '\n') {
|
||||
file, err := generator.options.LoadHelperFile(pt, templatePath, generator.catalog)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not load payload file")
|
||||
}
|
||||
payloads, err := generator.loadPayloadsFromFile(file)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not load payloads")
|
||||
}
|
||||
loadedPayloads[name] = payloads
|
||||
break
|
||||
}
|
||||
// Multiline inline payloads
|
||||
elements := strings.Split(pt, "\n")
|
||||
//golint:gomnd // this is not a magic number
|
||||
if len(elements) >= 2 {
|
||||
|
||||
@@ -1,120 +1,108 @@
|
||||
package generators
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk"
|
||||
osutils "github.com/projectdiscovery/utils/os"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
)
|
||||
|
||||
func TestLoadPayloads(t *testing.T) {
|
||||
// since we are changing value of global variable i.e templates directory
|
||||
// run this test as subprocess
|
||||
if os.Getenv("LOAD_PAYLOAD_NO_ACCESS") != "1" {
|
||||
cmd := exec.Command(os.Args[0], "-test.run=TestLoadPayloadsWithAccess")
|
||||
cmd.Env = append(os.Environ(), "LOAD_PAYLOAD_NO_ACCESS=1")
|
||||
err := cmd.Run()
|
||||
if e, ok := err.(*exec.ExitError); ok && !e.Success() {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("process ran with err %v, want exit status 1", err)
|
||||
type fakeCatalog struct{ catalog.Catalog }
|
||||
|
||||
func (f *fakeCatalog) OpenFile(filename string) (io.ReadCloser, error) {
|
||||
return nil, errors.New("not used")
|
||||
}
|
||||
func (f *fakeCatalog) GetTemplatePath(target string) ([]string, error) { return nil, nil }
|
||||
func (f *fakeCatalog) GetTemplatesPath(definitions []string) ([]string, map[string]error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (f *fakeCatalog) ResolvePath(templateName, second string) (string, error) {
|
||||
return templateName, nil
|
||||
}
|
||||
|
||||
func newTestGenerator() *PayloadGenerator {
|
||||
opts := types.DefaultOptions()
|
||||
// inject helper loader function
|
||||
opts.LoadHelperFileFunction = func(path, templatePath string, _ catalog.Catalog) (io.ReadCloser, error) {
|
||||
switch path {
|
||||
case "fileA.txt":
|
||||
return io.NopCloser(strings.NewReader("one\n two\n\nthree\n")), nil
|
||||
default:
|
||||
return io.NopCloser(strings.NewReader("x\ny\nz\n")), nil
|
||||
}
|
||||
}
|
||||
templateDir := getTemplatesDir(t)
|
||||
config.DefaultConfig.SetTemplatesDir(templateDir)
|
||||
|
||||
generator := &PayloadGenerator{catalog: disk.NewCatalog(templateDir), options: getOptions(false)}
|
||||
fullpath := filepath.Join(templateDir, "payloads.txt")
|
||||
|
||||
// Test sandbox
|
||||
t.Run("templates-directory", func(t *testing.T) {
|
||||
// testcase when loading file from template directory and template file is in root
|
||||
// expected to succeed
|
||||
values, err := generator.loadPayloads(map[string]interface{}{
|
||||
"new": fullpath,
|
||||
}, "/test")
|
||||
require.NoError(t, err, "could not load payloads")
|
||||
require.Equal(t, map[string][]string{"new": {"test", "another"}}, values, "could not get values")
|
||||
})
|
||||
t.Run("templates-path-relative", func(t *testing.T) {
|
||||
// testcase when loading file from template directory and template file is current working directory
|
||||
// expected to fail since this is LFI
|
||||
_, err := generator.loadPayloads(map[string]interface{}{
|
||||
"new": "../../../../../../../../../etc/passwd",
|
||||
}, ".")
|
||||
require.Error(t, err, "could load payloads")
|
||||
})
|
||||
t.Run("template-directory", func(t *testing.T) {
|
||||
// testcase when loading file from template directory and template file is inside template directory
|
||||
// expected to succeed
|
||||
values, err := generator.loadPayloads(map[string]interface{}{
|
||||
"new": fullpath,
|
||||
}, filepath.Join(templateDir, "test.yaml"))
|
||||
require.NoError(t, err, "could not load payloads")
|
||||
require.Equal(t, map[string][]string{"new": {"test", "another"}}, values, "could not get values")
|
||||
})
|
||||
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
// testcase when loading file from /etc/passwd and template file is at root i.e /
|
||||
// expected to fail since this is LFI
|
||||
values, err := generator.loadPayloads(map[string]interface{}{
|
||||
"new": "/etc/passwd",
|
||||
}, "/random")
|
||||
require.Error(t, err, "could load payloads got %v", values)
|
||||
require.Equal(t, 0, len(values), "could get values")
|
||||
|
||||
// testcase when loading file from template directory and template file is at root i.e /
|
||||
// expected to succeed
|
||||
values, err = generator.loadPayloads(map[string]interface{}{
|
||||
"new": fullpath,
|
||||
}, "/random")
|
||||
require.NoError(t, err, "could load payloads %v", values)
|
||||
require.Equal(t, 1, len(values), "could get values")
|
||||
require.Equal(t, []string{"test", "another"}, values["new"], "could get values")
|
||||
})
|
||||
return &PayloadGenerator{options: opts, catalog: &fakeCatalog{}}
|
||||
}
|
||||
|
||||
func TestLoadPayloadsWithAccess(t *testing.T) {
|
||||
// since we are changing value of global variable i.e templates directory
|
||||
// run this test as subprocess
|
||||
if os.Getenv("LOAD_PAYLOAD_WITH_ACCESS") != "1" {
|
||||
cmd := exec.Command(os.Args[0], "-test.run=TestLoadPayloadsWithAccess")
|
||||
cmd.Env = append(os.Environ(), "LOAD_PAYLOAD_WITH_ACCESS=1")
|
||||
err := cmd.Run()
|
||||
if e, ok := err.(*exec.ExitError); ok && !e.Success() {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("process ran with err %v, want exit status 1", err)
|
||||
}
|
||||
func TestLoadPayloads_FastPathFile(t *testing.T) {
|
||||
g := newTestGenerator()
|
||||
out, err := g.loadPayloads(map[string]interface{}{"A": "fileA.txt"}, "")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
got := out["A"]
|
||||
if len(got) != 3 || got[0] != "one" || got[1] != " two" || got[2] != "three" {
|
||||
t.Fatalf("unexpected: %#v", got)
|
||||
}
|
||||
templateDir := getTemplatesDir(t)
|
||||
config.DefaultConfig.SetTemplatesDir(templateDir)
|
||||
|
||||
generator := &PayloadGenerator{catalog: disk.NewCatalog(templateDir), options: getOptions(true)}
|
||||
|
||||
t.Run("no-sandbox-unix", func(t *testing.T) {
|
||||
if osutils.IsWindows() {
|
||||
return
|
||||
}
|
||||
_, err := generator.loadPayloads(map[string]interface{}{
|
||||
"new": "/etc/passwd",
|
||||
}, "/random")
|
||||
require.NoError(t, err, "could load payloads")
|
||||
})
|
||||
}
|
||||
|
||||
func getTemplatesDir(t *testing.T) string {
|
||||
tempdir, err := os.MkdirTemp("", "templates-*")
|
||||
require.NoError(t, err, "could not create temp dir")
|
||||
fullpath := filepath.Join(tempdir, "payloads.txt")
|
||||
err = os.WriteFile(fullpath, []byte("test\nanother"), 0777)
|
||||
require.NoError(t, err, "could not write payload")
|
||||
return tempdir
|
||||
func TestLoadPayloads_InlineMultiline(t *testing.T) {
|
||||
g := newTestGenerator()
|
||||
inline := "a\nb\n"
|
||||
out, err := g.loadPayloads(map[string]interface{}{"B": inline}, "")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
got := out["B"]
|
||||
if len(got) != 3 || got[0] != "a" || got[1] != "b" || got[2] != "" {
|
||||
t.Fatalf("unexpected: %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadPayloads_SingleLineFallsBackToFile(t *testing.T) {
|
||||
g := newTestGenerator()
|
||||
inline := "fileA.txt" // single line, should be treated as file path
|
||||
out, err := g.loadPayloads(map[string]interface{}{"C": inline}, "")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
got := out["C"]
|
||||
if len(got) != 3 {
|
||||
t.Fatalf("unexpected len: %d", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadPayloads_InterfaceSlice(t *testing.T) {
|
||||
g := newTestGenerator()
|
||||
out, err := g.loadPayloads(map[string]interface{}{"D": []interface{}{"p", "q"}}, "")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
got := out["D"]
|
||||
if len(got) != 2 || got[0] != "p" || got[1] != "q" {
|
||||
t.Fatalf("unexpected: %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadPayloadsFromFile_SkipsEmpty(t *testing.T) {
|
||||
g := newTestGenerator()
|
||||
rc := io.NopCloser(strings.NewReader("a\n\n\n b \n"))
|
||||
lines, err := g.loadPayloadsFromFile(rc)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if len(lines) != 2 || lines[0] != "a" || lines[1] != " b " {
|
||||
t.Fatalf("unexpected: %#v", lines)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate_AllowsInlineMultiline(t *testing.T) {
|
||||
g := newTestGenerator()
|
||||
inline := "x\ny\n"
|
||||
if err := g.validate(map[string]interface{}{"E": inline}, ""); err != nil {
|
||||
t.Fatalf("validate rejected inline multiline: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -14,3 +14,32 @@ func TestMergeMapsMany(t *testing.T) {
|
||||
"c": {"5"},
|
||||
}, got, "could not get correct merged map")
|
||||
}
|
||||
|
||||
func TestMergeMapsAndExpand(t *testing.T) {
|
||||
m1 := map[string]interface{}{"a": "1"}
|
||||
m2 := map[string]interface{}{"b": "2"}
|
||||
out := MergeMaps(m1, m2)
|
||||
if out["a"].(string) != "1" || out["b"].(string) != "2" {
|
||||
t.Fatalf("unexpected merge: %#v", out)
|
||||
}
|
||||
flat := map[string]string{"x": "y"}
|
||||
exp := ExpandMapValues(flat)
|
||||
if len(exp["x"]) != 1 || exp["x"][0] != "y" {
|
||||
t.Fatalf("unexpected expand: %#v", exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIteratorRemaining(t *testing.T) {
|
||||
g, err := New(map[string]interface{}{"k": []interface{}{"a", "b"}}, BatteringRamAttack, "", nil, "", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new: %v", err)
|
||||
}
|
||||
it := g.NewIterator()
|
||||
if it.Total() != 2 || it.Remaining() != 2 {
|
||||
t.Fatalf("unexpected totals: %d %d", it.Total(), it.Remaining())
|
||||
}
|
||||
_, _ = it.Value()
|
||||
if it.Remaining() != 1 {
|
||||
t.Fatalf("unexpected remaining after one: %d", it.Remaining())
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package generators
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -17,9 +16,8 @@ func (g *PayloadGenerator) validate(payloads map[string]interface{}, templatePat
|
||||
for name, payload := range payloads {
|
||||
switch payloadType := payload.(type) {
|
||||
case string:
|
||||
// check if it's a multiline string list
|
||||
if len(strings.Split(payloadType, "\n")) != 1 {
|
||||
return errors.New("invalid number of lines in payload")
|
||||
if strings.ContainsRune(payloadType, '\n') {
|
||||
continue
|
||||
}
|
||||
|
||||
// For historical reasons, "validate" checks to see if the payload file exist.
|
||||
|
||||
@@ -92,9 +92,8 @@ type generatedRequest struct {
|
||||
|
||||
// setReqURLPattern sets the url request pattern for the generated request
|
||||
func (gr *generatedRequest) setReqURLPattern(reqURLPattern string) {
|
||||
data := strings.Split(reqURLPattern, "\n")
|
||||
if len(data) > 1 {
|
||||
reqURLPattern = strings.TrimSpace(data[0])
|
||||
if idx := strings.IndexByte(reqURLPattern, '\n'); idx >= 0 {
|
||||
reqURLPattern = strings.TrimSpace(reqURLPattern[:idx])
|
||||
// this is raw request (if it has 3 parts after strings.Fields then its valid only use 2nd part)
|
||||
parts := strings.Fields(reqURLPattern)
|
||||
if len(parts) >= 3 {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/authprovider/authx"
|
||||
@@ -17,6 +18,8 @@ import (
|
||||
urlutil "github.com/projectdiscovery/utils/url"
|
||||
)
|
||||
|
||||
var bufferPool = sync.Pool{New: func() any { return new(bytes.Buffer) }}
|
||||
|
||||
// Request defines a basic HTTP raw request
|
||||
type Request struct {
|
||||
FullURL string
|
||||
@@ -270,13 +273,17 @@ func (r *Request) TryFillCustomHeaders(headers []string) error {
|
||||
if newLineIndex > 0 {
|
||||
newLineIndex += hostHeaderIndex + 2
|
||||
// insert custom headers
|
||||
var buf bytes.Buffer
|
||||
buf := bufferPool.Get().(*bytes.Buffer)
|
||||
buf.Reset()
|
||||
buf.Write(r.UnsafeRawBytes[:newLineIndex])
|
||||
for _, header := range headers {
|
||||
buf.WriteString(fmt.Sprintf("%s\r\n", header))
|
||||
buf.WriteString(header)
|
||||
buf.WriteString("\r\n")
|
||||
}
|
||||
buf.Write(r.UnsafeRawBytes[newLineIndex:])
|
||||
r.UnsafeRawBytes = buf.Bytes()
|
||||
r.UnsafeRawBytes = append([]byte(nil), buf.Bytes()...)
|
||||
buf.Reset()
|
||||
bufferPool.Put(buf)
|
||||
return nil
|
||||
}
|
||||
return errors.New("no new line found at the end of host header")
|
||||
@@ -301,9 +308,10 @@ func (r *Request) ApplyAuthStrategy(strategy authx.AuthStrategy) {
|
||||
parsed.Params.Add(p.Key, p.Value)
|
||||
}
|
||||
case *authx.CookiesAuthStrategy:
|
||||
var buff bytes.Buffer
|
||||
buff := bufferPool.Get().(*bytes.Buffer)
|
||||
buff.Reset()
|
||||
for _, cookie := range s.Data.Cookies {
|
||||
buff.WriteString(fmt.Sprintf("%s=%s; ", cookie.Key, cookie.Value))
|
||||
fmt.Fprintf(buff, "%s=%s; ", cookie.Key, cookie.Value)
|
||||
}
|
||||
if buff.Len() > 0 {
|
||||
if val, ok := r.Headers["Cookie"]; ok {
|
||||
@@ -312,6 +320,7 @@ func (r *Request) ApplyAuthStrategy(strategy authx.AuthStrategy) {
|
||||
r.Headers["Cookie"] = buff.String()
|
||||
}
|
||||
}
|
||||
bufferPool.Put(buff)
|
||||
case *authx.HeadersAuthStrategy:
|
||||
for _, header := range s.Data.Headers {
|
||||
r.Headers[header.Key] = header.Value
|
||||
|
||||
@@ -7,6 +7,21 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTryFillCustomHeaders_BufferDetached(t *testing.T) {
|
||||
r := &Request{
|
||||
UnsafeRawBytes: []byte("GET / HTTP/1.1\r\nHost: example.com\r\n\r\nBody"),
|
||||
}
|
||||
// first fill
|
||||
err := r.TryFillCustomHeaders([]string{"X-Test: 1"})
|
||||
require.NoError(t, err, "unexpected error on first call")
|
||||
prev := r.UnsafeRawBytes
|
||||
prevStr := string(prev) // content snapshot
|
||||
err = r.TryFillCustomHeaders([]string{"X-Another: 2"})
|
||||
require.NoError(t, err, "unexpected error on second call")
|
||||
require.Equal(t, prevStr, string(prev), "first slice mutated after second call; buffer not detached")
|
||||
require.NotEqual(t, prevStr, string(r.UnsafeRawBytes), "request bytes did not change after second call")
|
||||
}
|
||||
|
||||
func TestParseRawRequestWithPort(t *testing.T) {
|
||||
request, err := Parse(`GET /gg/phpinfo.php HTTP/1.1
|
||||
Host: {{Hostname}}:123
|
||||
|
||||
@@ -240,6 +240,48 @@ func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicV
|
||||
}
|
||||
})
|
||||
|
||||
// bounded worker-pool to avoid spawning one goroutine per payload
|
||||
type task struct {
|
||||
req *generatedRequest
|
||||
updatedInput *contextargs.Context
|
||||
}
|
||||
|
||||
var workersWg sync.WaitGroup
|
||||
currentWorkers := maxWorkers
|
||||
tasks := make(chan task, maxWorkers)
|
||||
spawnWorker := func(ctx context.Context) {
|
||||
workersWg.Add(1)
|
||||
go func() {
|
||||
defer workersWg.Done()
|
||||
for t := range tasks {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
if spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(t.updatedInput) || spmHandler.Cancelled() {
|
||||
continue
|
||||
}
|
||||
spmHandler.Acquire()
|
||||
if spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(t.updatedInput) || spmHandler.Cancelled() {
|
||||
spmHandler.Release()
|
||||
continue
|
||||
}
|
||||
request.options.RateLimitTake()
|
||||
select {
|
||||
case <-spmHandler.Done():
|
||||
spmHandler.Release()
|
||||
continue
|
||||
case spmHandler.ResultChan <- request.executeRequest(t.updatedInput, t.req, make(map[string]interface{}), false, wrappedCallback, 0):
|
||||
spmHandler.Release()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
for i := 0; i < currentWorkers; i++ {
|
||||
spawnWorker(ctx)
|
||||
}
|
||||
|
||||
// iterate payloads and make requests
|
||||
generator := request.newGenerator(false)
|
||||
for {
|
||||
@@ -259,6 +301,13 @@ func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicV
|
||||
if err := spmHandler.Resize(input.Context(), request.options.Options.PayloadConcurrency); err != nil {
|
||||
return err
|
||||
}
|
||||
// if payload concurrency increased, add more workers
|
||||
if spmHandler.Size() > currentWorkers {
|
||||
for i := 0; i < spmHandler.Size()-currentWorkers; i++ {
|
||||
spawnWorker(ctx)
|
||||
}
|
||||
currentWorkers = spmHandler.Size()
|
||||
}
|
||||
}
|
||||
|
||||
// break if stop at first match is found or host is unresponsive
|
||||
@@ -284,29 +333,21 @@ func (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicV
|
||||
spmHandler.Cancel()
|
||||
return nil
|
||||
}
|
||||
spmHandler.Acquire()
|
||||
go func(httpRequest *generatedRequest) {
|
||||
defer spmHandler.Release()
|
||||
if spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(updatedInput) || spmHandler.Cancelled() {
|
||||
return
|
||||
select {
|
||||
case <-spmHandler.Done():
|
||||
close(tasks)
|
||||
workersWg.Wait()
|
||||
spmHandler.Wait()
|
||||
if spmHandler.FoundFirstMatch() {
|
||||
return nil
|
||||
}
|
||||
// putting ratelimiter here prevents any unnecessary waiting if any
|
||||
request.options.RateLimitTake()
|
||||
|
||||
// after ratelimit take, check if we need to stop
|
||||
if spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(updatedInput) || spmHandler.Cancelled() {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-spmHandler.Done():
|
||||
return
|
||||
case spmHandler.ResultChan <- request.executeRequest(input, httpRequest, make(map[string]interface{}), false, wrappedCallback, 0):
|
||||
return
|
||||
}
|
||||
}(generatedHttpRequest)
|
||||
return multierr.Combine(spmHandler.CombinedResults()...)
|
||||
case tasks <- task{req: generatedHttpRequest, updatedInput: updatedInput}:
|
||||
}
|
||||
request.options.Progress.IncrementRequests()
|
||||
}
|
||||
close(tasks)
|
||||
workersWg.Wait()
|
||||
spmHandler.Wait()
|
||||
if spmHandler.FoundFirstMatch() {
|
||||
// ignore any context cancellation and in-transit execution errors
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -257,3 +258,116 @@ func TestReqURLPattern(t *testing.T) {
|
||||
require.NotEmpty(t, finalEvent.Results[0].ReqURLPattern, "could not get req url pattern")
|
||||
require.Equal(t, `/{{rand_char("abc")}}/{{interactsh-url}}/123?query={{rand_int(1, 10)}}&data={{randstr}}`, finalEvent.Results[0].ReqURLPattern)
|
||||
}
|
||||
|
||||
// fakeHostErrorsCache implements hosterrorscache.CacheInterface minimally for tests
|
||||
type fakeHostErrorsCache struct{}
|
||||
|
||||
func (f *fakeHostErrorsCache) SetVerbose(bool) {}
|
||||
func (f *fakeHostErrorsCache) Close() {}
|
||||
func (f *fakeHostErrorsCache) Remove(*contextargs.Context) {}
|
||||
func (f *fakeHostErrorsCache) MarkFailed(string, *contextargs.Context, error) {}
|
||||
func (f *fakeHostErrorsCache) MarkFailedOrRemove(string, *contextargs.Context, error) {
|
||||
}
|
||||
|
||||
// Check always returns true to simulate an already unresponsive host
|
||||
func (f *fakeHostErrorsCache) Check(string, *contextargs.Context) bool { return true }
|
||||
|
||||
func TestExecuteParallelHTTP_StopAtFirstMatch(t *testing.T) {
|
||||
options := testutils.DefaultOptions
|
||||
testutils.Init(options)
|
||||
|
||||
// server that always matches
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = fmt.Fprintf(w, "match")
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
templateID := "parallel-stop-first"
|
||||
req := &Request{
|
||||
ID: templateID,
|
||||
Method: HTTPMethodTypeHolder{MethodType: HTTPGet},
|
||||
Path: []string{"{{BaseURL}}/p?x={{v}}"},
|
||||
Threads: 2,
|
||||
Payloads: map[string]interface{}{
|
||||
"v": []string{"1", "2"},
|
||||
},
|
||||
Operators: operators.Operators{
|
||||
Matchers: []*matchers.Matcher{{
|
||||
Part: "body",
|
||||
Type: matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},
|
||||
Words: []string{"match"},
|
||||
}},
|
||||
},
|
||||
StopAtFirstMatch: true,
|
||||
}
|
||||
|
||||
executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{
|
||||
ID: templateID,
|
||||
Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"},
|
||||
})
|
||||
err := req.Compile(executerOpts)
|
||||
require.NoError(t, err)
|
||||
|
||||
var matches int32
|
||||
metadata := make(output.InternalEvent)
|
||||
previous := make(output.InternalEvent)
|
||||
ctxArgs := contextargs.NewWithInput(context.Background(), ts.URL)
|
||||
err = req.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {
|
||||
if event.OperatorsResult != nil && event.OperatorsResult.Matched {
|
||||
atomic.AddInt32(&matches, 1)
|
||||
}
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int32(1), atomic.LoadInt32(&matches), "expected only first match to be processed")
|
||||
}
|
||||
|
||||
func TestExecuteParallelHTTP_SkipOnUnresponsiveFromCache(t *testing.T) {
|
||||
options := testutils.DefaultOptions
|
||||
testutils.Init(options)
|
||||
|
||||
// server that would match if reached
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = fmt.Fprintf(w, "match")
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
templateID := "parallel-skip-unresponsive"
|
||||
req := &Request{
|
||||
ID: templateID,
|
||||
Method: HTTPMethodTypeHolder{MethodType: HTTPGet},
|
||||
Path: []string{"{{BaseURL}}/p?x={{v}}"},
|
||||
Threads: 2,
|
||||
Payloads: map[string]interface{}{
|
||||
"v": []string{"1", "2"},
|
||||
},
|
||||
Operators: operators.Operators{
|
||||
Matchers: []*matchers.Matcher{{
|
||||
Part: "body",
|
||||
Type: matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},
|
||||
Words: []string{"match"},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{
|
||||
ID: templateID,
|
||||
Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"},
|
||||
})
|
||||
// inject fake host errors cache that forces skip
|
||||
executerOpts.HostErrorsCache = &fakeHostErrorsCache{}
|
||||
|
||||
err := req.Compile(executerOpts)
|
||||
require.NoError(t, err)
|
||||
|
||||
var matches int32
|
||||
metadata := make(output.InternalEvent)
|
||||
previous := make(output.InternalEvent)
|
||||
ctxArgs := contextargs.NewWithInput(context.Background(), ts.URL)
|
||||
err = req.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {
|
||||
if event.OperatorsResult != nil && event.OperatorsResult.Matched {
|
||||
atomic.AddInt32(&matches, 1)
|
||||
}
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int32(0), atomic.LoadInt32(&matches), "expected no matches when host is marked unresponsive")
|
||||
}
|
||||
@@ -811,8 +811,11 @@ func beautifyJavascript(code string) string {
|
||||
}
|
||||
|
||||
func prettyPrint(templateId string, buff string) {
|
||||
if buff == "" {
|
||||
return
|
||||
}
|
||||
lines := strings.Split(buff, "\n")
|
||||
final := []string{}
|
||||
final := make([]string, 0, len(lines))
|
||||
for _, v := range lines {
|
||||
if v != "" {
|
||||
final = append(final, "\t"+v)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
id: oracle-auth-test
|
||||
|
||||
info:
|
||||
name: Oracle - Authentication Test
|
||||
author: pdteam
|
||||
severity: info
|
||||
tags: js,oracle,network,auth
|
||||
|
||||
javascript:
|
||||
- pre-condition: |
|
||||
isPortOpen(Host,Port);
|
||||
code: |
|
||||
let o = require('nuclei/oracle');
|
||||
let c = o.OracleClient();
|
||||
c.Connect(Host, Port, ServiceName, User, Pass);
|
||||
|
||||
args:
|
||||
ServiceName: "XE"
|
||||
Host: "{{Host}}"
|
||||
Port: "1521"
|
||||
User: "system"
|
||||
Pass: "{{passwords}}"
|
||||
payloads:
|
||||
passwords:
|
||||
- mysecret
|
||||
matchers:
|
||||
- type: dsl
|
||||
dsl:
|
||||
- "response == true"
|
||||
@@ -1,6 +1,7 @@
|
||||
package reporting
|
||||
|
||||
import (
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/es"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/jsonexporter"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/jsonl"
|
||||
@@ -23,6 +24,8 @@ type Options struct {
|
||||
AllowList *filters.Filter `yaml:"allow-list"`
|
||||
// DenyList contains a list of denied events for reporting module
|
||||
DenyList *filters.Filter `yaml:"deny-list"`
|
||||
// ValidatorCallback is a callback function that is called to validate an event before it is reported
|
||||
ValidatorCallback func(event *output.ResultEvent) bool `yaml:"-"`
|
||||
// GitHub contains configuration options for GitHub Issue Tracker
|
||||
GitHub *github.Options `yaml:"github"`
|
||||
// GitLab contains configuration options for GitLab Issue Tracker
|
||||
|
||||
@@ -192,11 +192,13 @@ func New(options *Options, db string, doNotDedupe bool) (Client, error) {
|
||||
}
|
||||
}
|
||||
|
||||
storage, err := dedupe.New(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if db != "" || len(client.trackers) > 0 || len(client.exporters) > 0 {
|
||||
storage, err := dedupe.New(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client.dedupe = storage
|
||||
}
|
||||
client.dedupe = storage
|
||||
return client, nil
|
||||
}
|
||||
|
||||
@@ -288,6 +290,10 @@ func (c *ReportingClient) CreateIssue(event *output.ResultEvent) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.options.ValidatorCallback != nil && !c.options.ValidatorCallback(event) {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
unique := true
|
||||
if c.dedupe != nil {
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
package jira
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
|
||||
"github.com/andygrunwald/go-jira"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/trivago/tgo/tcontainer"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
||||
@@ -25,6 +29,120 @@ type Formatter struct {
|
||||
util.MarkdownFormatter
|
||||
}
|
||||
|
||||
// TemplateContext holds the data available for template evaluation
|
||||
type TemplateContext struct {
|
||||
Severity string
|
||||
Name string
|
||||
Host string
|
||||
CVSSScore string
|
||||
CVEID string
|
||||
CWEID string
|
||||
CVSSMetrics string
|
||||
Tags []string
|
||||
}
|
||||
|
||||
// buildTemplateContext creates a template context from a ResultEvent
|
||||
func buildTemplateContext(event *output.ResultEvent) *TemplateContext {
|
||||
ctx := &TemplateContext{
|
||||
Host: event.Host,
|
||||
Name: event.Info.Name,
|
||||
Tags: event.Info.Tags.ToSlice(),
|
||||
}
|
||||
|
||||
// Set severity string
|
||||
ctx.Severity = event.Info.SeverityHolder.Severity.String()
|
||||
|
||||
if event.Info.Classification != nil {
|
||||
ctx.CVSSScore = fmt.Sprintf("%.2f", ptr.Safe(event.Info.Classification).CVSSScore)
|
||||
ctx.CVEID = strings.Join(ptr.Safe(event.Info.Classification).CVEID.ToSlice(), ", ")
|
||||
ctx.CWEID = strings.Join(ptr.Safe(event.Info.Classification).CWEID.ToSlice(), ", ")
|
||||
ctx.CVSSMetrics = ptr.Safe(event.Info.Classification).CVSSMetrics
|
||||
}
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
// evaluateTemplate executes a template string with the given context
|
||||
func evaluateTemplate(templateStr string, ctx *TemplateContext) (string, error) {
|
||||
// If no template markers found, return as-is for backward compatibility
|
||||
if !strings.Contains(templateStr, "{{") {
|
||||
return templateStr, nil
|
||||
}
|
||||
|
||||
// Create template with useful functions for JIRA custom fields
|
||||
funcMap := template.FuncMap{
|
||||
"upper": strings.ToUpper,
|
||||
"lower": strings.ToLower,
|
||||
"title": cases.Title(language.English).String,
|
||||
"contains": strings.Contains,
|
||||
"hasPrefix": strings.HasPrefix,
|
||||
"hasSuffix": strings.HasSuffix,
|
||||
"trim": strings.Trim,
|
||||
"trimSpace": strings.TrimSpace,
|
||||
"replace": strings.ReplaceAll,
|
||||
"split": strings.Split,
|
||||
"join": strings.Join,
|
||||
}
|
||||
|
||||
tmpl, err := template.New("field").Funcs(funcMap).Parse(templateStr)
|
||||
if err != nil {
|
||||
return templateStr, fmt.Errorf("failed to parse template: %w", err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := tmpl.Execute(&buf, ctx); err != nil {
|
||||
return templateStr, fmt.Errorf("failed to execute template: %w", err)
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// evaluateCustomFieldValue evaluates a custom field value, supporting both new template syntax and legacy $variable syntax
|
||||
func (i *Integration) evaluateCustomFieldValue(value string, templateCtx *TemplateContext, event *output.ResultEvent) (interface{}, error) {
|
||||
// Try template evaluation first (supports {{...}} syntax)
|
||||
if strings.Contains(value, "{{") {
|
||||
return evaluateTemplate(value, templateCtx)
|
||||
}
|
||||
|
||||
// Handle legacy $variable syntax for backward compatibility
|
||||
if strings.HasPrefix(value, "$") {
|
||||
variableName := strings.TrimPrefix(value, "$")
|
||||
switch variableName {
|
||||
case "CVSSMetrics":
|
||||
if event.Info.Classification != nil {
|
||||
return ptr.Safe(event.Info.Classification).CVSSMetrics, nil
|
||||
}
|
||||
return "", nil
|
||||
case "CVEID":
|
||||
if event.Info.Classification != nil {
|
||||
return strings.Join(ptr.Safe(event.Info.Classification).CVEID.ToSlice(), ", "), nil
|
||||
}
|
||||
return "", nil
|
||||
case "CWEID":
|
||||
if event.Info.Classification != nil {
|
||||
return strings.Join(ptr.Safe(event.Info.Classification).CWEID.ToSlice(), ", "), nil
|
||||
}
|
||||
return "", nil
|
||||
case "CVSSScore":
|
||||
if event.Info.Classification != nil {
|
||||
return fmt.Sprintf("%.2f", ptr.Safe(event.Info.Classification).CVSSScore), nil
|
||||
}
|
||||
return "", nil
|
||||
case "Host":
|
||||
return event.Host, nil
|
||||
case "Severity":
|
||||
return event.Info.SeverityHolder.Severity.String(), nil
|
||||
case "Name":
|
||||
return event.Info.Name, nil
|
||||
default:
|
||||
return value, nil // return as-is if variable not found
|
||||
}
|
||||
}
|
||||
|
||||
// Return as-is if no template or variable syntax found
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (jiraFormatter *Formatter) MakeBold(text string) string {
|
||||
return "*" + text + "*"
|
||||
}
|
||||
@@ -155,12 +273,12 @@ func (i *Integration) CreateNewIssue(event *output.ResultEvent) (*filters.Create
|
||||
if label := i.options.IssueType; label != "" {
|
||||
labels = append(labels, label)
|
||||
}
|
||||
// for each custom value, take the name of the custom field and
|
||||
// set the value of the custom field to the value specified in the
|
||||
// configuration options
|
||||
// Build template context for evaluating custom field templates
|
||||
templateCtx := buildTemplateContext(event)
|
||||
|
||||
// Process custom fields with template evaluation support
|
||||
customFields := tcontainer.NewMarshalMap()
|
||||
for name, value := range i.options.CustomFields {
|
||||
//customFields[name] = map[string]interface{}{"value": value}
|
||||
if valueMap, ok := value.(map[interface{}]interface{}); ok {
|
||||
// Iterate over nested map
|
||||
for nestedName, nestedValue := range valueMap {
|
||||
@@ -168,32 +286,21 @@ func (i *Integration) CreateNewIssue(event *output.ResultEvent) (*filters.Create
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`couldn't iterate on nested item "%s": %s`, nestedName, nestedValue)
|
||||
}
|
||||
if strings.HasPrefix(fmtNestedValue, "$") {
|
||||
nestedValue = strings.TrimPrefix(fmtNestedValue, "$")
|
||||
switch nestedValue {
|
||||
case "CVSSMetrics":
|
||||
nestedValue = ptr.Safe(event.Info.Classification).CVSSMetrics
|
||||
case "CVEID":
|
||||
nestedValue = ptr.Safe(event.Info.Classification).CVEID
|
||||
case "CWEID":
|
||||
nestedValue = ptr.Safe(event.Info.Classification).CWEID
|
||||
case "CVSSScore":
|
||||
nestedValue = ptr.Safe(event.Info.Classification).CVSSScore
|
||||
case "Host":
|
||||
nestedValue = event.Host
|
||||
case "Severity":
|
||||
nestedValue = event.Info.SeverityHolder
|
||||
case "Name":
|
||||
nestedValue = event.Info.Name
|
||||
}
|
||||
|
||||
// Evaluate template or handle legacy $variable syntax
|
||||
evaluatedValue, err := i.evaluateCustomFieldValue(fmtNestedValue, templateCtx, event)
|
||||
if err != nil {
|
||||
gologger.Warning().Msgf("Failed to evaluate template for field %s.%s: %v", name, nestedName, err)
|
||||
evaluatedValue = fmtNestedValue // fallback to original value
|
||||
}
|
||||
|
||||
switch nestedName {
|
||||
case "id":
|
||||
customFields[name] = map[string]interface{}{"id": nestedValue}
|
||||
customFields[name] = map[string]interface{}{"id": evaluatedValue}
|
||||
case "name":
|
||||
customFields[name] = map[string]interface{}{"value": nestedValue}
|
||||
customFields[name] = map[string]interface{}{"value": evaluatedValue}
|
||||
case "freeform":
|
||||
customFields[name] = nestedValue
|
||||
customFields[name] = evaluatedValue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -330,11 +437,55 @@ func (i *Integration) FindExistingIssue(event *output.ResultEvent, useStatus boo
|
||||
jql = fmt.Sprintf("%s AND status != \"%s\"", jql, i.options.StatusNot)
|
||||
}
|
||||
|
||||
searchOptions := &jira.SearchOptions{
|
||||
MaxResults: 1, // if any issue exists, then we won't create a new one
|
||||
// Hotfix for Jira Cloud: use Enhanced Search API (v3) to avoid deprecated v2 path
|
||||
if i.options.Cloud {
|
||||
params := url.Values{}
|
||||
params.Set("jql", jql)
|
||||
params.Set("maxResults", "1")
|
||||
params.Set("fields", "id,key")
|
||||
|
||||
req, err := i.jira.NewRequest("GET", "/rest/api/3/search/jql"+"?"+params.Encode(), nil)
|
||||
if err != nil {
|
||||
return jira.Issue{}, err
|
||||
}
|
||||
|
||||
var searchResult struct {
|
||||
Issues []struct {
|
||||
ID string `json:"id"`
|
||||
Key string `json:"key"`
|
||||
} `json:"issues"`
|
||||
IsLast bool `json:"isLast"`
|
||||
NextPageToken string `json:"nextPageToken"`
|
||||
}
|
||||
|
||||
resp, err := i.jira.Do(req, &searchResult)
|
||||
if err != nil {
|
||||
var data string
|
||||
if resp != nil && resp.Body != nil {
|
||||
d, _ := io.ReadAll(resp.Body)
|
||||
data = string(d)
|
||||
}
|
||||
return jira.Issue{}, fmt.Errorf("%w => %s", err, data)
|
||||
}
|
||||
|
||||
if len(searchResult.Issues) == 0 {
|
||||
return jira.Issue{}, nil
|
||||
}
|
||||
first := searchResult.Issues[0]
|
||||
base := strings.TrimRight(i.options.URL, "/")
|
||||
return jira.Issue{
|
||||
ID: first.ID,
|
||||
Key: first.Key,
|
||||
Self: fmt.Sprintf("%s/rest/api/3/issue/%s", base, first.ID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
chunk, resp, err := i.jira.Issue.Search(jql, searchOptions)
|
||||
searchOptions := &jira.SearchOptionsV2{
|
||||
MaxResults: 1, // if any issue exists, then we won't create a new one
|
||||
Fields: []string{"summary", "description", "issuetype", "status", "priority", "project"},
|
||||
}
|
||||
|
||||
issues, resp, err := i.jira.Issue.SearchV2JQL(jql, searchOptions)
|
||||
if err != nil {
|
||||
var data string
|
||||
if resp != nil && resp.Body != nil {
|
||||
@@ -348,10 +499,10 @@ func (i *Integration) FindExistingIssue(event *output.ResultEvent, useStatus boo
|
||||
case 0:
|
||||
return jira.Issue{}, nil
|
||||
case 1:
|
||||
return chunk[0], nil
|
||||
return issues[0], nil
|
||||
default:
|
||||
gologger.Warning().Msgf("Discovered multiple opened issues %s for the host %s: The issue [%s] will be updated.", template, event.Host, chunk[0].ID)
|
||||
return chunk[0], nil
|
||||
gologger.Warning().Msgf("Discovered multiple opened issues %s for the host %s: The issue [%s] will be updated.", template, event.Host, issues[0].ID)
|
||||
return issues[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,33 @@
|
||||
package jira
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/model"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters"
|
||||
"github.com/projectdiscovery/retryablehttp-go"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type recordingTransport struct {
|
||||
inner http.RoundTripper
|
||||
paths []string
|
||||
}
|
||||
|
||||
func (rt *recordingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
if rt.inner == nil {
|
||||
rt.inner = http.DefaultTransport
|
||||
}
|
||||
rt.paths = append(rt.paths, req.URL.Path)
|
||||
return rt.inner.RoundTrip(req)
|
||||
}
|
||||
|
||||
func TestLinkCreation(t *testing.T) {
|
||||
jiraIntegration := &Integration{}
|
||||
link := jiraIntegration.CreateLink("ProjectDiscovery", "https://projectdiscovery.io")
|
||||
@@ -70,3 +87,184 @@ func Test_ShouldFilter_Tracker(t *testing.T) {
|
||||
}}))
|
||||
})
|
||||
}
|
||||
|
||||
func TestTemplateEvaluation(t *testing.T) {
|
||||
event := &output.ResultEvent{
|
||||
Host: "example.com",
|
||||
Info: model.Info{
|
||||
Name: "Test vulnerability",
|
||||
SeverityHolder: severity.Holder{Severity: severity.Critical},
|
||||
Classification: &model.Classification{
|
||||
CVSSScore: 9.8,
|
||||
CVEID: stringslice.StringSlice{Value: []string{"CVE-2023-1234"}},
|
||||
CWEID: stringslice.StringSlice{Value: []string{"CWE-79"}},
|
||||
CVSSMetrics: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
integration := &Integration{}
|
||||
|
||||
t.Run("conditional template", func(t *testing.T) {
|
||||
templateStr := `{{if eq .Severity "critical"}}11187{{else if eq .Severity "high"}}11186{{else if eq .Severity "medium"}}11185{{else}}11184{{end}}`
|
||||
result, err := integration.evaluateCustomFieldValue(templateStr, buildTemplateContext(event), event)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "11187", result)
|
||||
})
|
||||
|
||||
t.Run("freeform description template", func(t *testing.T) {
|
||||
templateStr := `Vulnerability detected by Nuclei. Name: {{.Name}}, Severity: {{.Severity}}, Host: {{.Host}}`
|
||||
result, err := integration.evaluateCustomFieldValue(templateStr, buildTemplateContext(event), event)
|
||||
require.NoError(t, err)
|
||||
expected := "Vulnerability detected by Nuclei. Name: Test vulnerability, Severity: critical, Host: example.com"
|
||||
require.Equal(t, expected, result)
|
||||
})
|
||||
|
||||
t.Run("legacy variable syntax", func(t *testing.T) {
|
||||
result, err := integration.evaluateCustomFieldValue("$Severity", buildTemplateContext(event), event)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "critical", result)
|
||||
|
||||
result, err = integration.evaluateCustomFieldValue("$Host", buildTemplateContext(event), event)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "example.com", result)
|
||||
})
|
||||
|
||||
t.Run("complex template with conditionals", func(t *testing.T) {
|
||||
templateStr := `{{.Name}} on {{.Host}}
|
||||
{{if .CVSSScore}}CVSS: {{.CVSSScore}}{{end}}
|
||||
{{if eq .Severity "critical"}}⚠️ CRITICAL{{else}}Standard{{end}}`
|
||||
result, err := integration.evaluateCustomFieldValue(templateStr, buildTemplateContext(event), event)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, result, "Test vulnerability on example.com")
|
||||
require.Contains(t, result, "CVSS: 9.80")
|
||||
require.Contains(t, result, "⚠️ CRITICAL")
|
||||
})
|
||||
|
||||
t.Run("no template syntax", func(t *testing.T) {
|
||||
result, err := integration.evaluateCustomFieldValue("plain text", buildTemplateContext(event), event)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "plain text", result)
|
||||
})
|
||||
|
||||
t.Run("template functions", func(t *testing.T) {
|
||||
// Test case conversion functions
|
||||
result, err := integration.evaluateCustomFieldValue("{{.Severity | upper}}", buildTemplateContext(event), event)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "CRITICAL", result)
|
||||
|
||||
result, err = integration.evaluateCustomFieldValue("{{.Name | lower}}", buildTemplateContext(event), event)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "test vulnerability", result)
|
||||
|
||||
result, err = integration.evaluateCustomFieldValue("{{.Name | title}}", buildTemplateContext(event), event)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Test Vulnerability", result)
|
||||
|
||||
// Test string check functions
|
||||
result, err = integration.evaluateCustomFieldValue(`{{if contains .Name "Test"}}has-test{{else}}no-test{{end}}`, buildTemplateContext(event), event)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "has-test", result)
|
||||
|
||||
result, err = integration.evaluateCustomFieldValue(`{{if hasPrefix .Host "example"}}starts-with-example{{else}}other{{end}}`, buildTemplateContext(event), event)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "starts-with-example", result)
|
||||
|
||||
result, err = integration.evaluateCustomFieldValue(`{{if hasSuffix .Host ".com"}}ends-with-com{{else}}other{{end}}`, buildTemplateContext(event), event)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "ends-with-com", result)
|
||||
|
||||
// Test string manipulation functions
|
||||
result, err = integration.evaluateCustomFieldValue(`{{replace .Name " " "-"}}`, buildTemplateContext(event), event)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Test-vulnerability", result)
|
||||
|
||||
result, err = integration.evaluateCustomFieldValue(`{{trimSpace " test "}}`, buildTemplateContext(event), event)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "test", result)
|
||||
|
||||
result, err = integration.evaluateCustomFieldValue(`{{trim "...test..." "."}}`, buildTemplateContext(event), event)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "test", result)
|
||||
|
||||
// Test split and join functions
|
||||
result, err = integration.evaluateCustomFieldValue(`{{join (split .Name " ") "-"}}`, buildTemplateContext(event), event)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Test-vulnerability", result)
|
||||
})
|
||||
|
||||
t.Run("complex template with functions", func(t *testing.T) {
|
||||
templateStr := `{{.Name | upper}} on {{.Host}}
|
||||
{{if contains .Name "SQL"}}SQL-INJECTION{{else if contains .Name "XSS"}}XSS-ATTACK{{else}}OTHER{{end}}
|
||||
Priority: {{if eq .Severity "critical"}}{{.Severity | upper}}{{else}}{{.Severity}}{{end}}`
|
||||
result, err := integration.evaluateCustomFieldValue(templateStr, buildTemplateContext(event), event)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, result, "TEST VULNERABILITY on example.com", result)
|
||||
require.Contains(t, result, "OTHER")
|
||||
require.Contains(t, result, "CRITICAL")
|
||||
})
|
||||
}
|
||||
|
||||
// Live test to verify SearchV2JQL hits /rest/api/3/search/jql when creds are provided via env
|
||||
func TestJiraLive_SearchV2UsesJqlEndpoint(t *testing.T) {
|
||||
jiraURL := os.Getenv("JIRA_URL")
|
||||
jiraEmail := os.Getenv("JIRA_EMAIL")
|
||||
jiraAccountID := os.Getenv("JIRA_ACCOUNT_ID")
|
||||
jiraToken := os.Getenv("JIRA_TOKEN")
|
||||
jiraPAT := os.Getenv("JIRA_PAT")
|
||||
jiraProjectName := os.Getenv("JIRA_PROJECT_NAME")
|
||||
jiraProjectID := os.Getenv("JIRA_PROJECT_ID")
|
||||
jiraStatusNot := os.Getenv("JIRA_STATUS_NOT")
|
||||
jiraCloud := os.Getenv("JIRA_CLOUD")
|
||||
|
||||
if jiraURL == "" || (jiraPAT == "" && jiraToken == "") || (jiraEmail == "" && jiraAccountID == "") || (jiraProjectName == "" && jiraProjectID == "") {
|
||||
t.Skip("live Jira test skipped: missing JIRA_* env vars")
|
||||
}
|
||||
|
||||
statusNot := jiraStatusNot
|
||||
if statusNot == "" {
|
||||
statusNot = "Done"
|
||||
}
|
||||
|
||||
isCloud := !strings.EqualFold(jiraCloud, "false") && jiraCloud != "0"
|
||||
|
||||
rec := &recordingTransport{}
|
||||
rc := retryablehttp.NewClient(retryablehttp.DefaultOptionsSingle)
|
||||
rc.HTTPClient.Transport = rec
|
||||
|
||||
opts := &Options{
|
||||
Cloud: isCloud,
|
||||
URL: jiraURL,
|
||||
Email: jiraEmail,
|
||||
AccountID: jiraAccountID,
|
||||
Token: jiraToken,
|
||||
PersonalAccessToken: jiraPAT,
|
||||
ProjectName: jiraProjectName,
|
||||
ProjectID: jiraProjectID,
|
||||
IssueType: "Task",
|
||||
StatusNot: statusNot,
|
||||
HttpClient: rc,
|
||||
}
|
||||
|
||||
integration, err := New(opts)
|
||||
require.NoError(t, err)
|
||||
|
||||
event := &output.ResultEvent{
|
||||
Host: "example.com",
|
||||
Info: model.Info{
|
||||
Name: "Nuclei Live Verify",
|
||||
SeverityHolder: severity.Holder{Severity: severity.Low},
|
||||
},
|
||||
}
|
||||
|
||||
_, _ = integration.FindExistingIssue(event, true)
|
||||
|
||||
var hitSearchV2 bool
|
||||
for _, p := range rec.paths {
|
||||
if strings.HasSuffix(p, "/rest/api/3/search/jql") {
|
||||
hitSearchV2 = true
|
||||
break
|
||||
}
|
||||
}
|
||||
require.True(t, hitSearchV2, "expected client to call /rest/api/3/search/jql, got paths: %v", rec.paths)
|
||||
}
|
||||
+28
-2
@@ -12,7 +12,9 @@ type Cache struct {
|
||||
|
||||
// New returns a new templates cache
|
||||
func NewCache() *Cache {
|
||||
return &Cache{items: mapsutil.NewSyncLockMap[string, parsedTemplate]()}
|
||||
return &Cache{
|
||||
items: mapsutil.NewSyncLockMap[string, parsedTemplate](),
|
||||
}
|
||||
}
|
||||
|
||||
type parsedTemplate struct {
|
||||
@@ -33,7 +35,31 @@ func (t *Cache) Has(template string) (*Template, []byte, error) {
|
||||
|
||||
// Store stores a template with data and error
|
||||
func (t *Cache) Store(id string, tpl *Template, raw []byte, err error) {
|
||||
_ = t.items.Set(id, parsedTemplate{template: tpl, raw: conversion.String(raw), err: err})
|
||||
entry := parsedTemplate{
|
||||
template: tpl,
|
||||
err: err,
|
||||
raw: conversion.String(raw),
|
||||
}
|
||||
_ = t.items.Set(id, entry)
|
||||
}
|
||||
|
||||
// StoreWithoutRaw stores a template without raw data for memory efficiency
|
||||
func (t *Cache) StoreWithoutRaw(id string, tpl *Template, err error) {
|
||||
entry := parsedTemplate{
|
||||
template: tpl,
|
||||
err: err,
|
||||
raw: "",
|
||||
}
|
||||
_ = t.items.Set(id, entry)
|
||||
}
|
||||
|
||||
// Get returns only the template without raw bytes
|
||||
func (t *Cache) Get(id string) (*Template, error) {
|
||||
value, ok := t.items.Get(id)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
return value.template, value.err
|
||||
}
|
||||
|
||||
// Purge the cache
|
||||
|
||||
@@ -404,7 +404,7 @@ func (template *Template) ImportFileRefs(options *protocols.ExecutorOptions) err
|
||||
// for code protocol requests
|
||||
for _, request := range template.RequestsCode {
|
||||
// simple test to check if source is a file or a snippet
|
||||
if len(strings.Split(request.Source, "\n")) == 1 && fileutil.FileExists(request.Source) {
|
||||
if !strings.ContainsRune(request.Source, '\n') && fileutil.FileExists(request.Source) {
|
||||
if val, ok := loadFile(request.Source); ok {
|
||||
template.ImportedFiles = append(template.ImportedFiles, request.Source)
|
||||
request.Source = val
|
||||
@@ -415,7 +415,7 @@ func (template *Template) ImportFileRefs(options *protocols.ExecutorOptions) err
|
||||
// for javascript protocol code references
|
||||
for _, request := range template.RequestsJavascript {
|
||||
// simple test to check if source is a file or a snippet
|
||||
if len(strings.Split(request.Code, "\n")) == 1 && fileutil.FileExists(request.Code) {
|
||||
if !strings.ContainsRune(request.Code, '\n') && fileutil.FileExists(request.Code) {
|
||||
if val, ok := loadFile(request.Code); ok {
|
||||
template.ImportedFiles = append(template.ImportedFiles, request.Code)
|
||||
request.Code = val
|
||||
@@ -442,7 +442,7 @@ func (template *Template) ImportFileRefs(options *protocols.ExecutorOptions) err
|
||||
if req.Type() == types.CodeProtocol {
|
||||
request := req.(*code.Request)
|
||||
// simple test to check if source is a file or a snippet
|
||||
if len(strings.Split(request.Source, "\n")) == 1 && fileutil.FileExists(request.Source) {
|
||||
if !strings.ContainsRune(request.Source, '\n') && fileutil.FileExists(request.Source) {
|
||||
if val, ok := loadFile(request.Source); ok {
|
||||
template.ImportedFiles = append(template.ImportedFiles, request.Source)
|
||||
request.Source = val
|
||||
@@ -456,7 +456,7 @@ func (template *Template) ImportFileRefs(options *protocols.ExecutorOptions) err
|
||||
if req.Type() == types.JavascriptProtocol {
|
||||
request := req.(*javascript.Request)
|
||||
// simple test to check if source is a file or a snippet
|
||||
if len(strings.Split(request.Code, "\n")) == 1 && fileutil.FileExists(request.Code) {
|
||||
if !strings.ContainsRune(request.Code, '\n') && fileutil.FileExists(request.Code) {
|
||||
if val, ok := loadFile(request.Code); ok {
|
||||
template.ImportedFiles = append(template.ImportedFiles, request.Code)
|
||||
request.Code = val
|
||||
|
||||
@@ -9,6 +9,32 @@ import (
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func TestCachePoolZeroing(t *testing.T) {
|
||||
c := NewCache()
|
||||
|
||||
tpl := &Template{ID: "x"}
|
||||
raw := []byte("SOME BIG RAW")
|
||||
|
||||
c.Store("id1", tpl, raw, nil)
|
||||
gotTpl, gotErr := c.Get("id1")
|
||||
if gotErr != nil {
|
||||
t.Fatalf("unexpected err: %v", gotErr)
|
||||
}
|
||||
if gotTpl == nil || gotTpl.ID != "x" {
|
||||
t.Fatalf("unexpected tpl: %#v", gotTpl)
|
||||
}
|
||||
|
||||
// StoreWithoutRaw should not retain raw
|
||||
c.StoreWithoutRaw("id2", tpl, nil)
|
||||
gotTpl2, gotErr2 := c.Get("id2")
|
||||
if gotErr2 != nil {
|
||||
t.Fatalf("unexpected err: %v", gotErr2)
|
||||
}
|
||||
if gotTpl2 == nil || gotTpl2.ID != "x" {
|
||||
t.Fatalf("unexpected tpl2: %#v", gotTpl2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateStruct(t *testing.T) {
|
||||
templatePath := "./tests/match-1.yaml"
|
||||
bin, err := os.ReadFile(templatePath)
|
||||
|
||||
@@ -421,6 +421,10 @@ type Options struct {
|
||||
FormatUseRequiredOnly bool
|
||||
// SkipFormatValidation is used to skip format validation
|
||||
SkipFormatValidation bool
|
||||
// VarsTextTemplating is used to inject variables into yaml input files
|
||||
VarsTextTemplating bool
|
||||
// VarsFilePaths is used to inject variables into yaml input files from a file
|
||||
VarsFilePaths goflags.StringSlice
|
||||
// PayloadConcurrency is the number of concurrent payloads to run per template
|
||||
PayloadConcurrency int
|
||||
// ProbeConcurrency is the number of concurrent http probes to run with httpx
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cespare/xxhash"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
|
||||
"github.com/projectdiscovery/ratelimit"
|
||||
"github.com/projectdiscovery/retryablehttp-go"
|
||||
mapsutil "github.com/projectdiscovery/utils/maps"
|
||||
"golang.org/x/exp/constraints"
|
||||
@@ -71,3 +74,12 @@ func MapHash[K constraints.Ordered, V any](m map[K]V) uint64 {
|
||||
}
|
||||
return xxhash.Sum64([]byte(sb.String()))
|
||||
}
|
||||
|
||||
// GetRateLimiter returns a rate limiter with the given max tokens and duration
|
||||
// if maxTokens is 0 or duration is 0, it returns an unlimited rate limiter
|
||||
func GetRateLimiter(ctx context.Context, maxTokens int, duration time.Duration) *ratelimit.Limiter {
|
||||
if maxTokens == 0 || duration == 0 {
|
||||
return ratelimit.NewUnlimited(ctx)
|
||||
}
|
||||
return ratelimit.New(ctx, uint(maxTokens), duration)
|
||||
}
|
||||
Reference in new issue
Block a user