Repository: hahwul/dalfox

Issue:
Implement a Flag for Always-On Headless Verification
From https://x.com/0xsabir/status/1874435409700393105

Discussion:

Solution patch:
diff --git a/cmd/args.go b/cmd/args.go
index 9ae7a17..d309b82 100644
--- a/cmd/args.go
+++ b/cmd/args.go
@@ -1,59 +1,60 @@
 package cmd
 
 type Args struct {
-	Header            []string
-	P                 []string
-	IgnoreParams      []string
-	Config            string
-	Cookie            string
-	Data              string
-	CustomPayload     string
-	CustomAlertValue  string
-	CustomAlertType   string
-	UserAgent         string
-	Blind             string
-	Output            string
-	Format            string
-	FoundAction       string
-	FoundActionShell  string
-	Proxy             string
-	Grep              string
-	IgnoreReturn      string
-	MiningWord        string
-	Method            string
-	CookieFromRaw     string
-	RemotePayloads    string
-	RemoteWordlists   string
-	OnlyPoC           string
-	PoCType           string
-	ReportFormat      string
-	HarFilePath       string
-	Timeout           int
-	Delay             int
-	Concurrence       int
-	MaxCPU            int
-	OnlyDiscovery     bool
-	Silence           bool
-	Mining            bool
-	FindingDOM        bool
-	FollowRedirect    bool
-	NoColor           bool
-	NoSpinner         bool
-	UseBAV            bool
-	SkipBAV           bool
-	SkipMiningDom     bool
-	SkipMiningDict    bool
-	SkipMiningAll     bool
-	SkipXSSScan       bool
-	OnlyCustomPayload bool
-	SkipGrep          bool
-	Debug             bool
-	SkipHeadless      bool
-	UseDeepDXSS       bool
-	OutputAll         bool
-	WAFEvasion        bool
-	ReportBool        bool
-	OutputRequest     bool
-	OutputResponse    bool
-	SkipDiscovery     bool
+	Header                    []string
+	P                         []string
+	IgnoreParams              []string
+	Config                    string
+	Cookie                    string
+	Data                      string
+	CustomPayload             string
+	CustomAlertValue          string
+	CustomAlertType           string
+	UserAgent                 string
+	Blind                     string
+	Output                    string
+	Format                    string
+	FoundAction               string
+	FoundActionShell          string
+	Proxy                     string
+	Grep                      string
+	IgnoreReturn              string
+	MiningWord                string
+	Method                    string
+	CookieFromRaw             string
+	RemotePayloads            string
+	RemoteWordlists           string
+	OnlyPoC                   string
+	PoCType                   string
+	ReportFormat              string
+	HarFilePath               string
+	Timeout                   int
+	Delay                     int
+	Concurrence               int
+	MaxCPU                    int
+	OnlyDiscovery             bool
+	Silence                   bool
+	Mining                    bool
+	FindingDOM                bool
+	FollowRedirect            bool
+	NoColor                   bool
+	NoSpinner                 bool
+	UseBAV                    bool
+	SkipBAV                   bool
+	SkipMiningDom             bool
+	SkipMiningDict            bool
+	SkipMiningAll             bool
+	SkipXSSScan               bool
+	OnlyCustomPayload         bool
+	SkipGrep                  bool
+	Debug                     bool
+	SkipHeadless              bool
+	UseDeepDXSS               bool
+	OutputAll                 bool
+	WAFEvasion                bool
+	ReportBool                bool
+	OutputRequest             bool
+	OutputResponse            bool
+	SkipDiscovery             bool
+	ForceHeadlessVerification bool
 }
