From 6c1ca7498f937079dd954f2c328a43be82fd7734 Mon Sep 17 00:00:00 2001 From: virusvfv Date: Sun, 26 Jan 2025 12:22:01 -0500 Subject: [PATCH] Add Basic/NTLM authorization with FUZZ and pass-the-hash support (via -basic -ntlm parameter) Add -tcpaggr parameter - set max http requests in one tcp connection --- go.mod | 8 ++- go.sum | 36 +++------- help.go | 2 +- main.go | 3 + pkg/ffuf/autocalibration.go | 2 +- pkg/ffuf/config.go | 6 ++ pkg/ffuf/interfaces.go | 2 +- pkg/ffuf/job.go | 23 ++++++- pkg/ffuf/optionsparser.go | 22 ++++++ pkg/ffuf/request.go | 6 ++ pkg/ffuf/util.go | 3 + pkg/runner/simple.go | 129 ++++++++++++++++++++++++++++++------ 12 files changed, 187 insertions(+), 55 deletions(-) diff --git a/go.mod b/go.mod index 390031ec..1a6d1f9d 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/ffuf/ffuf/v2 -go 1.17 +go 1.22 require ( github.com/PuerkitoBio/goquery v1.8.0 @@ -13,6 +13,8 @@ require ( require ( github.com/andybalholm/cascadia v1.3.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/sys v0.5.0 // indirect + github.com/virusvfv/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sys v0.29.0 // indirect ) diff --git a/go.sum b/go.sum index 8cdda315..cffe6be8 100644 --- a/go.sum +++ b/go.sum @@ -18,39 +18,23 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +github.com/virusvfv/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:yxcft0/UAsuAYQC1cJYETjxNgotqfAh5rxOHsApfSTU= +github.com/virusvfv/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:W+6mEbp0dcxW59pPepFV7CQb/Z/94qkXNzl9giJw2r4= +github.com/virusvfv/go-ntlmssp v0.0.0-20250126162905-8973e0bbab20 h1:Fi068xJ2SgLCYXiQBxH3veYujxOLnyIooPFBZIZYOcI= +github.com/virusvfv/go-ntlmssp v0.0.0-20250126162905-8973e0bbab20/go.mod h1:W+6mEbp0dcxW59pPepFV7CQb/Z/94qkXNzl9giJw2r4= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/help.go b/help.go index a2f0c2a8..6ad66453 100644 --- a/help.go +++ b/help.go @@ -54,7 +54,7 @@ func Usage() { Description: "Options controlling the HTTP request and its parts.", Flags: make([]UsageFlag, 0), Hidden: false, - ExpectedFlags: []string{"cc", "ck", "H", "X", "b", "d", "r", "u", "raw", "recursion", "recursion-depth", "recursion-strategy", "replay-proxy", "timeout", "ignore-body", "x", "sni", "http2"}, + ExpectedFlags: []string{"cc", "ck", "H", "X", "b", "d", "r", "u", "raw", "recursion", "recursion-depth", "recursion-strategy", "replay-proxy", "timeout", "ignore-body", "x", "sni", "tcpaggr", "basic", "ntlm", "http2"}, } u_general := UsageSection{ Name: "GENERAL OPTIONS", diff --git a/main.go b/main.go index 64b4711f..5fe62c24 100644 --- a/main.go +++ b/main.go @@ -89,6 +89,7 @@ func ParseFlags(opts *ffuf.ConfigOptions) *ffuf.ConfigOptions { flag.IntVar(&opts.General.Threads, "t", opts.General.Threads, "Number of concurrent threads.") flag.IntVar(&opts.HTTP.RecursionDepth, "recursion-depth", opts.HTTP.RecursionDepth, "Maximum recursion depth.") flag.IntVar(&opts.HTTP.Timeout, "timeout", opts.HTTP.Timeout, "HTTP request timeout in seconds.") + flag.IntVar(&opts.HTTP.TCPAggr, "tcpaggr", opts.HTTP.TCPAggr, "max HTTP request in one TCP connection. Default 50. Set it to 1 to switch proxies") flag.IntVar(&opts.Input.InputNum, "input-num", opts.Input.InputNum, "Number of inputs to test. Used in conjunction with --input-cmd.") flag.StringVar(&opts.General.AutoCalibrationKeyword, "ack", opts.General.AutoCalibrationKeyword, "Autocalibration keyword") flag.StringVar(&opts.HTTP.ClientCert, "cc", "", "Client cert for authentication. Client key needs to be defined as well for this to work") @@ -115,6 +116,8 @@ func ParseFlags(opts *ffuf.ConfigOptions) *ffuf.ConfigOptions { flag.StringVar(&opts.HTTP.RecursionStrategy, "recursion-strategy", opts.HTTP.RecursionStrategy, "Recursion strategy: \"default\" for a redirect based, and \"greedy\" to recurse on all matches") flag.StringVar(&opts.HTTP.URL, "u", opts.HTTP.URL, "Target URL") flag.StringVar(&opts.HTTP.SNI, "sni", opts.HTTP.SNI, "Target TLS SNI, does not support FUZZ keyword") + flag.StringVar(&opts.HTTP.Basic, "basic", opts.HTTP.Basic, "Basic auth. Support FUZZ keyword. Ex: username:FUZZ or FUZZ:password") + flag.StringVar(&opts.HTTP.Ntlm, "ntlm", opts.HTTP.Ntlm, "NTLM auth. Support pass-the-hash. Ex: username:FUZZ, FUZZ:password, user:11223344556677889900aadeeff") flag.StringVar(&opts.Input.Extensions, "e", opts.Input.Extensions, "Comma separated list of extensions. Extends FUZZ keyword.") flag.StringVar(&opts.Input.InputMode, "mode", opts.Input.InputMode, "Multi-wordlist operation mode. Available modes: clusterbomb, pitchfork, sniper") flag.StringVar(&opts.Input.InputShell, "input-shell", opts.Input.InputShell, "Shell to be used for running command") diff --git a/pkg/ffuf/autocalibration.go b/pkg/ffuf/autocalibration.go index 322f7abd..315c3089 100644 --- a/pkg/ffuf/autocalibration.go +++ b/pkg/ffuf/autocalibration.go @@ -89,7 +89,7 @@ func (j *Job) calibrationRequest(inputs map[string][]byte) (Response, error) { log.Printf("%s", err) return Response{}, err } - resp, err := j.Runner.Execute(&req) + resp, err := j.Runner.Execute(&req, false) if err != nil { j.Output.Error(fmt.Sprintf("Encountered an error while executing autocalibration request: %s\n", err)) j.incError() diff --git a/pkg/ffuf/config.go b/pkg/ffuf/config.go index 5680e5cc..c6a3eae5 100644 --- a/pkg/ffuf/config.go +++ b/pkg/ffuf/config.go @@ -61,12 +61,15 @@ type Config struct { StopOnErrors bool `json:"stop_errors"` Threads int `json:"threads"` Timeout int `json:"timeout"` + TCPAggr int `json:"tcpaggr"` Url string `json:"url"` Verbose bool `json:"verbose"` Wordlists []string `json:"wordlists"` Http2 bool `json:"http2"` ClientCert string `json:"client-cert"` ClientKey string `json:"client-key"` + Basic string `json:"basic"` + Ntlm string `json:"ntlm"` } type InputProviderConfig struct { @@ -116,6 +119,7 @@ func NewConfig(ctx context.Context, cancel context.CancelFunc) Config { conf.RequestFile = "" conf.RequestProto = "https" conf.SNI = "" + conf.TCPAggr = 50 conf.ScraperFile = "" conf.Scrapers = "all" conf.StopOn403 = false @@ -126,6 +130,8 @@ func NewConfig(ctx context.Context, cancel context.CancelFunc) Config { conf.Verbose = false conf.Wordlists = []string{} conf.Http2 = false + conf.Basic = "" + conf.Ntlm = "" return conf } diff --git a/pkg/ffuf/interfaces.go b/pkg/ffuf/interfaces.go index 16f627f6..4834ddba 100644 --- a/pkg/ffuf/interfaces.go +++ b/pkg/ffuf/interfaces.go @@ -29,7 +29,7 @@ type FilterProvider interface { // RunnerProvider is an interface for request executors type RunnerProvider interface { Prepare(input map[string][]byte, basereq *Request) (Request, error) - Execute(req *Request) (Response, error) + Execute(req *Request, newConn bool) (Response, error) Dump(req *Request) ([]byte, error) } diff --git a/pkg/ffuf/job.go b/pkg/ffuf/job.go index fdc84175..a26c4924 100644 --- a/pkg/ffuf/job.go +++ b/pkg/ffuf/job.go @@ -30,6 +30,8 @@ type Job struct { Paused bool Count403 int Count429 int + ConnCount int + NewConn bool Error string Rate *RateThrottle startTime time.Time @@ -52,6 +54,8 @@ func NewJob(conf *Config) *Job { var j Job j.Config = conf j.Counter = 0 + j.ConnCount = 0 + j.NewConn = false j.ErrorCounter = 0 j.SpuriousErrorCounter = 0 j.Running = false @@ -403,7 +407,17 @@ func (j *Job) runTask(input map[string][]byte, position int, retried bool) { return } - resp, err := j.Runner.Execute(&req) + //make no more 50 http-requests in one TCP connection + //if j.NewConn set - pass it to Runner.Execute + newConn := false + if j.ConnCount > j.Config.TCPAggr || j.NewConn { + newConn = true + j.ConnCount = 0 + j.NewConn = false + } + + resp, err := j.Runner.Execute(&req, newConn) + j.ConnCount++ if err != nil { if retried { j.incError() @@ -464,6 +478,11 @@ func (j *Job) runTask(input map[string][]byte, position int, retried bool) { } if j.isMatch(resp) { + //if NTLM i set and HTTP code is not 401 set j.NewConn flag + if j.Config.Ntlm != "" && resp.StatusCode != 401 { + j.NewConn = true + } + // Re-send request through replay-proxy if needed if j.ReplayRunner != nil { replayreq, err := j.ReplayRunner.Prepare(input, &basereq) @@ -473,7 +492,7 @@ func (j *Job) runTask(input map[string][]byte, position int, retried bool) { j.incError() log.Printf("%s", err) } else { - _, _ = j.ReplayRunner.Execute(&replayreq) + _, _ = j.ReplayRunner.Execute(&replayreq, false) } } j.Output.Result(resp) diff --git a/pkg/ffuf/optionsparser.go b/pkg/ffuf/optionsparser.go index e4a34e2a..a834124b 100644 --- a/pkg/ffuf/optionsparser.go +++ b/pkg/ffuf/optionsparser.go @@ -41,7 +41,10 @@ type HTTPOptions struct { SNI string `json:"sni"` Timeout int `json:"timeout"` URL string `json:"url"` + Basic string `json:"basic"` + Ntlm string `json:"ntlm"` Http2 bool `json:"http2"` + TCPAggr int `json:"tcpaggr"` ClientCert string `json:"client-cert"` ClientKey string `json:"client-key"` } @@ -157,6 +160,8 @@ func NewConfigOptions() *ConfigOptions { c.HTTP.Timeout = 10 c.HTTP.SNI = "" c.HTTP.URL = "" + c.HTTP.Basic = "" + c.HTTP.Ntlm = "" c.HTTP.Http2 = false c.Input.DirSearchCompat = false c.Input.Encoders = []string{} @@ -365,6 +370,17 @@ func ConfigFromOptions(parseOpts *ConfigOptions, ctx context.Context, cancel con conf.SNI = parseOpts.HTTP.SNI } + // Prepare Auth + if parseOpts.HTTP.Basic != "" { + conf.Basic = parseOpts.HTTP.Basic + } + if parseOpts.HTTP.Ntlm != "" { + conf.Ntlm = parseOpts.HTTP.Ntlm + } + if parseOpts.HTTP.TCPAggr != 0 { + conf.TCPAggr = parseOpts.HTTP.TCPAggr + } + // prepare cert if parseOpts.HTTP.ClientCert != "" { conf.ClientCert = parseOpts.HTTP.ClientCert @@ -704,6 +720,12 @@ func keywordPresent(keyword string, conf *Config) bool { if strings.Contains(conf.Data, keyword) { return true } + if strings.Contains(conf.Basic, keyword) { + return true + } + if strings.Contains(conf.Ntlm, keyword) { + return true + } for k, v := range conf.Headers { if strings.Contains(k, keyword) { return true diff --git a/pkg/ffuf/request.go b/pkg/ffuf/request.go index de93cbfe..d7f5d4e4 100644 --- a/pkg/ffuf/request.go +++ b/pkg/ffuf/request.go @@ -14,12 +14,17 @@ type Request struct { Input map[string][]byte Position int Raw string + Auth string } func NewRequest(conf *Config) Request { var req Request req.Method = conf.Method req.Url = conf.Url + req.Auth = conf.Basic + if conf.Ntlm != "" { + req.Auth = conf.Ntlm + } req.Headers = make(map[string]string) return req } @@ -45,6 +50,7 @@ func CopyRequest(basereq *Request) Request { req.Method = basereq.Method req.Host = basereq.Host req.Url = basereq.Url + req.Auth = basereq.Auth req.Headers = make(map[string]string, len(basereq.Headers)) for h, v := range basereq.Headers { diff --git a/pkg/ffuf/util.go b/pkg/ffuf/util.go index 8cfef06d..cbd48541 100644 --- a/pkg/ffuf/util.go +++ b/pkg/ffuf/util.go @@ -60,6 +60,9 @@ func RequestContainsKeyword(req Request, kw string) bool { if strings.Contains(string(req.Data), kw) { return true } + if strings.Contains(req.Auth, kw) { + return true + } for k, v := range req.Headers { if strings.Contains(k, kw) || strings.Contains(v, kw) { return true diff --git a/pkg/runner/simple.go b/pkg/runner/simple.go index 8929cc0c..f92f3271 100644 --- a/pkg/runner/simple.go +++ b/pkg/runner/simple.go @@ -20,6 +20,7 @@ import ( "github.com/ffuf/ffuf/v2/pkg/ffuf" "github.com/andybalholm/brotli" + "github.com/virusvfv/go-ntlmssp" ) // Download results < 5MB @@ -30,8 +31,7 @@ type SimpleRunner struct { client *http.Client } -func NewSimpleRunner(conf *ffuf.Config, replay bool) ffuf.RunnerProvider { - var simplerunner SimpleRunner +func NewHttpClient(conf *ffuf.Config, replay bool) *http.Client { proxyURL := http.ProxyFromEnvironment customProxy := "" @@ -53,28 +53,100 @@ func NewSimpleRunner(conf *ffuf.Config, replay bool) ffuf.RunnerProvider { cert = []tls.Certificate{tmp} } - simplerunner.config = conf - simplerunner.client = &http.Client{ + if conf.ClientCert != "" && conf.ClientKey != "" { + tmp, _ := tls.LoadX509KeyPair(conf.ClientCert, conf.ClientKey) + cert = []tls.Certificate{tmp} + } + + client := &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, Timeout: time.Duration(time.Duration(conf.Timeout) * time.Second), - Transport: &http.Transport{ - ForceAttemptHTTP2: conf.Http2, - Proxy: proxyURL, - MaxIdleConns: 1000, - MaxIdleConnsPerHost: 500, - MaxConnsPerHost: 500, - DialContext: (&net.Dialer{ - Timeout: time.Duration(time.Duration(conf.Timeout) * time.Second), - }).DialContext, - TLSHandshakeTimeout: time.Duration(time.Duration(conf.Timeout) * time.Second), - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - MinVersion: tls.VersionTLS10, - Renegotiation: tls.RenegotiateOnceAsClient, - ServerName: conf.SNI, - Certificates: cert, + Transport: ntlmssp.Negotiator{ + RoundTripper: &http.Transport{ + ForceAttemptHTTP2: false, + Proxy: proxyURL, + MaxIdleConns: 1000, + MaxIdleConnsPerHost: 500, + MaxConnsPerHost: 500, + IdleConnTimeout: 5 * time.Second, + ///DisableKeepAlives: true, + + DialContext: (&net.Dialer{ + Timeout: time.Duration(time.Duration(conf.Timeout) * time.Second), + //KeepAlive: 5 * time.Second, + }).DialContext, + TLSHandshakeTimeout: time.Duration(time.Duration(conf.Timeout) * time.Second), + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + MinVersion: tls.VersionTLS10, + Renegotiation: tls.RenegotiateOnceAsClient, + ServerName: conf.SNI, + Certificates: cert, + }, }, }} + return client +} + +func NewSimpleRunner(conf *ffuf.Config, replay bool) ffuf.RunnerProvider { + var simplerunner SimpleRunner + /* + proxyURL := http.ProxyFromEnvironment + customProxy := "" + + if replay { + customProxy = conf.ReplayProxyURL + } else { + customProxy = conf.ProxyURL + } + if len(customProxy) > 0 { + pu, err := url.Parse(customProxy) + if err == nil { + proxyURL = http.ProxyURL(pu) + } + } + cert := []tls.Certificate{} + + if conf.ClientCert != "" && conf.ClientKey != "" { + tmp, _ := tls.LoadX509KeyPair(conf.ClientCert, conf.ClientKey) + cert = []tls.Certificate{tmp} + } + + */ + + simplerunner.config = conf + simplerunner.client = NewHttpClient(conf, replay) + + /* + simplerunner.client = &http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, + Timeout: time.Duration(time.Duration(conf.Timeout) * time.Second), + Transport: ntlmssp.Negotiator{ + RoundTripper: &http.Transport{ + ForceAttemptHTTP2: false, + Proxy: proxyURL, + MaxIdleConns: 1000, + MaxIdleConnsPerHost: 500, + MaxConnsPerHost: 500, + IdleConnTimeout: 5 * time.Second, + ///DisableKeepAlives: true, + + DialContext: (&net.Dialer{ + Timeout: time.Duration(time.Duration(conf.Timeout) * time.Second), + //KeepAlive: 5 * time.Second, + }).DialContext, + TLSHandshakeTimeout: time.Duration(time.Duration(conf.Timeout) * time.Second), + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + MinVersion: tls.VersionTLS10, + Renegotiation: tls.RenegotiateOnceAsClient, + ServerName: conf.SNI, + Certificates: cert, + }, + }, + }} + + */ if conf.FollowRedirects { simplerunner.client.CheckRedirect = nil @@ -94,6 +166,7 @@ func (r *SimpleRunner) Prepare(input map[string][]byte, basereq *ffuf.Request) ( } req.Headers = headers req.Url = strings.ReplaceAll(req.Url, keyword, string(inputitem)) + req.Auth = strings.ReplaceAll(req.Auth, keyword, string(inputitem)) req.Data = []byte(strings.ReplaceAll(string(req.Data), keyword, string(inputitem))) } @@ -101,7 +174,7 @@ func (r *SimpleRunner) Prepare(input map[string][]byte, basereq *ffuf.Request) ( return req, nil } -func (r *SimpleRunner) Execute(req *ffuf.Request) (ffuf.Response, error) { +func (r *SimpleRunner) Execute(req *ffuf.Request, newConn bool) (ffuf.Response, error) { var httpreq *http.Request var err error var rawreq []byte @@ -125,6 +198,10 @@ func (r *SimpleRunner) Execute(req *ffuf.Request) (ffuf.Response, error) { return ffuf.Response{}, err } + if req.Auth != "" && len(strings.Split(req.Auth, ":")) > 1 { + httpreq.SetBasicAuth(strings.SplitN(req.Auth, ":", 2)[0], strings.SplitN(req.Auth, ":", 2)[1]) + } + // set default User-Agent header if not present if _, ok := req.Headers["User-Agent"]; !ok { req.Headers["User-Agent"] = fmt.Sprintf("%s v%s", "Fuzz Faster U Fool", ffuf.Version()) @@ -150,6 +227,10 @@ func (r *SimpleRunner) Execute(req *ffuf.Request) (ffuf.Response, error) { rawreq, _ = httputil.DumpRequestOut(httpreq, true) } + if newConn { + r.client = NewHttpClient(r.config, false) + } + httpresp, err := r.client.Do(httpreq) if err != nil { return ffuf.Response{}, err @@ -206,6 +287,12 @@ func (r *SimpleRunner) Execute(req *ffuf.Request) (ffuf.Response, error) { resp.ContentWords = int64(wordsSize) resp.ContentLines = int64(linesSize) resp.Time = firstByteTime + + /* + if resp.StatusCode == 500 { + time.Sleep(5 * time.Second) + } + */ return resp, nil }