Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/authprovider/authx/dynamic.go
2843 views
1
package authx
2
3
import (
4
"fmt"
5
"strings"
6
"sync/atomic"
7
8
"github.com/projectdiscovery/gologger"
9
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/replacer"
10
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
11
"github.com/projectdiscovery/utils/errkit"
12
sliceutil "github.com/projectdiscovery/utils/slice"
13
)
14
15
type LazyFetchSecret func(d *Dynamic) error
16
17
var (
18
_ json.Unmarshaler = &Dynamic{}
19
)
20
21
// Dynamic is a struct for dynamic secret or credential
22
// these are high level secrets that take action to generate the actual secret
23
// ex: username and password are dynamic secrets, the actual secret is the token obtained
24
// after authenticating with the username and password
25
type Dynamic struct {
26
*Secret `yaml:",inline"` // this is a static secret that will be generated after the dynamic secret is resolved
27
Secrets []*Secret `yaml:"secrets"`
28
TemplatePath string `json:"template" yaml:"template"`
29
Variables []KV `json:"variables" yaml:"variables"`
30
Input string `json:"input" yaml:"input"` // (optional) target for the dynamic secret
31
Extracted map[string]interface{} `json:"-" yaml:"-"` // extracted values from the dynamic secret
32
fetchCallback LazyFetchSecret `json:"-" yaml:"-"`
33
fetched *atomic.Bool `json:"-" yaml:"-"` // atomic flag to check if the secret has been fetched
34
fetching *atomic.Bool `json:"-" yaml:"-"` // atomic flag to prevent recursive fetch calls
35
error error `json:"-" yaml:"-"` // error if any
36
}
37
38
func (d *Dynamic) GetDomainAndDomainRegex() ([]string, []string) {
39
var domains []string
40
var domainRegex []string
41
for _, secret := range d.Secrets {
42
domains = append(domains, secret.Domains...)
43
domainRegex = append(domainRegex, secret.DomainsRegex...)
44
}
45
if d.Secret != nil {
46
domains = append(domains, d.Domains...)
47
domainRegex = append(domainRegex, d.DomainsRegex...)
48
}
49
uniqueDomains := sliceutil.Dedupe(domains)
50
uniqueDomainRegex := sliceutil.Dedupe(domainRegex)
51
return uniqueDomains, uniqueDomainRegex
52
}
53
54
func (d *Dynamic) UnmarshalJSON(data []byte) error {
55
if d == nil {
56
return errkit.New("cannot unmarshal into nil Dynamic struct")
57
}
58
59
// Use an alias type (auxiliary) to avoid a recursive call in this method.
60
type Alias Dynamic
61
62
// If d.Secret was nil, json.Unmarshal will allocate a new Secret object
63
// and populate it from the top level JSON fields.
64
if err := json.Unmarshal(data, (*Alias)(d)); err != nil {
65
return err
66
}
67
68
return nil
69
}
70
71
// Validate validates the dynamic secret
72
func (d *Dynamic) Validate() error {
73
d.fetched = &atomic.Bool{}
74
d.fetching = &atomic.Bool{}
75
if d.TemplatePath == "" {
76
return errkit.New(" template-path is required for dynamic secret")
77
}
78
if len(d.Variables) == 0 {
79
return errkit.New("variables are required for dynamic secret")
80
}
81
82
if d.Secret != nil {
83
d.skipCookieParse = true // skip cookie parsing in dynamic secrets during validation
84
if err := d.Secret.Validate(); err != nil {
85
return err
86
}
87
}
88
for _, secret := range d.Secrets {
89
secret.skipCookieParse = true
90
if err := secret.Validate(); err != nil {
91
return err
92
}
93
}
94
return nil
95
}
96
97
// SetLazyFetchCallback sets the lazy fetch callback for the dynamic secret
98
func (d *Dynamic) SetLazyFetchCallback(callback LazyFetchSecret) {
99
d.fetchCallback = func(d *Dynamic) error {
100
err := callback(d)
101
if err != nil {
102
return err
103
}
104
if len(d.Extracted) == 0 {
105
return fmt.Errorf("no extracted values found for dynamic secret")
106
}
107
108
if d.Secret != nil {
109
if err := d.applyValuesToSecret(d.Secret); err != nil {
110
return err
111
}
112
}
113
114
for _, secret := range d.Secrets {
115
if err := d.applyValuesToSecret(secret); err != nil {
116
return err
117
}
118
}
119
return nil
120
}
121
}
122
123
func (d *Dynamic) applyValuesToSecret(secret *Secret) error {
124
// evaluate headers
125
for i, header := range secret.Headers {
126
if strings.Contains(header.Value, "{{") {
127
header.Value = replacer.Replace(header.Value, d.Extracted)
128
}
129
if strings.Contains(header.Key, "{{") {
130
header.Key = replacer.Replace(header.Key, d.Extracted)
131
}
132
secret.Headers[i] = header
133
}
134
135
// evaluate cookies
136
for i, cookie := range secret.Cookies {
137
if strings.Contains(cookie.Value, "{{") {
138
cookie.Value = replacer.Replace(cookie.Value, d.Extracted)
139
}
140
if strings.Contains(cookie.Key, "{{") {
141
cookie.Key = replacer.Replace(cookie.Key, d.Extracted)
142
}
143
if strings.Contains(cookie.Raw, "{{") {
144
cookie.Raw = replacer.Replace(cookie.Raw, d.Extracted)
145
}
146
secret.Cookies[i] = cookie
147
}
148
149
// evaluate query params
150
for i, query := range secret.Params {
151
if strings.Contains(query.Value, "{{") {
152
query.Value = replacer.Replace(query.Value, d.Extracted)
153
}
154
if strings.Contains(query.Key, "{{") {
155
query.Key = replacer.Replace(query.Key, d.Extracted)
156
}
157
secret.Params[i] = query
158
}
159
160
// check username, password and token
161
if strings.Contains(secret.Username, "{{") {
162
secret.Username = replacer.Replace(secret.Username, d.Extracted)
163
}
164
if strings.Contains(secret.Password, "{{") {
165
secret.Password = replacer.Replace(secret.Password, d.Extracted)
166
}
167
if strings.Contains(secret.Token, "{{") {
168
secret.Token = replacer.Replace(secret.Token, d.Extracted)
169
}
170
171
// now attempt to parse the cookies
172
secret.skipCookieParse = false
173
for i, cookie := range secret.Cookies {
174
if cookie.Raw != "" {
175
if err := cookie.Parse(); err != nil {
176
return fmt.Errorf("[%s] invalid raw cookie in cookiesAuth: %s", d.TemplatePath, err)
177
}
178
secret.Cookies[i] = cookie
179
}
180
}
181
return nil
182
}
183
184
// GetStrategy returns the auth strategies for the dynamic secret
185
func (d *Dynamic) GetStrategies() []AuthStrategy {
186
if d.fetched.Load() {
187
if d.error != nil {
188
return nil
189
}
190
} else {
191
// Try to fetch if not already fetched
192
_ = d.Fetch(true)
193
}
194
195
if d.error != nil {
196
return nil
197
}
198
var strategies []AuthStrategy
199
if d.Secret != nil {
200
strategies = append(strategies, d.GetStrategy())
201
}
202
for _, secret := range d.Secrets {
203
strategies = append(strategies, secret.GetStrategy())
204
}
205
return strategies
206
}
207
208
// Fetch fetches the dynamic secret
209
// if isFatal is true, it will stop the execution if the secret could not be fetched
210
func (d *Dynamic) Fetch(isFatal bool) error {
211
if d.fetched.Load() {
212
return d.error
213
}
214
215
// Try to set fetching flag atomically
216
if !d.fetching.CompareAndSwap(false, true) {
217
// Already fetching, return current error
218
return d.error
219
}
220
221
// We're the only one fetching, call the callback
222
d.error = d.fetchCallback(d)
223
224
// Mark as fetched and clear fetching flag
225
d.fetched.Store(true)
226
d.fetching.Store(false)
227
228
if d.error != nil && isFatal {
229
gologger.Fatal().Msgf("Could not fetch dynamic secret: %s\n", d.error)
230
}
231
return d.error
232
}
233
234
// Error returns the error if any
235
func (d *Dynamic) Error() error {
236
return d.error
237
}
238
239