diff --git a/cmd/root.go b/cmd/root.go
index 9e7a781..605d9f8 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -106,7 +106,7 @@ func init() {
 	rootCmd.PersistentFlags().BoolVar(&args.OutputRequest, "output-request", false, "Include raw HTTP requests in the results. Example: --output-request")
 	rootCmd.PersistentFlags().BoolVar(&args.OutputResponse, "output-response", false, "Include raw HTTP responses in the results. Example: --output-response")
 	rootCmd.PersistentFlags().BoolVar(&args.SkipDiscovery, "skip-discovery", false, "Skip the entire discovery phase, proceeding directly to XSS scanning. Requires -p flag to specify parameters. Example: --skip-discovery -p 'username'")
-
+rootCmd.PersistentFlags().BoolVar(&args.ForceHeadlessVerification, "force-headless-verification", false, "Force headless browser-based verification, useful when automatic detection fails or to override default behavior. Example: --force-headless-verification")
 }
 
 // initConfig reads in config file and ENV variables if set.
diff --git a/pkg/model/options.go b/pkg/model/options.go
index 0656824..ce8d13b 100644
--- a/pkg/model/options.go
+++ b/pkg/model/options.go
@@ -13,79 +13,80 @@ import (
 
 // Options is struct of options
 type Options struct {
-	UniqParam         []string `json:"param,omitempty"`
-	Cookie            string   `json:"cookie,omitempty"`
-	Header            []string `json:"header,omitempty"`
-	ConfigFile        string   `json:"config,omitempty"`
-	BlindURL          string   `json:"blind,omitempty"`
-	CustomPayloadFile string   `json:"custom-payload-file,omitempty"`
-	CustomAlertValue  string   `json:"custom-alert-value,omitempty"`
-	CustomAlertType   string   `json:"custom-alert-type,omitempty"`
-	Data              string   `json:"data,omitempty"`
-	UserAgent         string   `json:"user-agent,omitempty"`
-	OutputFile        string   `json:"output,omitempty"`
-	Format            string   `json:"format,omitempty"`
-	FoundAction       string   `json:"found-action,omitempty"`
-	FoundActionShell  string   `json:"found-action-shell,omitempty"`
-	ProxyAddress      string   `json:"proxy,omitempty"`
-	Grep              string   `json:"grep,omitempty"`
-	IgnoreReturn      string   `json:"ignore-return,omitempty"`
-	IgnoreParams      []string `json:"ignore-params,omitempty"`
-	Trigger           string   `json:"trigger,omitempty"`
-	Timeout           int      `json:"timeout,omitempty"`
-	Concurrence       int      `json:"worker,omitempty"`
-	MaxCPU            int      `json:"maxcpu,omitempty"`
-	Delay             int      `json:"delay,omitempty"`
-	AllURLS           int
-	NowURL            int
-	Sequence          int  `json:"sequence,omitempty"`
-	OnlyDiscovery     bool `json:"only-discovery,omitempty"`
-	OnlyCustomPayload bool `json:"only-custom-payload,omitempty"`
-	Silence           bool `json:"silence,omitempty"`
-	IsAPI             bool `json:"is-api,omitempty"`
-	IsLibrary         bool `json:"is-library,omitempty"`
-	Mass              bool `json:"mass,omitempty"`
-	MulticastMode     bool `json:"multicast-mode,omitempty"`
-	Scan              map[string]Scan
-	FollowRedirect    bool   `json:"follow-redirects,omitempty"`
-	Mining            bool   `json:"mining-dict,omitempty"`
-	FindingDOM        bool   `json:"mining-dom,omitempty"`
-	MiningWordlist    string `json:"mining-dict-word,omitempty"`
-	NoColor           bool   `json:"no-color,omitempty"`
-	Method            string `json:"method,omitempty"`
-	TriggerMethod     string `json:"trigger-method,omitempty"`
-	NoSpinner         bool   `json:"no-spinner,omitempty"`
-	NoBAV             bool   `json:"no-bav,omitempty"`
-	ServerHost        string `json:"server-host,omitempty"`
-	ServerPort        int    `json:"server-port,omitempty"`
-	NoGrep            bool   `json:"skip-grepping,omitempty"`
-	Debug             bool   `json:"debug,omitempty"`
-	CookieFromRaw     string `json:"cookie-from-raw,omitempty"`
-	ScanResult        Result
-	SpinnerObject     *s.Spinner
-	AuroraObject      a.Aurora
-	StartTime         t.Time
-	HarWriter         *har.Writer
-	PathReflection    map[int]string
-	RemotePayloads    string `json:"remote-payloads,omitempty"`
-	RemoteWordlists   string `json:"remote-wordlists,omitempty"`
-	UseHeadless       bool   `json:"use-headless,omitempty"`
-	UseDeepDXSS       bool   `json:"use-deepdxss,omitempty"`
-	OnlyPoC           string `json:"only-poc,omitempty"`
-	OutputAll         bool   `json:"output-all,omitempty"`
-	WAF               bool
-	WAFName           string `json:"waf-name,omitempty"`
-	WAFEvasion        bool
-	PoCType           string `json:"poc-type,omitempty"`
-	Mutex             *sync.Mutex
-	ReportFormat      string
-	ReportBool        bool
-	OutputRequest     bool `json:"output-request,omitempty"`
-	OutputResponse    bool `json:"output-response,omitempty"`
-	UseBAV            bool `json:"use-bav,omitempty"`
-	CustomTransport   http.RoundTripper
-	SkipDiscovery     bool `json:"skip-discovery,omitempty"`
-	LimitResult       int  `json:"limit-result,omitempty"`
+	UniqParam                 []string `json:"param,omitempty"`
+	Cookie                    string   `json:"cookie,omitempty"`
+	Header                    []string `json:"header,omitempty"`
+	ConfigFile                string   `json:"config,omitempty"`
+	BlindURL                  string   `json:"blind,omitempty"`
+	CustomPayloadFile         string   `json:"custom-payload-file,omitempty"`
+	CustomAlertValue          string   `json:"custom-alert-value,omitempty"`
+	CustomAlertType           string   `json:"custom-alert-type,omitempty"`
+	Data                      string   `json:"data,omitempty"`
+	UserAgent                 string   `json:"user-agent,omitempty"`
+	OutputFile                string   `json:"output,omitempty"`
+	Format                    string   `json:"format,omitempty"`
+	FoundAction               string   `json:"found-action,omitempty"`
+	FoundActionShell          string   `json:"found-action-shell,omitempty"`
+	ProxyAddress              string   `json:"proxy,omitempty"`
+	Grep                      string   `json:"grep,omitempty"`
+	IgnoreReturn              string   `json:"ignore-return,omitempty"`
+	IgnoreParams              []string `json:"ignore-params,omitempty"`
+	Trigger                   string   `json:"trigger,omitempty"`
+	Timeout                   int      `json:"timeout,omitempty"`
+	Concurrence               int      `json:"worker,omitempty"`
+	MaxCPU                    int      `json:"maxcpu,omitempty"`
+	Delay                     int      `json:"delay,omitempty"`
+	AllURLS                   int
+	NowURL                    int
+	Sequence                  int  `json:"sequence,omitempty"`
+	OnlyDiscovery             bool `json:"only-discovery,omitempty"`
+	OnlyCustomPayload         bool `json:"only-custom-payload,omitempty"`
+	Silence                   bool `json:"silence,omitempty"`
+	IsAPI                     bool `json:"is-api,omitempty"`
+	IsLibrary                 bool `json:"is-library,omitempty"`
+	Mass                      bool `json:"mass,omitempty"`
+	MulticastMode             bool `json:"multicast-mode,omitempty"`
+	Scan                      map[string]Scan
+	FollowRedirect            bool   `json:"follow-redirects,omitempty"`
+	Mining                    bool   `json:"mining-dict,omitempty"`
+	FindingDOM                bool   `json:"mining-dom,omitempty"`
+	MiningWordlist            string `json:"mining-dict-word,omitempty"`
+	NoColor                   bool   `json:"no-color,omitempty"`
+	Method                    string `json:"method,omitempty"`
+	TriggerMethod             string `json:"trigger-method,omitempty"`
+	NoSpinner                 bool   `json:"no-spinner,omitempty"`
+	NoBAV                     bool   `json:"no-bav,omitempty"`
+	ServerHost                string `json:"server-host,omitempty"`
+	ServerPort                int    `json:"server-port,omitempty"`
+	NoGrep                    bool   `json:"skip-grepping,omitempty"`
+	Debug                     bool   `json:"debug,omitempty"`
+	CookieFromRaw             string `json:"cookie-from-raw,omitempty"`
+	ScanResult                Result
+	SpinnerObject             *s.Spinner
+	AuroraObject              a.Aurora
+	StartTime                 t.Time
+	HarWriter                 *har.Writer
+	PathReflection            map[int]string
+	RemotePayloads            string `json:"remote-payloads,omitempty"`
+	RemoteWordlists           string `json:"remote-wordlists,omitempty"`
+	UseHeadless               bool   `json:"use-headless,omitempty"`
+	UseDeepDXSS               bool   `json:"use-deepdxss,omitempty"`
+	OnlyPoC                   string `json:"only-poc,omitempty"`
+	OutputAll                 bool   `json:"output-all,omitempty"`
+	WAF                       bool
+	WAFName                   string `json:"waf-name,omitempty"`
+	WAFEvasion                bool
+	PoCType                   string `json:"poc-type,omitempty"`
+	Mutex                     *sync.Mutex
+	ReportFormat              string
+	ReportBool                bool
+	OutputRequest             bool `json:"output-request,omitempty"`
+	OutputResponse            bool `json:"output-response,omitempty"`
+	UseBAV                    bool `json:"use-bav,omitempty"`
+	CustomTransport           http.RoundTripper
+	SkipDiscovery             bool `json:"skip-discovery,omitempty"`
+	LimitResult               int  `json:"limit-result,omitempty"`
+	ForceHeadlessVerification bool `json:"force-headless-verification,omitempty"`
 }
 
 // MassJob is list for mass
diff --git a/pkg/scanning/sendReq.go b/pkg/scanning/sendReq.go
index a7125b3..9d2460f 100644
--- a/pkg/scanning/sendReq.go
+++ b/pkg/scanning/sendReq.go
@@ -133,6 +133,10 @@ func processResponse(str string, resp *http.Response, payload string, req *http.
 	if resp.Header["Content-Type"] != nil && utils.IsAllowType(resp.Header["Content-Type"][0]) {
 		vds := verification.VerifyDOM(str)
 		vrs := verification.VerifyReflection(str, payload)
+if !vds && options.ForceHeadlessVerification {
+			// Only run headless verification if VerifyDOM failed
+			vds = CheckXSSWithHeadless(req.URL.String(), options)
+		}
 		rLog.WithField("data2", "vds").Debug(vds)
 		rLog.WithField("data2", "vrs").Debug(vrs)
 		return str, resp, vds, vrs, nil



Test patch:
diff --git a/pkg/scanning/sendReq_test.go b/pkg/scanning/sendReq_test.go
index fff48b7..b251be2 100644
--- a/pkg/scanning/sendReq_test.go
+++ b/pkg/scanning/sendReq_test.go
@@ -80,6 +80,20 @@ func TestSendReq(t *testing.T) {
 			want3:   false,
 			wantErr: false,
 		},
+		{
+			name: "Request with ForceHeadlessVerification",
+			args: args{
+				req: func() *http.Request {
+					req, _ := http.NewRequest(http.MethodGet, "https://dalfox.hahwul.com", nil)
+					return req
+				}(),
+				payload: "test-payload",
+				options: model.Options{
+					Timeout:                   10,
+					ForceHeadlessVerification: true,
+				},
+			},
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -266,7 +280,6 @@ func TestHandlePoC(t *testing.T) {
 			},
 			showG: true,
 		},
-
 		{
 			name: "PoC with showG false",
 			poc: model.PoC{