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 func
dev
Dogan Can Bakir 2023-07-28 18:50:57 +03:00 committed by GitHub
parent 6bdef68734
commit 163bc22281
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 255 additions and 166 deletions

View File

@ -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

View File

@ -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{}

View File

@ -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

View File

@ -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{}

View File

@ -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{}

View File

@ -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{}

View File

@ -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 (

View File

@ -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{}

View File

@ -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{}

View File

@ -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
}

View File

@ -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() }},
}

View File

@ -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{}

View File

@ -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

View File

@ -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{}

View File

@ -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{}

View File

@ -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{}

View File

@ -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{}

View File

@ -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{}

View File

@ -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{}

View File

@ -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{}

View File

@ -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"),
)

View File

@ -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")
}

View File

@ -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)
})
}
}

View File

@ -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

View File

@ -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
}