Path: blob/dev/pkg/authprovider/authx/dynamic.go
2845 views
package authx12import (3"fmt"4"strings"5"sync/atomic"67"github.com/projectdiscovery/gologger"8"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/replacer"9"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"10"github.com/projectdiscovery/utils/errkit"11sliceutil "github.com/projectdiscovery/utils/slice"12)1314type LazyFetchSecret func(d *Dynamic) error1516var (17_ json.Unmarshaler = &Dynamic{}18)1920// Dynamic is a struct for dynamic secret or credential21// these are high level secrets that take action to generate the actual secret22// ex: username and password are dynamic secrets, the actual secret is the token obtained23// after authenticating with the username and password24type Dynamic struct {25*Secret `yaml:",inline"` // this is a static secret that will be generated after the dynamic secret is resolved26Secrets []*Secret `yaml:"secrets"`27TemplatePath string `json:"template" yaml:"template"`28Variables []KV `json:"variables" yaml:"variables"`29Input string `json:"input" yaml:"input"` // (optional) target for the dynamic secret30Extracted map[string]interface{} `json:"-" yaml:"-"` // extracted values from the dynamic secret31fetchCallback LazyFetchSecret `json:"-" yaml:"-"`32fetched *atomic.Bool `json:"-" yaml:"-"` // atomic flag to check if the secret has been fetched33fetching *atomic.Bool `json:"-" yaml:"-"` // atomic flag to prevent recursive fetch calls34error error `json:"-" yaml:"-"` // error if any35}3637func (d *Dynamic) GetDomainAndDomainRegex() ([]string, []string) {38var domains []string39var domainRegex []string40for _, secret := range d.Secrets {41domains = append(domains, secret.Domains...)42domainRegex = append(domainRegex, secret.DomainsRegex...)43}44if d.Secret != nil {45domains = append(domains, d.Domains...)46domainRegex = append(domainRegex, d.DomainsRegex...)47}48uniqueDomains := sliceutil.Dedupe(domains)49uniqueDomainRegex := sliceutil.Dedupe(domainRegex)50return uniqueDomains, uniqueDomainRegex51}5253func (d *Dynamic) UnmarshalJSON(data []byte) error {54if d == nil {55return errkit.New("cannot unmarshal into nil Dynamic struct")56}5758// Use an alias type (auxiliary) to avoid a recursive call in this method.59type Alias Dynamic6061// If d.Secret was nil, json.Unmarshal will allocate a new Secret object62// and populate it from the top level JSON fields.63if err := json.Unmarshal(data, (*Alias)(d)); err != nil {64return err65}6667return nil68}6970// Validate validates the dynamic secret71func (d *Dynamic) Validate() error {72d.fetched = &atomic.Bool{}73d.fetching = &atomic.Bool{}74if d.TemplatePath == "" {75return errkit.New(" template-path is required for dynamic secret")76}77if len(d.Variables) == 0 {78return errkit.New("variables are required for dynamic secret")79}8081if d.Secret != nil {82d.skipCookieParse = true // skip cookie parsing in dynamic secrets during validation83if err := d.Secret.Validate(); err != nil {84return err85}86}87for _, secret := range d.Secrets {88secret.skipCookieParse = true89if err := secret.Validate(); err != nil {90return err91}92}93return nil94}9596// SetLazyFetchCallback sets the lazy fetch callback for the dynamic secret97func (d *Dynamic) SetLazyFetchCallback(callback LazyFetchSecret) {98d.fetchCallback = func(d *Dynamic) error {99err := callback(d)100if err != nil {101return err102}103if len(d.Extracted) == 0 {104return fmt.Errorf("no extracted values found for dynamic secret")105}106107if d.Secret != nil {108if err := d.applyValuesToSecret(d.Secret); err != nil {109return err110}111}112113for _, secret := range d.Secrets {114if err := d.applyValuesToSecret(secret); err != nil {115return err116}117}118return nil119}120}121122func (d *Dynamic) applyValuesToSecret(secret *Secret) error {123// evaluate headers124for i, header := range secret.Headers {125if strings.Contains(header.Value, "{{") {126header.Value = replacer.Replace(header.Value, d.Extracted)127}128if strings.Contains(header.Key, "{{") {129header.Key = replacer.Replace(header.Key, d.Extracted)130}131secret.Headers[i] = header132}133134// evaluate cookies135for i, cookie := range secret.Cookies {136if strings.Contains(cookie.Value, "{{") {137cookie.Value = replacer.Replace(cookie.Value, d.Extracted)138}139if strings.Contains(cookie.Key, "{{") {140cookie.Key = replacer.Replace(cookie.Key, d.Extracted)141}142if strings.Contains(cookie.Raw, "{{") {143cookie.Raw = replacer.Replace(cookie.Raw, d.Extracted)144}145secret.Cookies[i] = cookie146}147148// evaluate query params149for i, query := range secret.Params {150if strings.Contains(query.Value, "{{") {151query.Value = replacer.Replace(query.Value, d.Extracted)152}153if strings.Contains(query.Key, "{{") {154query.Key = replacer.Replace(query.Key, d.Extracted)155}156secret.Params[i] = query157}158159// check username, password and token160if strings.Contains(secret.Username, "{{") {161secret.Username = replacer.Replace(secret.Username, d.Extracted)162}163if strings.Contains(secret.Password, "{{") {164secret.Password = replacer.Replace(secret.Password, d.Extracted)165}166if strings.Contains(secret.Token, "{{") {167secret.Token = replacer.Replace(secret.Token, d.Extracted)168}169170// now attempt to parse the cookies171secret.skipCookieParse = false172for i, cookie := range secret.Cookies {173if cookie.Raw != "" {174if err := cookie.Parse(); err != nil {175return fmt.Errorf("[%s] invalid raw cookie in cookiesAuth: %s", d.TemplatePath, err)176}177secret.Cookies[i] = cookie178}179}180return nil181}182183// GetStrategy returns the auth strategies for the dynamic secret184func (d *Dynamic) GetStrategies() []AuthStrategy {185if d.fetched.Load() {186if d.error != nil {187return nil188}189} else {190// Try to fetch if not already fetched191_ = d.Fetch(true)192}193194if d.error != nil {195return nil196}197var strategies []AuthStrategy198if d.Secret != nil {199strategies = append(strategies, d.GetStrategy())200}201for _, secret := range d.Secrets {202strategies = append(strategies, secret.GetStrategy())203}204return strategies205}206207// Fetch fetches the dynamic secret208// if isFatal is true, it will stop the execution if the secret could not be fetched209func (d *Dynamic) Fetch(isFatal bool) error {210if d.fetched.Load() {211return d.error212}213214// Try to set fetching flag atomically215if !d.fetching.CompareAndSwap(false, true) {216// Already fetching, return current error217return d.error218}219220// We're the only one fetching, call the callback221d.error = d.fetchCallback(d)222223// Mark as fetched and clear fetching flag224d.fetched.Store(true)225d.fetching.Store(false)226227if d.error != nil && isFatal {228gologger.Fatal().Msgf("Could not fetch dynamic secret: %s\n", d.error)229}230return d.error231}232233// Error returns the error if any234func (d *Dynamic) Error() error {235return d.error236}237238239