这是indexloc提供的服务,不要输入任何密码
Skip to content

Add Basic/NTLM authorization with FUZZ and pass-the-hash support #826

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/ffuf/ffuf/v2

go 1.17
go 1.22

require (
github.com/PuerkitoBio/goquery v1.8.0
Expand All @@ -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
)
36 changes: 10 additions & 26 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
2 changes: 1 addition & 1 deletion help.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion pkg/ffuf/autocalibration.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
6 changes: 6 additions & 0 deletions pkg/ffuf/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,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 {
Expand Down Expand Up @@ -117,6 +120,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
Expand All @@ -127,6 +131,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
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/ffuf/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
23 changes: 21 additions & 2 deletions pkg/ffuf/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type Job struct {
Paused bool
Count403 int
Count429 int
ConnCount int
NewConn bool
Error string
Rate *RateThrottle
startTime time.Time
Expand All @@ -53,6 +55,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
Expand Down Expand Up @@ -406,7 +410,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 {
req.Error = err.Error()
}
Expand Down Expand Up @@ -488,6 +502,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)
Expand All @@ -497,7 +516,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)
Expand Down
22 changes: 22 additions & 0 deletions pkg/ffuf/optionsparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
Expand Down Expand Up @@ -158,6 +161,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{}
Expand Down Expand Up @@ -367,6 +372,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
Expand Down Expand Up @@ -707,6 +723,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
Expand Down
22 changes: 14 additions & 8 deletions pkg/ffuf/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import (

// Request holds the meaningful data that is passed for runner for making the query
type Request struct {
Method string
Host string
Url string
Headers map[string]string
Data []byte
Input map[string][]byte
Position int
Raw string
Method string
Host string
Url string
Headers map[string]string
Data []byte
Input map[string][]byte
Position int
Raw string
Auth string
Error string
Timestamp time.Time
}
Expand All @@ -23,6 +24,10 @@ 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
}
Expand All @@ -48,6 +53,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 {
Expand Down
3 changes: 3 additions & 0 deletions pkg/ffuf/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading