From efd8ab93428a4119ad4d4e496a81a940cc1decf6 Mon Sep 17 00:00:00 2001 From: Ice3man Date: Thu, 7 Nov 2024 21:07:54 +0530 Subject: [PATCH] feat: auth file support enhancements for more complex scenarios + misc --- internal/runner/lazy.go | 9 +- pkg/authprovider/authx/cookies_auth.go | 3 + pkg/authprovider/authx/dynamic.go | 153 ++++++++++++++++--------- pkg/authprovider/authx/strategy.go | 12 +- pkg/authprovider/file.go | 8 +- 5 files changed, 122 insertions(+), 63 deletions(-) diff --git a/internal/runner/lazy.go b/internal/runner/lazy.go index 900850b673..b0d3341d46 100644 --- a/internal/runner/lazy.go +++ b/internal/runner/lazy.go @@ -92,8 +92,13 @@ func GetLazyAuthFetchCallback(opts *AuthLazyFetchOptions) authx.LazyFetchSecret } // dynamic values for k, v := range e.OperatorsResult.DynamicValues { - if len(v) > 0 { - data[k] = v[0] + // Iterate through all the values and choose the + // largest value as the extracted value + for _, value := range v { + oldVal, ok := data[k] + if !ok || len(value) > len(oldVal.(string)) { + data[k] = value + } } } // named extractors diff --git a/pkg/authprovider/authx/cookies_auth.go b/pkg/authprovider/authx/cookies_auth.go index 7f3e756a71..9df56fb1b1 100644 --- a/pkg/authprovider/authx/cookies_auth.go +++ b/pkg/authprovider/authx/cookies_auth.go @@ -33,6 +33,9 @@ func (s *CookiesAuthStrategy) Apply(req *http.Request) { // ApplyOnRR applies the cookies auth strategy to the retryable request func (s *CookiesAuthStrategy) ApplyOnRR(req *retryablehttp.Request) { + // Before adding new cookies, remove existing cookies + req.Header.Del("Cookie") + for _, cookie := range s.Data.Cookies { c := &http.Cookie{ Name: cookie.Key, diff --git a/pkg/authprovider/authx/dynamic.go b/pkg/authprovider/authx/dynamic.go index 0e210cf5e7..f61fc5d31c 100644 --- a/pkg/authprovider/authx/dynamic.go +++ b/pkg/authprovider/authx/dynamic.go @@ -9,6 +9,7 @@ import ( "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/replacer" errorutil "github.com/projectdiscovery/utils/errors" + sliceutil "github.com/projectdiscovery/utils/slice" ) type LazyFetchSecret func(d *Dynamic) error @@ -22,7 +23,8 @@ var ( // ex: username and password are dynamic secrets, the actual secret is the token obtained // after authenticating with the username and password type Dynamic struct { - Secret `yaml:",inline"` // this is a static secret that will be generated after the dynamic secret is resolved + *Secret `yaml:",inline"` // this is a static secret that will be generated after the dynamic secret is resolved + Secrets []*Secret `yaml:"secrets"` TemplatePath string `json:"template" yaml:"template"` Variables []KV `json:"variables" yaml:"variables"` Input string `json:"input" yaml:"input"` // (optional) target for the dynamic secret @@ -33,6 +35,22 @@ type Dynamic struct { error error `json:"-" yaml:"-"` // error if any } +func (d *Dynamic) GetDomainAndDomainRegex() ([]string, []string) { + var domains []string + var domainRegex []string + for _, secret := range d.Secrets { + domains = append(domains, secret.Domains...) + domainRegex = append(domainRegex, secret.DomainsRegex...) + } + if d.Secret != nil { + domains = append(domains, d.Secret.Domains...) + domainRegex = append(domainRegex, d.Secret.DomainsRegex...) + } + uniqueDomains := sliceutil.Dedupe(domains) + uniqueDomainRegex := sliceutil.Dedupe(domainRegex) + return uniqueDomains, uniqueDomainRegex +} + func (d *Dynamic) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &d); err != nil { return err @@ -41,7 +59,7 @@ func (d *Dynamic) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &s); err != nil { return err } - d.Secret = s + d.Secret = &s return nil } @@ -54,9 +72,18 @@ func (d *Dynamic) Validate() error { if len(d.Variables) == 0 { return errorutil.New("variables are required for dynamic secret") } - d.skipCookieParse = true // skip cookie parsing in dynamic secrets during validation - if err := d.Secret.Validate(); err != nil { - return err + + if d.Secret != nil { + d.Secret.skipCookieParse = true // skip cookie parsing in dynamic secrets during validation + if err := d.Secret.Validate(); err != nil { + return err + } + } + for _, secret := range d.Secrets { + secret.skipCookieParse = true + if err := secret.Validate(); err != nil { + return err + } } return nil } @@ -74,76 +101,98 @@ func (d *Dynamic) SetLazyFetchCallback(callback LazyFetchSecret) { return fmt.Errorf("no extracted values found for dynamic secret") } - // evaluate headers - for i, header := range d.Headers { - if strings.Contains(header.Value, "{{") { - header.Value = replacer.Replace(header.Value, d.Extracted) + if d.Secret != nil { + if err := d.applyValuesToSecret(d.Secret); err != nil { + return err } - if strings.Contains(header.Key, "{{") { - header.Key = replacer.Replace(header.Key, d.Extracted) - } - d.Headers[i] = header } - // evaluate cookies - for i, cookie := range d.Cookies { - if strings.Contains(cookie.Value, "{{") { - cookie.Value = replacer.Replace(cookie.Value, d.Extracted) - } - if strings.Contains(cookie.Key, "{{") { - cookie.Key = replacer.Replace(cookie.Key, d.Extracted) + for _, secret := range d.Secrets { + if err := d.applyValuesToSecret(secret); err != nil { + return err } - if strings.Contains(cookie.Raw, "{{") { - cookie.Raw = replacer.Replace(cookie.Raw, d.Extracted) - } - d.Cookies[i] = cookie } + return nil + } +} - // evaluate query params - for i, query := range d.Params { - if strings.Contains(query.Value, "{{") { - query.Value = replacer.Replace(query.Value, d.Extracted) - } - if strings.Contains(query.Key, "{{") { - query.Key = replacer.Replace(query.Key, d.Extracted) - } - d.Params[i] = query +func (d *Dynamic) applyValuesToSecret(secret *Secret) error { + // evaluate headers + for i, header := range secret.Headers { + if strings.Contains(header.Value, "{{") { + header.Value = replacer.Replace(header.Value, d.Extracted) + } + if strings.Contains(header.Key, "{{") { + header.Key = replacer.Replace(header.Key, d.Extracted) } + secret.Headers[i] = header + } - // check username, password and token - if strings.Contains(d.Username, "{{") { - d.Username = replacer.Replace(d.Username, d.Extracted) + // evaluate cookies + for i, cookie := range secret.Cookies { + if strings.Contains(cookie.Value, "{{") { + cookie.Value = replacer.Replace(cookie.Value, d.Extracted) } - if strings.Contains(d.Password, "{{") { - d.Password = replacer.Replace(d.Password, d.Extracted) + if strings.Contains(cookie.Key, "{{") { + cookie.Key = replacer.Replace(cookie.Key, d.Extracted) } - if strings.Contains(d.Token, "{{") { - d.Token = replacer.Replace(d.Token, d.Extracted) + if strings.Contains(cookie.Raw, "{{") { + cookie.Raw = replacer.Replace(cookie.Raw, d.Extracted) } + secret.Cookies[i] = cookie + } + + // evaluate query params + for i, query := range secret.Params { + if strings.Contains(query.Value, "{{") { + query.Value = replacer.Replace(query.Value, d.Extracted) + } + if strings.Contains(query.Key, "{{") { + query.Key = replacer.Replace(query.Key, d.Extracted) + } + secret.Params[i] = query + } - // now attempt to parse the cookies - d.skipCookieParse = false - for i, cookie := range d.Cookies { - if cookie.Raw != "" { - if err := cookie.Parse(); err != nil { - return fmt.Errorf("[%s] invalid raw cookie in cookiesAuth: %s", d.TemplatePath, err) - } - d.Cookies[i] = cookie + // check username, password and token + if strings.Contains(secret.Username, "{{") { + secret.Username = replacer.Replace(secret.Username, d.Extracted) + } + if strings.Contains(secret.Password, "{{") { + secret.Password = replacer.Replace(secret.Password, d.Extracted) + } + if strings.Contains(secret.Token, "{{") { + secret.Token = replacer.Replace(secret.Token, d.Extracted) + } + + // now attempt to parse the cookies + secret.skipCookieParse = false + for i, cookie := range secret.Cookies { + if cookie.Raw != "" { + if err := cookie.Parse(); err != nil { + return fmt.Errorf("[%s] invalid raw cookie in cookiesAuth: %s", d.TemplatePath, err) } + secret.Cookies[i] = cookie } - return nil } + return nil } -// GetStrategy returns the auth strategy for the dynamic secret -func (d *Dynamic) GetStrategy() AuthStrategy { +// GetStrategy returns the auth strategies for the dynamic secret +func (d *Dynamic) GetStrategies() []AuthStrategy { if !d.fetched { _ = d.Fetch(true) } if d.error != nil { return nil } - return d.Secret.GetStrategy() + var strategies []AuthStrategy + if d.Secret != nil { + strategies = append(strategies, d.Secret.GetStrategy()) + } + for _, secret := range d.Secrets { + strategies = append(strategies, secret.GetStrategy()) + } + return strategies } // Fetch fetches the dynamic secret diff --git a/pkg/authprovider/authx/strategy.go b/pkg/authprovider/authx/strategy.go index 8204083989..775862954c 100644 --- a/pkg/authprovider/authx/strategy.go +++ b/pkg/authprovider/authx/strategy.go @@ -24,16 +24,16 @@ type DynamicAuthStrategy struct { // Apply applies the strategy to the request func (d *DynamicAuthStrategy) Apply(req *http.Request) { - strategy := d.Dynamic.GetStrategy() - if strategy != nil { - strategy.Apply(req) + strategy := d.Dynamic.GetStrategies() + for _, s := range strategy { + s.Apply(req) } } // ApplyOnRR applies the strategy to the retryable request func (d *DynamicAuthStrategy) ApplyOnRR(req *retryablehttp.Request) { - strategy := d.Dynamic.GetStrategy() - if strategy != nil { - strategy.ApplyOnRR(req) + strategy := d.Dynamic.GetStrategies() + for _, s := range strategy { + s.ApplyOnRR(req) } } diff --git a/pkg/authprovider/file.go b/pkg/authprovider/file.go index 3a32a94fe4..64cfcb8793 100644 --- a/pkg/authprovider/file.go +++ b/pkg/authprovider/file.go @@ -85,8 +85,10 @@ func (f *FileAuthProvider) init() { } } for _, dynamic := range f.store.Dynamic { - if len(dynamic.DomainsRegex) > 0 { - for _, domain := range dynamic.DomainsRegex { + domain, domainsRegex := dynamic.GetDomainAndDomainRegex() + + if len(domainsRegex) > 0 { + for _, domain := range domainsRegex { if f.compiled == nil { f.compiled = make(map[*regexp.Regexp][]authx.AuthStrategy) } @@ -101,7 +103,7 @@ func (f *FileAuthProvider) init() { } } } - for _, domain := range dynamic.Domains { + for _, domain := range domain { if f.domains == nil { f.domains = make(map[string][]authx.AuthStrategy) }