mirror of https://github.com/daffainfo/nuclei.git
add headless options flag (#3951)
* add headless options flag * disable some tests for windows * disable interactsh tests on darwin * disable network/hex.yaml on windows * make DisableOn funcdev
parent
6bdef68734
commit
163bc22281
|
@ -249,6 +249,7 @@ HEADLESS:
|
|||
-headless enable templates that require headless browser support (root user on Linux will disable sandbox)
|
||||
-page-timeout int seconds to wait for each page in headless mode (default 20)
|
||||
-sb, -show-browser show the browser on the screen when running templates with headless mode
|
||||
-ho, -headless-options string[] start headless chrome with additional options
|
||||
-sc, -system-chrome use local installed Chrome browser instead of nuclei installed
|
||||
-lha, -list-headless-action list available headless actions
|
||||
|
||||
|
|
|
@ -34,9 +34,9 @@ import (
|
|||
"github.com/projectdiscovery/ratelimit"
|
||||
)
|
||||
|
||||
var codeTestcases = map[string]testutils.TestCase{
|
||||
"code/test.yaml": &goIntegrationTest{},
|
||||
"code/test.json": &goIntegrationTest{},
|
||||
var codeTestcases = []TestCaseInfo{
|
||||
{Path: "code/test.yaml", TestCase: &goIntegrationTest{}},
|
||||
{Path: "code/test.json", TestCase: &goIntegrationTest{}},
|
||||
}
|
||||
|
||||
type goIntegrationTest struct{}
|
||||
|
|
|
@ -8,8 +8,8 @@ import (
|
|||
|
||||
type customConfigDirTest struct{}
|
||||
|
||||
var customConfigDirTestCases = map[string]testutils.TestCase{
|
||||
"dns/cname-fingerprint.yaml": &customConfigDirTest{},
|
||||
var customConfigDirTestCases = []TestCaseInfo{
|
||||
{Path: "dns/cname-fingerprint.yaml", TestCase: &customConfigDirTest{}},
|
||||
}
|
||||
|
||||
// Execute executes a test case and returns an error if occurred
|
||||
|
|
|
@ -4,14 +4,14 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
var dnsTestCases = map[string]testutils.TestCase{
|
||||
"dns/basic.yaml": &dnsBasic{},
|
||||
"dns/ptr.yaml": &dnsPtr{},
|
||||
"dns/caa.yaml": &dnsCAA{},
|
||||
"dns/tlsa.yaml": &dnsTLSA{},
|
||||
"dns/variables.yaml": &dnsVariables{},
|
||||
"dns/payload.yaml": &dnsPayload{},
|
||||
"dns/dsl-matcher-variable.yaml": &dnsDSLMatcherVariable{},
|
||||
var dnsTestCases = []TestCaseInfo{
|
||||
{Path: "dns/basic.yaml", TestCase: &dnsBasic{}},
|
||||
{Path: "dns/ptr.yaml", TestCase: &dnsPtr{}},
|
||||
{Path: "dns/caa.yaml", TestCase: &dnsCAA{}},
|
||||
{Path: "dns/tlsa.yaml", TestCase: &dnsTLSA{}},
|
||||
{Path: "dns/variables.yaml", TestCase: &dnsVariables{}},
|
||||
{Path: "dns/payload.yaml", TestCase: &dnsPayload{}},
|
||||
{Path: "dns/dsl-matcher-variable.yaml", TestCase: &dnsDSLMatcherVariable{}},
|
||||
}
|
||||
|
||||
type dnsBasic struct{}
|
||||
|
|
|
@ -4,11 +4,11 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
var fileTestcases = map[string]testutils.TestCase{
|
||||
"file/matcher-with-or.yaml": &fileWithOrMatcher{},
|
||||
"file/matcher-with-and.yaml": &fileWithAndMatcher{},
|
||||
"file/matcher-with-nested-and.yaml": &fileWithAndMatcher{},
|
||||
"file/extract.yaml": &fileWithExtractor{},
|
||||
var fileTestcases = []TestCaseInfo{
|
||||
{Path: "file/matcher-with-or.yaml", TestCase: &fileWithOrMatcher{}},
|
||||
{Path: "file/matcher-with-and.yaml", TestCase: &fileWithAndMatcher{}},
|
||||
{Path: "file/matcher-with-nested-and.yaml", TestCase: &fileWithAndMatcher{}},
|
||||
{Path: "file/extract.yaml", TestCase: &fileWithExtractor{}},
|
||||
}
|
||||
|
||||
type fileWithOrMatcher struct{}
|
||||
|
|
|
@ -12,11 +12,11 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
var fuzzingTestCases = map[string]testutils.TestCase{
|
||||
"fuzz/fuzz-mode.yaml": &fuzzModeOverride{},
|
||||
"fuzz/fuzz-type.yaml": &fuzzTypeOverride{},
|
||||
"fuzz/fuzz-query.yaml": &httpFuzzQuery{},
|
||||
"fuzz/fuzz-headless.yaml": &HeadlessFuzzingQuery{},
|
||||
var fuzzingTestCases = []TestCaseInfo{
|
||||
{Path: "fuzz/fuzz-mode.yaml", TestCase: &fuzzModeOverride{}},
|
||||
{Path: "fuzz/fuzz-type.yaml", TestCase: &fuzzTypeOverride{}},
|
||||
{Path: "fuzz/fuzz-query.yaml", TestCase: &httpFuzzQuery{}},
|
||||
{Path: "fuzz/fuzz-headless.yaml", TestCase: &HeadlessFuzzingQuery{}},
|
||||
}
|
||||
|
||||
type httpFuzzQuery struct{}
|
||||
|
|
|
@ -13,8 +13,8 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
var genericTestcases = map[string]testutils.TestCase{
|
||||
"generic/auth/certificate/http-get.yaml": &clientCertificate{},
|
||||
var genericTestcases = []TestCaseInfo{
|
||||
{Path: "generic/auth/certificate/http-get.yaml", TestCase: &clientCertificate{}},
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
|
@ -10,14 +10,14 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
var headlessTestcases = map[string]testutils.TestCase{
|
||||
"headless/headless-basic.yaml": &headlessBasic{},
|
||||
"headless/headless-header-action.yaml": &headlessHeaderActions{},
|
||||
"headless/headless-extract-values.yaml": &headlessExtractValues{},
|
||||
"headless/headless-payloads.yaml": &headlessPayloads{},
|
||||
"headless/variables.yaml": &headlessVariables{},
|
||||
"headless/file-upload.yaml": &headlessFileUpload{},
|
||||
"headless/headless-header-status-test.yaml": &headlessHeaderStatus{},
|
||||
var headlessTestcases = []TestCaseInfo{
|
||||
{Path: "headless/headless-basic.yaml", TestCase: &headlessBasic{}},
|
||||
{Path: "headless/headless-header-action.yaml", TestCase: &headlessHeaderActions{}},
|
||||
{Path: "headless/headless-extract-values.yaml", TestCase: &headlessExtractValues{}},
|
||||
{Path: "headless/headless-payloads.yaml", TestCase: &headlessPayloads{}},
|
||||
{Path: "headless/variables.yaml", TestCase: &headlessVariables{}},
|
||||
{Path: "headless/file-upload.yaml", TestCase: &headlessFileUpload{}},
|
||||
{Path: "headless/headless-header-status-test.yaml", TestCase: &headlessHeaderStatus{}},
|
||||
}
|
||||
|
||||
type headlessBasic struct{}
|
||||
|
|
|
@ -26,60 +26,60 @@ import (
|
|||
stringsutil "github.com/projectdiscovery/utils/strings"
|
||||
)
|
||||
|
||||
var httpTestcases = map[string]testutils.TestCase{
|
||||
var httpTestcases = []TestCaseInfo{
|
||||
// TODO: excluded due to parsing errors with console
|
||||
// "http/raw-unsafe-request.yaml": &httpRawUnsafeRequest{},
|
||||
"http/get-headers.yaml": &httpGetHeaders{},
|
||||
"http/get-query-string.yaml": &httpGetQueryString{},
|
||||
"http/get-redirects.yaml": &httpGetRedirects{},
|
||||
"http/get-host-redirects.yaml": &httpGetHostRedirects{},
|
||||
"http/disable-redirects.yaml": &httpDisableRedirects{},
|
||||
"http/get.yaml": &httpGet{},
|
||||
"http/post-body.yaml": &httpPostBody{},
|
||||
"http/post-json-body.yaml": &httpPostJSONBody{},
|
||||
"http/post-multipart-body.yaml": &httpPostMultipartBody{},
|
||||
"http/raw-cookie-reuse.yaml": &httpRawCookieReuse{},
|
||||
"http/raw-dynamic-extractor.yaml": &httpRawDynamicExtractor{},
|
||||
"http/raw-get-query.yaml": &httpRawGetQuery{},
|
||||
"http/raw-get.yaml": &httpRawGet{},
|
||||
"http/raw-with-params.yaml": &httpRawWithParams{},
|
||||
"http/raw-unsafe-with-params.yaml": &httpRawWithParams{}, // Not a typo, functionality is same as above
|
||||
"http/raw-path-trailing-slash.yaml": &httpRawPathTrailingSlash{},
|
||||
"http/raw-payload.yaml": &httpRawPayload{},
|
||||
"http/raw-post-body.yaml": &httpRawPostBody{},
|
||||
"http/raw-unsafe-path.yaml": &httpRawUnsafePath{},
|
||||
"http/http-paths.yaml": &httpPaths{},
|
||||
"http/request-condition.yaml": &httpRequestCondition{},
|
||||
"http/request-condition-new.yaml": &httpRequestCondition{},
|
||||
"http/self-contained.yaml": &httpRequestSelfContained{},
|
||||
"http/self-contained-with-path.yaml": &httpRequestSelfContained{}, // Not a typo, functionality is same as above
|
||||
"http/self-contained-with-params.yaml": &httpRequestSelfContainedWithParams{},
|
||||
"http/self-contained-file-input.yaml": &httpRequestSelfContainedFileInput{},
|
||||
"http/get-case-insensitive.yaml": &httpGetCaseInsensitive{},
|
||||
"http/get.yaml,http/get-case-insensitive.yaml": &httpGetCaseInsensitiveCluster{},
|
||||
"http/get-redirects-chain-headers.yaml": &httpGetRedirectsChainHeaders{},
|
||||
"http/dsl-matcher-variable.yaml": &httpDSLVariable{},
|
||||
"http/dsl-functions.yaml": &httpDSLFunctions{},
|
||||
"http/race-simple.yaml": &httpRaceSimple{},
|
||||
"http/race-multiple.yaml": &httpRaceMultiple{},
|
||||
"http/stop-at-first-match.yaml": &httpStopAtFirstMatch{},
|
||||
"http/stop-at-first-match-with-extractors.yaml": &httpStopAtFirstMatchWithExtractors{},
|
||||
"http/variables.yaml": &httpVariables{},
|
||||
"http/variable-dsl-function.yaml": &httpVariableDSLFunction{},
|
||||
"http/get-override-sni.yaml": &httpSniAnnotation{},
|
||||
"http/get-sni.yaml": &customCLISNI{},
|
||||
"http/redirect-match-url.yaml": &httpRedirectMatchURL{},
|
||||
"http/get-sni-unsafe.yaml": &customCLISNIUnsafe{},
|
||||
"http/annotation-timeout.yaml": &annotationTimeout{},
|
||||
"http/custom-attack-type.yaml": &customAttackType{},
|
||||
"http/get-all-ips.yaml": &scanAllIPS{},
|
||||
"http/get-without-scheme.yaml": &httpGetWithoutScheme{},
|
||||
"http/cl-body-without-header.yaml": &httpCLBodyWithoutHeader{},
|
||||
"http/cl-body-with-header.yaml": &httpCLBodyWithHeader{},
|
||||
"http/save-extractor-values-to-file.yaml": &httpSaveExtractorValuesToFile{},
|
||||
"http/cli-with-constants.yaml": &ConstantWithCliVar{},
|
||||
"http/matcher-status.yaml": &matcherStatusTest{},
|
||||
"http/disable-path-automerge.yaml": &httpDisablePathAutomerge{},
|
||||
{Path: "http/get-headers.yaml", TestCase: &httpGetHeaders{}},
|
||||
{Path: "http/get-query-string.yaml", TestCase: &httpGetQueryString{}},
|
||||
{Path: "http/get-redirects.yaml", TestCase: &httpGetRedirects{}},
|
||||
{Path: "http/get-host-redirects.yaml", TestCase: &httpGetHostRedirects{}},
|
||||
{Path: "http/disable-redirects.yaml", TestCase: &httpDisableRedirects{}},
|
||||
{Path: "http/get.yaml", TestCase: &httpGet{}},
|
||||
{Path: "http/post-body.yaml", TestCase: &httpPostBody{}},
|
||||
{Path: "http/post-json-body.yaml", TestCase: &httpPostJSONBody{}},
|
||||
{Path: "http/post-multipart-body.yaml", TestCase: &httpPostMultipartBody{}},
|
||||
{Path: "http/raw-cookie-reuse.yaml", TestCase: &httpRawCookieReuse{}},
|
||||
{Path: "http/raw-dynamic-extractor.yaml", TestCase: &httpRawDynamicExtractor{}},
|
||||
{Path: "http/raw-get-query.yaml", TestCase: &httpRawGetQuery{}},
|
||||
{Path: "http/raw-get.yaml", TestCase: &httpRawGet{}},
|
||||
{Path: "http/raw-with-params.yaml", TestCase: &httpRawWithParams{}},
|
||||
{Path: "http/raw-unsafe-with-params.yaml", TestCase: &httpRawWithParams{}}, // Not a typo, functionality is same as above
|
||||
{Path: "http/raw-path-trailing-slash.yaml", TestCase: &httpRawPathTrailingSlash{}},
|
||||
{Path: "http/raw-payload.yaml", TestCase: &httpRawPayload{}},
|
||||
{Path: "http/raw-post-body.yaml", TestCase: &httpRawPostBody{}},
|
||||
{Path: "http/raw-unsafe-path.yaml", TestCase: &httpRawUnsafePath{}},
|
||||
{Path: "http/http-paths.yaml", TestCase: &httpPaths{}},
|
||||
{Path: "http/request-condition.yaml", TestCase: &httpRequestCondition{}},
|
||||
{Path: "http/request-condition-new.yaml", TestCase: &httpRequestCondition{}},
|
||||
{Path: "http/self-contained.yaml", TestCase: &httpRequestSelfContained{}},
|
||||
{Path: "http/self-contained-with-path.yaml", TestCase: &httpRequestSelfContained{}}, // Not a typo, functionality is same as above
|
||||
{Path: "http/self-contained-with-params.yaml", TestCase: &httpRequestSelfContainedWithParams{}},
|
||||
{Path: "http/self-contained-file-input.yaml", TestCase: &httpRequestSelfContainedFileInput{}},
|
||||
{Path: "http/get-case-insensitive.yaml", TestCase: &httpGetCaseInsensitive{}},
|
||||
{Path: "http/get.yaml,http/get-case-insensitive.yaml", TestCase: &httpGetCaseInsensitiveCluster{}},
|
||||
{Path: "http/get-redirects-chain-headers.yaml", TestCase: &httpGetRedirectsChainHeaders{}},
|
||||
{Path: "http/dsl-matcher-variable.yaml", TestCase: &httpDSLVariable{}},
|
||||
{Path: "http/dsl-functions.yaml", TestCase: &httpDSLFunctions{}},
|
||||
{Path: "http/race-simple.yaml", TestCase: &httpRaceSimple{}},
|
||||
{Path: "http/race-multiple.yaml", TestCase: &httpRaceMultiple{}},
|
||||
{Path: "http/stop-at-first-match.yaml", TestCase: &httpStopAtFirstMatch{}},
|
||||
{Path: "http/stop-at-first-match-with-extractors.yaml", TestCase: &httpStopAtFirstMatchWithExtractors{}},
|
||||
{Path: "http/variables.yaml", TestCase: &httpVariables{}},
|
||||
{Path: "http/variable-dsl-function.yaml", TestCase: &httpVariableDSLFunction{}},
|
||||
{Path: "http/get-override-sni.yaml", TestCase: &httpSniAnnotation{}},
|
||||
{Path: "http/get-sni.yaml", TestCase: &customCLISNI{}},
|
||||
{Path: "http/redirect-match-url.yaml", TestCase: &httpRedirectMatchURL{}},
|
||||
{Path: "http/get-sni-unsafe.yaml", TestCase: &customCLISNIUnsafe{}},
|
||||
{Path: "http/annotation-timeout.yaml", TestCase: &annotationTimeout{}},
|
||||
{Path: "http/custom-attack-type.yaml", TestCase: &customAttackType{}},
|
||||
{Path: "http/get-all-ips.yaml", TestCase: &scanAllIPS{}},
|
||||
{Path: "http/get-without-scheme.yaml", TestCase: &httpGetWithoutScheme{}},
|
||||
{Path: "http/cl-body-without-header.yaml", TestCase: &httpCLBodyWithoutHeader{}},
|
||||
{Path: "http/cl-body-with-header.yaml", TestCase: &httpCLBodyWithHeader{}},
|
||||
{Path: "http/save-extractor-values-to-file.yaml", TestCase: &httpSaveExtractorValuesToFile{}},
|
||||
{Path: "http/cli-with-constants.yaml", TestCase: &ConstantWithCliVar{}},
|
||||
{Path: "http/matcher-status.yaml", TestCase: &matcherStatusTest{}},
|
||||
{Path: "http/disable-path-automerge.yaml", TestCase: &httpDisablePathAutomerge{}},
|
||||
}
|
||||
|
||||
type httpInteractshRequest struct{}
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/logrusorgru/aurora"
|
||||
|
@ -13,6 +13,12 @@ import (
|
|||
sliceutil "github.com/projectdiscovery/utils/slice"
|
||||
)
|
||||
|
||||
type TestCaseInfo struct {
|
||||
Path string
|
||||
TestCase testutils.TestCase
|
||||
DisableOn func() bool
|
||||
}
|
||||
|
||||
var (
|
||||
debug = os.Getenv("DEBUG") == "true"
|
||||
githubAction = os.Getenv("GH_ACTION") == "true"
|
||||
|
@ -21,7 +27,7 @@ var (
|
|||
success = aurora.Green("[✓]").String()
|
||||
failed = aurora.Red("[✘]").String()
|
||||
|
||||
protocolTests = map[string]map[string]testutils.TestCase{
|
||||
protocolTests = map[string][]TestCaseInfo{
|
||||
"http": httpTestcases,
|
||||
"interactsh": interactshTestCases,
|
||||
"network": networkTestcases,
|
||||
|
@ -99,18 +105,18 @@ func executeWithRetry(testCase testutils.TestCase, templatePath string, retryCou
|
|||
}
|
||||
|
||||
func debugTests() {
|
||||
keys := getMapKeys(protocolTests[runProtocol])
|
||||
for _, tpath := range keys {
|
||||
testcase := protocolTests[runProtocol][tpath]
|
||||
if runTemplate != "" && !strings.Contains(tpath, runTemplate) {
|
||||
testCaseInfos := protocolTests[runProtocol]
|
||||
for _, testCaseInfo := range testCaseInfos {
|
||||
if (runTemplate != "" && !strings.Contains(testCaseInfo.Path, runTemplate)) ||
|
||||
(testCaseInfo.DisableOn != nil && testCaseInfo.DisableOn()) {
|
||||
continue
|
||||
}
|
||||
if runProtocol == "interactsh" {
|
||||
if _, err := executeWithRetry(testcase, tpath, interactshRetryCount); err != nil {
|
||||
if _, err := executeWithRetry(testCaseInfo.TestCase, testCaseInfo.Path, interactshRetryCount); err != nil {
|
||||
fmt.Printf("\n%v", err.Error())
|
||||
}
|
||||
} else {
|
||||
if _, err := execute(testcase, tpath); err != nil {
|
||||
if _, err := execute(testCaseInfo.TestCase, testCaseInfo.Path); err != nil {
|
||||
fmt.Printf("\n%v", err.Error())
|
||||
}
|
||||
}
|
||||
|
@ -120,21 +126,22 @@ func debugTests() {
|
|||
func runTests(customTemplatePaths []string) []string {
|
||||
var failedTestTemplatePaths []string
|
||||
|
||||
for proto, testCases := range protocolTests {
|
||||
for proto, testCaseInfos := range protocolTests {
|
||||
if len(customTemplatePaths) == 0 {
|
||||
fmt.Printf("Running test cases for %q protocol\n", aurora.Blue(proto))
|
||||
}
|
||||
keys := getMapKeys(testCases)
|
||||
|
||||
for _, templatePath := range keys {
|
||||
testCase := testCases[templatePath]
|
||||
if len(customTemplatePaths) == 0 || sliceutil.Contains(customTemplatePaths, templatePath) {
|
||||
for _, testCaseInfo := range testCaseInfos {
|
||||
if testCaseInfo.DisableOn != nil && testCaseInfo.DisableOn() {
|
||||
fmt.Printf("skipping test case %v. disabled on %v.\n", aurora.Blue(testCaseInfo.Path), runtime.GOOS)
|
||||
continue
|
||||
}
|
||||
if len(customTemplatePaths) == 0 || sliceutil.Contains(customTemplatePaths, testCaseInfo.Path) {
|
||||
var failedTemplatePath string
|
||||
var err error
|
||||
if proto == "interactsh" {
|
||||
failedTemplatePath, err = executeWithRetry(testCase, templatePath, interactshRetryCount)
|
||||
failedTemplatePath, err = executeWithRetry(testCaseInfo.TestCase, testCaseInfo.Path, interactshRetryCount)
|
||||
} else {
|
||||
failedTemplatePath, err = execute(testCase, templatePath)
|
||||
failedTemplatePath, err = execute(testCaseInfo.TestCase, testCaseInfo.Path)
|
||||
}
|
||||
if err != nil {
|
||||
failedTestTemplatePaths = append(failedTestTemplatePaths, failedTemplatePath)
|
||||
|
@ -169,12 +176,3 @@ func normalizeSplit(str string) []string {
|
|||
return r == ','
|
||||
})
|
||||
}
|
||||
|
||||
func getMapKeys[T any](testcases map[string]T) []string {
|
||||
keys := make([]string, 0, len(testcases))
|
||||
for k := range testcases {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package main
|
||||
|
||||
import "github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
import osutils "github.com/projectdiscovery/utils/os"
|
||||
|
||||
// All Interactsh related testcases
|
||||
var interactshTestCases = map[string]testutils.TestCase{
|
||||
"http/interactsh.yaml": &httpInteractshRequest{},
|
||||
"http/interactsh-stop-at-first-match.yaml": &httpInteractshStopAtFirstMatchRequest{},
|
||||
"http/default-matcher-condition.yaml": &httpDefaultMatcherCondition{},
|
||||
var interactshTestCases = []TestCaseInfo{
|
||||
{Path: "http/interactsh.yaml", TestCase: &httpInteractshRequest{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
|
||||
{Path: "http/interactsh-stop-at-first-match.yaml", TestCase: &httpInteractshStopAtFirstMatchRequest{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
|
||||
{Path: "http/default-matcher-condition.yaml", TestCase: &httpDefaultMatcherCondition{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},
|
||||
}
|
||||
|
|
|
@ -12,13 +12,13 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
var loaderTestcases = map[string]testutils.TestCase{
|
||||
"loader/template-list.yaml": &remoteTemplateList{},
|
||||
"loader/workflow-list.yaml": &remoteWorkflowList{},
|
||||
"loader/excluded-template.yaml": &excludedTemplate{},
|
||||
"loader/nonexistent-template-list.yaml": &nonExistentTemplateList{},
|
||||
"loader/nonexistent-workflow-list.yaml": &nonExistentWorkflowList{},
|
||||
"loader/template-list-not-allowed.yaml": &remoteTemplateListNotAllowed{},
|
||||
var loaderTestcases = []TestCaseInfo{
|
||||
{Path: "loader/template-list.yaml", TestCase: &remoteTemplateList{}},
|
||||
{Path: "loader/workflow-list.yaml", TestCase: &remoteWorkflowList{}},
|
||||
{Path: "loader/excluded-template.yaml", TestCase: &excludedTemplate{}},
|
||||
{Path: "loader/nonexistent-template-list.yaml", TestCase: &nonExistentTemplateList{}},
|
||||
{Path: "loader/nonexistent-workflow-list.yaml", TestCase: &nonExistentWorkflowList{}},
|
||||
{Path: "loader/template-list-not-allowed.yaml", TestCase: &remoteTemplateListNotAllowed{}},
|
||||
}
|
||||
|
||||
type remoteTemplateList struct{}
|
||||
|
|
|
@ -4,15 +4,16 @@ import (
|
|||
"net"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
osutils "github.com/projectdiscovery/utils/os"
|
||||
)
|
||||
|
||||
var networkTestcases = map[string]testutils.TestCase{
|
||||
"network/basic.yaml": &networkBasic{},
|
||||
"network/hex.yaml": &networkBasic{},
|
||||
"network/multi-step.yaml": &networkMultiStep{},
|
||||
"network/self-contained.yaml": &networkRequestSelContained{},
|
||||
"network/variables.yaml": &networkVariables{},
|
||||
"network/same-address.yaml": &networkBasic{},
|
||||
var networkTestcases = []TestCaseInfo{
|
||||
{Path: "network/basic.yaml", TestCase: &networkBasic{}, DisableOn: func() bool { return osutils.IsWindows() }},
|
||||
{Path: "network/hex.yaml", TestCase: &networkBasic{}, DisableOn: func() bool { return osutils.IsWindows() }},
|
||||
{Path: "network/multi-step.yaml", TestCase: &networkMultiStep{}},
|
||||
{Path: "network/self-contained.yaml", TestCase: &networkRequestSelContained{}},
|
||||
{Path: "network/variables.yaml", TestCase: &networkVariables{}},
|
||||
{Path: "network/same-address.yaml", TestCase: &networkBasic{}},
|
||||
}
|
||||
|
||||
const defaultStaticPort = 5431
|
||||
|
|
|
@ -6,10 +6,10 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
var offlineHttpTestcases = map[string]testutils.TestCase{
|
||||
"offlinehttp/rfc-req-resp.yaml": &RfcRequestResponse{},
|
||||
"offlinehttp/offline-allowed-paths.yaml": &RequestResponseWithAllowedPaths{},
|
||||
"offlinehttp/offline-raw.yaml": &RawRequestResponse{},
|
||||
var offlineHttpTestcases = []TestCaseInfo{
|
||||
{Path: "offlinehttp/rfc-req-resp.yaml", TestCase: &RfcRequestResponse{}},
|
||||
{Path: "offlinehttp/offline-allowed-paths.yaml", TestCase: &RequestResponseWithAllowedPaths{}},
|
||||
{Path: "offlinehttp/offline-raw.yaml", TestCase: &RawRequestResponse{}},
|
||||
}
|
||||
|
||||
type RfcRequestResponse struct{}
|
||||
|
|
|
@ -7,12 +7,12 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
var sslTestcases = map[string]testutils.TestCase{
|
||||
"ssl/basic.yaml": &sslBasic{},
|
||||
"ssl/basic-ztls.yaml": &sslBasicZtls{},
|
||||
"ssl/custom-cipher.yaml": &sslCustomCipher{},
|
||||
"ssl/custom-version.yaml": &sslCustomVersion{},
|
||||
"ssl/ssl-with-vars.yaml": &sslWithVars{},
|
||||
var sslTestcases = []TestCaseInfo{
|
||||
{Path: "ssl/basic.yaml", TestCase: &sslBasic{}},
|
||||
{Path: "ssl/basic-ztls.yaml", TestCase: &sslBasicZtls{}},
|
||||
{Path: "ssl/custom-cipher.yaml", TestCase: &sslCustomCipher{}},
|
||||
{Path: "ssl/custom-version.yaml", TestCase: &sslCustomVersion{}},
|
||||
{Path: "ssl/ssl-with-vars.yaml", TestCase: &sslWithVars{}},
|
||||
}
|
||||
|
||||
type sslBasic struct{}
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
errorutil "github.com/projectdiscovery/utils/errors"
|
||||
)
|
||||
|
||||
var templatesDirTestCases = map[string]testutils.TestCase{
|
||||
"dns/cname-fingerprint.yaml": &templateDirWithTargetTest{},
|
||||
var templatesDirTestCases = []TestCaseInfo{
|
||||
{Path: "dns/cname-fingerprint.yaml", TestCase: &templateDirWithTargetTest{}},
|
||||
}
|
||||
|
||||
type templateDirWithTargetTest struct{}
|
||||
|
|
|
@ -12,15 +12,15 @@ func getTemplatePath() string {
|
|||
return config.DefaultConfig.TemplatesDirectory
|
||||
}
|
||||
|
||||
var templatesPathTestCases = map[string]testutils.TestCase{
|
||||
var templatesPathTestCases = []TestCaseInfo{
|
||||
//template folder path issue
|
||||
"http/get.yaml": &folderPathTemplateTest{},
|
||||
{Path: "http/get.yaml", TestCase: &folderPathTemplateTest{}},
|
||||
//cwd
|
||||
"./dns/cname-fingerprint.yaml": &cwdTemplateTest{},
|
||||
{Path: "./dns/cname-fingerprint.yaml", TestCase: &cwdTemplateTest{}},
|
||||
//relative path
|
||||
"dns/cname-fingerprint.yaml": &relativePathTemplateTest{},
|
||||
{Path: "dns/cname-fingerprint.yaml", TestCase: &relativePathTemplateTest{}},
|
||||
//absolute path
|
||||
fmt.Sprintf("%v/dns/cname-fingerprint.yaml", getTemplatePath()): &absolutePathTemplateTest{},
|
||||
{Path: fmt.Sprintf("%v/dns/cname-fingerprint.yaml", getTemplatePath()), TestCase: &absolutePathTemplateTest{}},
|
||||
}
|
||||
|
||||
type cwdTemplateTest struct{}
|
||||
|
|
|
@ -9,11 +9,11 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
var websocketTestCases = map[string]testutils.TestCase{
|
||||
"websocket/basic.yaml": &websocketBasic{},
|
||||
"websocket/cswsh.yaml": &websocketCswsh{},
|
||||
"websocket/no-cswsh.yaml": &websocketNoCswsh{},
|
||||
"websocket/path.yaml": &websocketWithPath{},
|
||||
var websocketTestCases = []TestCaseInfo{
|
||||
{Path: "websocket/basic.yaml", TestCase: &websocketBasic{}},
|
||||
{Path: "websocket/cswsh.yaml", TestCase: &websocketCswsh{}},
|
||||
{Path: "websocket/no-cswsh.yaml", TestCase: &websocketNoCswsh{}},
|
||||
{Path: "websocket/path.yaml", TestCase: &websocketWithPath{}},
|
||||
}
|
||||
|
||||
type websocketBasic struct{}
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
var whoisTestCases = map[string]testutils.TestCase{
|
||||
"whois/basic.yaml": &whoisBasic{},
|
||||
var whoisTestCases = []TestCaseInfo{
|
||||
{Path: "whois/basic.yaml", TestCase: &whoisBasic{}},
|
||||
}
|
||||
|
||||
type whoisBasic struct{}
|
||||
|
|
|
@ -11,14 +11,14 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
var workflowTestcases = map[string]testutils.TestCase{
|
||||
"workflow/basic.yaml": &workflowBasic{},
|
||||
"workflow/condition-matched.yaml": &workflowConditionMatched{},
|
||||
"workflow/condition-unmatched.yaml": &workflowConditionUnmatch{},
|
||||
"workflow/matcher-name.yaml": &workflowMatcherName{},
|
||||
"workflow/http-value-share-workflow.yaml": &workflowHttpKeyValueShare{},
|
||||
"workflow/dns-value-share-workflow.yaml": &workflowDnsKeyValueShare{},
|
||||
"workflow/shared-cookie.yaml": &workflowSharedCookies{},
|
||||
var workflowTestcases = []TestCaseInfo{
|
||||
{Path: "workflow/basic.yaml", TestCase: &workflowBasic{}},
|
||||
{Path: "workflow/condition-matched.yaml", TestCase: &workflowConditionMatched{}},
|
||||
{Path: "workflow/condition-unmatched.yaml", TestCase: &workflowConditionUnmatch{}},
|
||||
{Path: "workflow/matcher-name.yaml", TestCase: &workflowMatcherName{}},
|
||||
{Path: "workflow/http-value-share-workflow.yaml", TestCase: &workflowHttpKeyValueShare{}},
|
||||
{Path: "workflow/dns-value-share-workflow.yaml", TestCase: &workflowDnsKeyValueShare{}},
|
||||
{Path: "workflow/shared-cookie.yaml", TestCase: &workflowSharedCookies{}},
|
||||
}
|
||||
|
||||
type workflowBasic struct{}
|
||||
|
|
|
@ -272,6 +272,7 @@ on extensive configurability, massive extensibility and ease of use.`)
|
|||
flagSet.BoolVar(&options.Headless, "headless", false, "enable templates that require headless browser support (root user on Linux will disable sandbox)"),
|
||||
flagSet.IntVar(&options.PageTimeout, "page-timeout", 20, "seconds to wait for each page in headless mode"),
|
||||
flagSet.BoolVarP(&options.ShowBrowser, "show-browser", "sb", false, "show the browser on the screen when running templates with headless mode"),
|
||||
flagSet.StringSliceVarP(&options.HeadlessOptionalArguments, "headless-options", "ho", nil, "start headless chrome with additional options", goflags.FileCommaSeparatedStringSliceOptions),
|
||||
flagSet.BoolVarP(&options.UseInstalledChrome, "system-chrome", "sc", false, "use local installed Chrome browser instead of nuclei installed"),
|
||||
flagSet.BoolVarP(&options.ShowActions, "list-headless-action", "lha", false, "list available headless actions"),
|
||||
)
|
||||
|
|
|
@ -114,6 +114,10 @@ func validateOptions(options *types.Options) error {
|
|||
return errors.New("both verbose and silent mode specified")
|
||||
}
|
||||
|
||||
if (options.HeadlessOptionalArguments != nil || options.ShowBrowser || options.UseInstalledChrome) && !options.Headless {
|
||||
return errors.New("headless mode (-headless) is required if -ho, -sb, -sc or -lha are set")
|
||||
}
|
||||
|
||||
if options.FollowHostRedirects && options.FollowRedirects {
|
||||
return errors.New("both follow host redirects and follow redirects specified")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package runner
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/goflags"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseHeadlessOptionalArguments(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want map[string]string
|
||||
}{
|
||||
{
|
||||
name: "single value",
|
||||
input: "a=b",
|
||||
want: map[string]string{"a": "b"},
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
input: "",
|
||||
want: map[string]string{},
|
||||
},
|
||||
{
|
||||
name: "empty key",
|
||||
input: "=b",
|
||||
want: map[string]string{},
|
||||
},
|
||||
{
|
||||
name: "empty value",
|
||||
input: "a=",
|
||||
want: map[string]string{},
|
||||
},
|
||||
{
|
||||
name: "double input",
|
||||
input: "a=b,c=d",
|
||||
want: map[string]string{"a": "b", "c": "d"},
|
||||
},
|
||||
{
|
||||
name: "duplicated input",
|
||||
input: "a=b,a=b",
|
||||
want: map[string]string{"a": "b"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
strsl := goflags.StringSlice{}
|
||||
for _, v := range strings.Split(tt.input, ",") {
|
||||
//nolint
|
||||
strsl.Set(v)
|
||||
}
|
||||
opt := types.Options{HeadlessOptionalArguments: strsl}
|
||||
got := opt.ParseHeadlessOptionalArguments()
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/go-rod/rod"
|
||||
"github.com/go-rod/rod/lib/launcher"
|
||||
"github.com/go-rod/rod/lib/launcher/flags"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||
|
@ -75,6 +76,11 @@ func New(options *types.Options) (*Browser, error) {
|
|||
if types.ProxyURL != "" {
|
||||
chromeLauncher = chromeLauncher.Proxy(types.ProxyURL)
|
||||
}
|
||||
|
||||
for k, v := range options.ParseHeadlessOptionalArguments() {
|
||||
chromeLauncher.Set(flags.Flag(k), v)
|
||||
}
|
||||
|
||||
launcherURL, err := chromeLauncher.Launch()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -2,6 +2,7 @@ package types
|
|||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/projectdiscovery/goflags"
|
||||
|
@ -197,6 +198,8 @@ type Options struct {
|
|||
Headless bool
|
||||
// ShowBrowser specifies whether the show the browser in headless mode
|
||||
ShowBrowser bool
|
||||
// HeadlessOptionalArguments specifies optional arguments to pass to Chrome
|
||||
HeadlessOptionalArguments goflags.StringSlice
|
||||
// NoTables disables pretty printing of cloud results in tables
|
||||
NoTables bool
|
||||
// DisableClustering disables clustering of templates
|
||||
|
@ -441,3 +444,17 @@ func (options *Options) HasCloudOptions() bool {
|
|||
func (options *Options) ShouldUseHostError() bool {
|
||||
return options.MaxHostError > 0 && !options.NoHostErrors
|
||||
}
|
||||
|
||||
func (options *Options) ParseHeadlessOptionalArguments() map[string]string {
|
||||
optionalArguments := make(map[string]string)
|
||||
for _, v := range options.HeadlessOptionalArguments {
|
||||
if argParts := strings.SplitN(v, "=", 2); len(argParts) >= 2 {
|
||||
key := strings.TrimSpace(argParts[0])
|
||||
value := strings.TrimSpace(argParts[1])
|
||||
if key != "" && value != "" {
|
||||
optionalArguments[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
return optionalArguments
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue