From f144424bb8bfd2f100f19bed76171dc94bd3232b Mon Sep 17 00:00:00 2001 From: Wlayzz <34021743+wlayzz@users.noreply.github.com> Date: Sun, 11 Jan 2026 23:50:45 +0100 Subject: [PATCH 1/5] fix: update Host header for favicon requests after redirects When FollowRedirects is enabled and the target redirects to a different host (e.g., hackerone.com -> www.hackerone.com), the favicon URL is correctly resolved to the final host, but the cloned request's Host header still contains the original host. This causes the server to return 404 because the Host header doesn't match the URL. This fix updates the Host header to match the resolved URL host before making the favicon request. --- runner/runner.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/runner/runner.go b/runner/runner.go index 2a20265..5720811 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -2611,6 +2611,10 @@ func (r *Runner) HandleFaviconHash(hp *httpx.HTTPX, req *retryablehttp.Request, } clone.SetURL(resolvedURL) + // Update Host header to match resolved URL host (important after redirects) + if resolvedURL.Host != "" && resolvedURL.Host != clone.Host { + clone.Host = resolvedURL.Host + } respFav, err := hp.Do(clone, httpx.UnsafeOptions{}) if err != nil || len(respFav.Data) == 0 { tries++ From 0f5141b0d74357e674bd95e455831d1317a2c501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fan=20Can=20Bak=C4=B1r?= Date: Mon, 12 Jan 2026 14:13:31 +0300 Subject: [PATCH 2/5] feat: add burp xml input mode support (-im flag) --- common/inputformats/burp.go | 42 ++++++++ common/inputformats/burp_test.go | 148 ++++++++++++++++++++++++++++ common/inputformats/formats.go | 41 ++++++++ common/inputformats/formats_test.go | 43 ++++++++ go.mod | 1 + go.sum | 2 + runner/options.go | 3 + runner/runner.go | 88 ++++++++++++++--- 8 files changed, 354 insertions(+), 14 deletions(-) create mode 100644 common/inputformats/burp.go create mode 100644 common/inputformats/burp_test.go create mode 100644 common/inputformats/formats.go create mode 100644 common/inputformats/formats_test.go diff --git a/common/inputformats/burp.go b/common/inputformats/burp.go new file mode 100644 index 0000000..0e49b2f --- /dev/null +++ b/common/inputformats/burp.go @@ -0,0 +1,42 @@ +package inputformats + +import ( + "io" + + "github.com/pkg/errors" + "github.com/seh-msft/burpxml" +) + +// BurpFormat is a Burp Suite XML file parser +type BurpFormat struct{} + +// NewBurpFormat creates a new Burp XML file parser +func NewBurpFormat() *BurpFormat { + return &BurpFormat{} +} + +var _ Format = &BurpFormat{} + +// Name returns the name of the format +func (b *BurpFormat) Name() string { + return "burp" +} + +// Parse parses the Burp XML input and calls the provided callback +// function for each URL it discovers. +func (b *BurpFormat) Parse(input io.Reader, callback func(url string) bool) error { + items, err := burpxml.Parse(input, true) + if err != nil { + return errors.Wrap(err, "could not parse burp xml") + } + + for _, item := range items.Items { + if item.Url == "" { + continue + } + if !callback(item.Url) { + break + } + } + return nil +} diff --git a/common/inputformats/burp_test.go b/common/inputformats/burp_test.go new file mode 100644 index 0000000..ee6a011 --- /dev/null +++ b/common/inputformats/burp_test.go @@ -0,0 +1,148 @@ +package inputformats + +import ( + "strings" + "testing" +) + +func TestBurpFormat_Name(t *testing.T) { + b := NewBurpFormat() + if b.Name() != "burp" { + t.Errorf("Expected name 'burp', got '%s'", b.Name()) + } +} + +func TestBurpFormat_Parse(t *testing.T) { + burpXML := ` + + + + + example.com + 80 + http + + + null + + 200 + 100 + HTML + + + + + + + example.com + 443 + https + + + null + + 200 + 100 + JSON + + + +` + + b := NewBurpFormat() + var urls []string + + err := b.Parse(strings.NewReader(burpXML), func(url string) bool { + urls = append(urls, url) + return true + }) + + if err != nil { + t.Fatalf("Parse returned error: %v", err) + } + + if len(urls) != 2 { + t.Errorf("Expected 2 URLs, got %d", len(urls)) + } + + expectedURLs := []string{"http://example.com/path1", "https://example.com/path2"} + for i, expected := range expectedURLs { + if urls[i] != expected { + t.Errorf("Expected URL %d to be '%s', got '%s'", i, expected, urls[i]) + } + } +} + +func TestBurpFormat_ParseEmpty(t *testing.T) { + burpXML := ` + +` + + b := NewBurpFormat() + var urls []string + + err := b.Parse(strings.NewReader(burpXML), func(url string) bool { + urls = append(urls, url) + return true + }) + + if err != nil { + t.Fatalf("Parse returned error: %v", err) + } + + if len(urls) != 0 { + t.Errorf("Expected 0 URLs, got %d", len(urls)) + } +} + +func TestBurpFormat_ParseStopEarly(t *testing.T) { + burpXML := ` + + + + example.com + 80 + http + + + null + + 200 + 100 + HTML + + + + + + example.com + 80 + http + + + null + + 200 + 100 + HTML + + + +` + + b := NewBurpFormat() + var urls []string + + err := b.Parse(strings.NewReader(burpXML), func(url string) bool { + urls = append(urls, url) + return false // stop after first + }) + + if err != nil { + t.Fatalf("Parse returned error: %v", err) + } + + if len(urls) != 1 { + t.Errorf("Expected 1 URL (stopped early), got %d", len(urls)) + } +} diff --git a/common/inputformats/formats.go b/common/inputformats/formats.go new file mode 100644 index 0000000..31a25a3 --- /dev/null +++ b/common/inputformats/formats.go @@ -0,0 +1,41 @@ +// TODO: This package should be abstracted out to projectdiscovery/utils +// so it can be shared between httpx, nuclei, and other tools. +package inputformats + +import ( + "io" + "strings" +) + +// Format is an interface implemented by all input formats +type Format interface { + // Name returns the name of the format + Name() string + // Parse parses the input and calls the provided callback + // function for each URL it discovers. + Parse(input io.Reader, callback func(url string) bool) error +} + +// Supported formats +var formats = []Format{ + NewBurpFormat(), +} + +// GetFormat returns the format by name +func GetFormat(name string) Format { + for _, f := range formats { + if strings.EqualFold(f.Name(), name) { + return f + } + } + return nil +} + +// SupportedFormats returns a comma-separated list of supported format names +func SupportedFormats() string { + var names []string + for _, f := range formats { + names = append(names, f.Name()) + } + return strings.Join(names, ", ") +} diff --git a/common/inputformats/formats_test.go b/common/inputformats/formats_test.go new file mode 100644 index 0000000..aefe05d --- /dev/null +++ b/common/inputformats/formats_test.go @@ -0,0 +1,43 @@ +package inputformats + +import ( + "strings" + "testing" +) + +func TestGetFormat(t *testing.T) { + tests := []struct { + name string + input string + wantNil bool + wantName string + }{ + {"burp lowercase", "burp", false, "burp"}, + {"burp uppercase", "BURP", false, "burp"}, + {"burp mixed case", "Burp", false, "burp"}, + {"invalid format", "invalid", true, ""}, + {"empty string", "", true, ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := GetFormat(tt.input) + if tt.wantNil && got != nil { + t.Errorf("GetFormat(%q) = %v, want nil", tt.input, got) + } + if !tt.wantNil && got == nil { + t.Errorf("GetFormat(%q) = nil, want non-nil", tt.input) + } + if !tt.wantNil && got != nil && got.Name() != tt.wantName { + t.Errorf("GetFormat(%q).Name() = %q, want %q", tt.input, got.Name(), tt.wantName) + } + }) + } +} + +func TestSupportedFormats(t *testing.T) { + supported := SupportedFormats() + if !strings.Contains(supported, "burp") { + t.Errorf("SupportedFormats() = %q, expected to contain 'burp'", supported) + } +} diff --git a/go.mod b/go.mod index 1ac2fdd..9a52844 100644 --- a/go.mod +++ b/go.mod @@ -139,6 +139,7 @@ require ( github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect github.com/sashabaranov/go-openai v1.37.0 // indirect + github.com/seh-msft/burpxml v1.0.1 // indirect github.com/shirou/gopsutil/v3 v3.24.2 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sorairolake/lzip-go v0.3.8 // indirect diff --git a/go.sum b/go.sum index 0188b44..395a3a4 100644 --- a/go.sum +++ b/go.sum @@ -389,6 +389,8 @@ github.com/sashabaranov/go-openai v1.37.0 h1:hQQowgYm4OXJ1Z/wTrE+XZaO20BYsL0R3uR github.com/sashabaranov/go-openai v1.37.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/sebdah/goldie/v2 v2.8.0 h1:dZb9wR8q5++oplmEiJT+U/5KyotVD+HNGCAc5gNr8rc= github.com/sebdah/goldie/v2 v2.8.0/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI= +github.com/seh-msft/burpxml v1.0.1 h1:5G3QPSzvfA1WcX7LkxmKBmK2RnNyGviGWnJPumE0nwg= +github.com/seh-msft/burpxml v1.0.1/go.mod h1:lTViCHPtGGS0scK0B4krm6Ld1kVZLWzQccwUomRc58I= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y= diff --git a/runner/options.go b/runner/options.go index 46e9bc8..dbccb32 100644 --- a/runner/options.go +++ b/runner/options.go @@ -23,6 +23,7 @@ import ( customport "github.com/projectdiscovery/httpx/common/customports" fileutilz "github.com/projectdiscovery/httpx/common/fileutil" httpxcommon "github.com/projectdiscovery/httpx/common/httpx" + "github.com/projectdiscovery/httpx/common/inputformats" "github.com/projectdiscovery/httpx/common/stringz" "github.com/projectdiscovery/networkpolicy" pdcpauth "github.com/projectdiscovery/utils/auth/pdcp" @@ -191,6 +192,7 @@ type Options struct { SocksProxy string Proxy string InputFile string + InputMode string InputTargetHost goflags.StringSlice Methods string RequestURI string @@ -375,6 +377,7 @@ func ParseOptions() *Options { flagSet.StringVarP(&options.InputFile, "list", "l", "", "input file containing list of hosts to process"), flagSet.StringVarP(&options.InputRawRequest, "request", "rr", "", "file containing raw request"), flagSet.StringSliceVarP(&options.InputTargetHost, "target", "u", nil, "input target host(s) to probe", goflags.CommaSeparatedStringSliceOptions), + flagSet.StringVarP(&options.InputMode, "input-mode", "im", "", fmt.Sprintf("mode of input file (%s)", inputformats.SupportedFormats())), ) flagSet.CreateGroup("Probes", "Probes", diff --git a/runner/runner.go b/runner/runner.go index 2a20265..714922f 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -35,6 +35,7 @@ import ( "github.com/projectdiscovery/fastdialer/fastdialer" "github.com/projectdiscovery/httpx/common/customextract" "github.com/projectdiscovery/httpx/common/hashes/jarm" + "github.com/projectdiscovery/httpx/common/inputformats" "github.com/projectdiscovery/httpx/common/pagetypeclassifier" "github.com/projectdiscovery/httpx/static" "github.com/projectdiscovery/mapcidr/asn" @@ -524,13 +525,26 @@ func (r *Runner) prepareInput() { } // check if file has been provided if fileutil.FileExists(r.options.InputFile) { - finput, err := os.Open(r.options.InputFile) - if err != nil { - gologger.Fatal().Msgf("Could not read input file '%s': %s\n", r.options.InputFile, err) - } - numHosts, err = r.loadAndCloseFile(finput) - if err != nil { - gologger.Fatal().Msgf("Could not read input file '%s': %s\n", r.options.InputFile, err) + // check if input mode is specified for special format handling + if r.options.InputMode != "" { + format := inputformats.GetFormat(r.options.InputMode) + if format == nil { + gologger.Fatal().Msgf("Invalid input mode '%s'. Supported: %s\n", r.options.InputMode, inputformats.SupportedFormats()) + } + numTargets, err := r.loadFromFormat(r.options.InputFile, format) + if err != nil { + gologger.Fatal().Msgf("Could not parse input file '%s': %s\n", r.options.InputFile, err) + } + numHosts = numTargets + } else { + finput, err := os.Open(r.options.InputFile) + if err != nil { + gologger.Fatal().Msgf("Could not read input file '%s': %s\n", r.options.InputFile, err) + } + numHosts, err = r.loadAndCloseFile(finput) + if err != nil { + gologger.Fatal().Msgf("Could not read input file '%s': %s\n", r.options.InputFile, err) + } } } else if r.options.InputFile != "" { files, err := fileutilz.ListFilesWithPattern(r.options.InputFile) @@ -630,13 +644,34 @@ func (r *Runner) streamInput() (chan string, error) { defer close(out) if fileutil.FileExists(r.options.InputFile) { - fchan, err := fileutil.ReadFile(r.options.InputFile) - if err != nil { - return - } - for item := range fchan { - if r.options.SkipDedupe || r.testAndSet(item) { - out <- item + // check if input mode is specified for special format handling + if r.options.InputMode != "" { + format := inputformats.GetFormat(r.options.InputMode) + if format == nil { + gologger.Fatal().Msgf("Invalid input mode '%s'. Supported: %s\n", r.options.InputMode, inputformats.SupportedFormats()) + } + finput, err := os.Open(r.options.InputFile) + if err != nil { + gologger.Error().Msgf("Could not open input file '%s': %s\n", r.options.InputFile, err) + return + } + defer finput.Close() + _ = format.Parse(finput, func(item string) bool { + item = strings.TrimSpace(item) + if r.options.SkipDedupe || r.testAndSet(item) { + out <- item + } + return true + }) + } else { + fchan, err := fileutil.ReadFile(r.options.InputFile) + if err != nil { + return + } + for item := range fchan { + if r.options.SkipDedupe || r.testAndSet(item) { + out <- item + } } } } else if r.options.InputFile != "" { @@ -692,6 +727,31 @@ func (r *Runner) loadAndCloseFile(finput *os.File) (numTargets int, err error) { return numTargets, err } +func (r *Runner) loadFromFormat(filePath string, format inputformats.Format) (numTargets int, err error) { + finput, err := os.Open(filePath) + if err != nil { + return 0, err + } + defer finput.Close() + + err = format.Parse(finput, func(target string) bool { + target = strings.TrimSpace(target) + expandedTarget, err := r.countTargetFromRawTarget(target) + if err == nil && expandedTarget > 0 { + numTargets += expandedTarget + r.hm.Set(target, []byte("1")) //nolint + } else if r.options.SkipDedupe && errors.Is(err, duplicateTargetErr) { + if v, ok := r.hm.Get(target); ok { + cnt, _ := strconv.Atoi(string(v)) + _ = r.hm.Set(target, []byte(strconv.Itoa(cnt+1))) + numTargets += 1 + } + } + return true + }) + return numTargets, err +} + func (r *Runner) countTargetFromRawTarget(rawTarget string) (numTargets int, err error) { if rawTarget == "" { return 0, nil From fa1d6ae9e0a358ccdfd22089b5160bcd5ae84b7d Mon Sep 17 00:00:00 2001 From: Mzack9999 Date: Mon, 12 Jan 2026 15:23:19 +0400 Subject: [PATCH 3/5] mod tidy --- go.mod | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 9a52844..fdbaf49 100644 --- a/go.mod +++ b/go.mod @@ -54,6 +54,8 @@ require ( github.com/dustin/go-humanize v1.0.1 github.com/go-viper/mapstructure/v2 v2.4.0 github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 + github.com/projectdiscovery/awesome-search-queries v0.0.0-20260104120501-961ef30f7193 + github.com/seh-msft/burpxml v1.0.1 github.com/weppos/publicsuffix-go v0.50.2 ) @@ -128,7 +130,6 @@ require ( github.com/pierrec/lz4/v4 v4.1.23 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/projectdiscovery/awesome-search-queries v0.0.0-20260104120501-961ef30f7193 // indirect github.com/projectdiscovery/blackrock v0.0.1 // indirect github.com/projectdiscovery/freeport v0.0.7 // indirect github.com/projectdiscovery/gostruct v0.0.2 // indirect @@ -139,7 +140,6 @@ require ( github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect github.com/sashabaranov/go-openai v1.37.0 // indirect - github.com/seh-msft/burpxml v1.0.1 // indirect github.com/shirou/gopsutil/v3 v3.24.2 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sorairolake/lzip-go v0.3.8 // indirect From 430e33f9a15c1757961a2c56b136c1ad324ba58c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fan=20Can=20Bak=C4=B1r?= Date: Mon, 12 Jan 2026 14:23:23 +0300 Subject: [PATCH 4/5] fix: address coderabbit review comments - Handle error from format.Parse in streamInput - Add InputMode validation to ValidateOptions for early error detection - Extract duplicate format validation to getInputFormat helper - Fix shadow variable err in loadFromFormat callback - Add bounds check in test before index access - Add test for malformed XML input - Run go mod tidy to fix dependency marking --- common/inputformats/burp_test.go | 21 ++++++++++++++++++ runner/options.go | 4 ++++ runner/runner.go | 38 +++++++++++++++++++------------- 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/common/inputformats/burp_test.go b/common/inputformats/burp_test.go index ee6a011..ad2db52 100644 --- a/common/inputformats/burp_test.go +++ b/common/inputformats/burp_test.go @@ -66,6 +66,9 @@ func TestBurpFormat_Parse(t *testing.T) { } expectedURLs := []string{"http://example.com/path1", "https://example.com/path2"} + if len(urls) != len(expectedURLs) { + t.Fatalf("Expected %d URLs, got %d: %v", len(expectedURLs), len(urls), urls) + } for i, expected := range expectedURLs { if urls[i] != expected { t.Errorf("Expected URL %d to be '%s', got '%s'", i, expected, urls[i]) @@ -146,3 +149,21 @@ func TestBurpFormat_ParseStopEarly(t *testing.T) { t.Errorf("Expected 1 URL (stopped early), got %d", len(urls)) } } + +func TestBurpFormat_ParseMalformed(t *testing.T) { + malformedXML := ` + + + + +` + + b := NewBurpFormat() + err := b.Parse(strings.NewReader(malformedXML), func(url string) bool { + return true + }) + + if err == nil { + t.Error("Expected error for malformed XML, got nil") + } +} diff --git a/runner/options.go b/runner/options.go index dbccb32..e87bd4d 100644 --- a/runner/options.go +++ b/runner/options.go @@ -680,6 +680,10 @@ func (options *Options) ValidateOptions() error { return fmt.Errorf("file '%s' does not exist", options.InputRawRequest) } + if options.InputMode != "" && inputformats.GetFormat(options.InputMode) == nil { + return fmt.Errorf("invalid input mode '%s', supported formats: %s", options.InputMode, inputformats.SupportedFormats()) + } + if options.Silent { incompatibleFlagsList := flagsIncompatibleWithSilent(options) if len(incompatibleFlagsList) > 0 { diff --git a/runner/runner.go b/runner/runner.go index 714922f..feae613 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -526,11 +526,7 @@ func (r *Runner) prepareInput() { // check if file has been provided if fileutil.FileExists(r.options.InputFile) { // check if input mode is specified for special format handling - if r.options.InputMode != "" { - format := inputformats.GetFormat(r.options.InputMode) - if format == nil { - gologger.Fatal().Msgf("Invalid input mode '%s'. Supported: %s\n", r.options.InputMode, inputformats.SupportedFormats()) - } + if format := r.getInputFormat(); format != nil { numTargets, err := r.loadFromFormat(r.options.InputFile, format) if err != nil { gologger.Fatal().Msgf("Could not parse input file '%s': %s\n", r.options.InputFile, err) @@ -638,6 +634,19 @@ func (r *Runner) testAndSet(k string) bool { return true } +// getInputFormat returns the format for the configured input mode. +// Returns nil if no input mode is configured, or logs fatal if the format is invalid. +func (r *Runner) getInputFormat() inputformats.Format { + if r.options.InputMode == "" { + return nil + } + format := inputformats.GetFormat(r.options.InputMode) + if format == nil { + gologger.Fatal().Msgf("Invalid input mode '%s'. Supported: %s\n", r.options.InputMode, inputformats.SupportedFormats()) + } + return format +} + func (r *Runner) streamInput() (chan string, error) { out := make(chan string) go func() { @@ -645,24 +654,23 @@ func (r *Runner) streamInput() (chan string, error) { if fileutil.FileExists(r.options.InputFile) { // check if input mode is specified for special format handling - if r.options.InputMode != "" { - format := inputformats.GetFormat(r.options.InputMode) - if format == nil { - gologger.Fatal().Msgf("Invalid input mode '%s'. Supported: %s\n", r.options.InputMode, inputformats.SupportedFormats()) - } + if format := r.getInputFormat(); format != nil { finput, err := os.Open(r.options.InputFile) if err != nil { gologger.Error().Msgf("Could not open input file '%s': %s\n", r.options.InputFile, err) return } defer finput.Close() - _ = format.Parse(finput, func(item string) bool { + if err := format.Parse(finput, func(item string) bool { item = strings.TrimSpace(item) if r.options.SkipDedupe || r.testAndSet(item) { out <- item } return true - }) + }); err != nil { + gologger.Error().Msgf("Could not parse input file '%s': %s\n", r.options.InputFile, err) + return + } } else { fchan, err := fileutil.ReadFile(r.options.InputFile) if err != nil { @@ -736,11 +744,11 @@ func (r *Runner) loadFromFormat(filePath string, format inputformats.Format) (nu err = format.Parse(finput, func(target string) bool { target = strings.TrimSpace(target) - expandedTarget, err := r.countTargetFromRawTarget(target) - if err == nil && expandedTarget > 0 { + expandedTarget, countErr := r.countTargetFromRawTarget(target) + if countErr == nil && expandedTarget > 0 { numTargets += expandedTarget r.hm.Set(target, []byte("1")) //nolint - } else if r.options.SkipDedupe && errors.Is(err, duplicateTargetErr) { + } else if r.options.SkipDedupe && errors.Is(countErr, duplicateTargetErr) { if v, ok := r.hm.Get(target); ok { cnt, _ := strconv.Atoi(string(v)) _ = r.hm.Set(target, []byte(strconv.Itoa(cnt+1))) From f445b0aa727f1aaafd27b53e2533180ae7d35e1e Mon Sep 17 00:00:00 2001 From: Mzack9999 Date: Mon, 12 Jan 2026 15:34:24 +0400 Subject: [PATCH 5/5] docs update + flags validation + debug logging --- README.md | 8 +++++--- common/inputformats/burp.go | 4 +++- runner/options.go | 4 ++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 283e669..58ba92d 100644 --- a/README.md +++ b/README.md @@ -92,9 +92,10 @@ Usage: Flags: INPUT: - -l, -list string input file containing list of hosts to process - -rr, -request string file containing raw request - -u, -target string[] input target host(s) to probe + -l, -list string input file containing list of hosts to process + -rr, -request string file containing raw request + -u, -target string[] input target host(s) to probe + -im, -input-mode string mode of input file (burp) PROBES: -sc, -status-code display response status-code @@ -279,6 +280,7 @@ For details about running httpx, see https://docs.projectdiscovery.io/tools/http # Notes - As default, `httpx` probe with **HTTPS** scheme and fall-back to **HTTP** only if **HTTPS** is not reachable. +- Burp Suite XML exports can be used as input with `-l burp-export.xml -im burp` - The `-no-fallback` flag can be used to probe and display both **HTTP** and **HTTPS** result. - Custom scheme for ports can be defined, for example `-ports http:443,http:80,https:8443` - Custom resolver supports multiple protocol (**doh|tcp|udp**) in form of `protocol:resolver:port` (e.g. `udp:127.0.0.1:53`) diff --git a/common/inputformats/burp.go b/common/inputformats/burp.go index 0e49b2f..8af3432 100644 --- a/common/inputformats/burp.go +++ b/common/inputformats/burp.go @@ -4,6 +4,7 @@ import ( "io" "github.com/pkg/errors" + "github.com/projectdiscovery/gologger" "github.com/seh-msft/burpxml" ) @@ -30,8 +31,9 @@ func (b *BurpFormat) Parse(input io.Reader, callback func(url string) bool) erro return errors.Wrap(err, "could not parse burp xml") } - for _, item := range items.Items { + for i, item := range items.Items { if item.Url == "" { + gologger.Debug().Msgf("Skipping burp item %d: empty URL", i) continue } if !callback(item.Url) { diff --git a/runner/options.go b/runner/options.go index e87bd4d..a8bfeb5 100644 --- a/runner/options.go +++ b/runner/options.go @@ -684,6 +684,10 @@ func (options *Options) ValidateOptions() error { return fmt.Errorf("invalid input mode '%s', supported formats: %s", options.InputMode, inputformats.SupportedFormats()) } + if options.InputMode != "" && options.InputFile == "" { + return errors.New("-im/-input-mode requires -l/-list to specify an input file") + } + if options.Silent { incompatibleFlagsList := flagsIncompatibleWithSilent(options) if len(incompatibleFlagsList) > 0 {