Ice3man 5f0b7eb19b
feat: added initial live DAST server implementation (#5772)
* feat: added initial live DAST server implementation

* feat: more logging + misc additions

* feat: auth file support enhancements for more complex scenarios + misc

* feat: added io.Reader support to input providers for http

* feat: added stats db to fuzzing + use sdk for dast server + misc

* feat: more additions and enhancements

* misc changes to live server

* misc

* use utils pprof server

* feat: added simpler stats tracking system

* feat: fixed analyzer timeout issue + missing case fix

* misc changes fix

* feat: changed the logics a bit + misc changes and additions

* feat: re-added slope checks + misc

* feat: added baseline measurements for time based checks

* chore(server): fix typos

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix(templates): potential DOM XSS

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix(authx): potential NIL deref

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* feat: misc review changes

* removed debug logging

* feat: remove existing cookies only

* feat: lint fixes

* misc

* misc text update

* request endpoint update

* feat: added tracking for status code, waf-detection & grouped errors (#6028)

* feat: added tracking for status code, waf-detection & grouped errors

* lint error fixes

* feat: review changes + moving to package + misc

---------

Co-authored-by: sandeep <8293321+ehsandeep@users.noreply.github.com>

* fix var dump (#5921)

* fix var dump

* fix dump test

* Added filename length restriction for debug mode (-srd flag) (#5931)

Co-authored-by: Andrey Matveenko <an.matveenko@vkteam.ru>

* more updates

* Update pkg/output/stats/waf/waf.go

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: sandeep <8293321+ehsandeep@users.noreply.github.com>
Co-authored-by: Dwi Siswanto <25837540+dwisiswant0@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Dogan Can Bakir <65292895+dogancanbakir@users.noreply.github.com>
Co-authored-by: 9flowers <51699499+Lercas@users.noreply.github.com>
Co-authored-by: Andrey Matveenko <an.matveenko@vkteam.ru>
Co-authored-by: Sandeep Singh <sandeep@projectdiscovery.io>
2025-02-13 18:46:28 +05:30

217 lines
6.2 KiB
Go

package authx
import (
"fmt"
"strings"
"sync"
"github.com/projectdiscovery/gologger"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/replacer"
"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
errorutil "github.com/projectdiscovery/utils/errors"
sliceutil "github.com/projectdiscovery/utils/slice"
)
type LazyFetchSecret func(d *Dynamic) error
var (
_ json.Unmarshaler = &Dynamic{}
)
// Dynamic is a struct for dynamic secret or credential
// these are high level secrets that take action to generate the actual secret
// 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
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
Extracted map[string]interface{} `json:"-" yaml:"-"` // extracted values from the dynamic secret
fetchCallback LazyFetchSecret `json:"-" yaml:"-"`
m *sync.Mutex `json:"-" yaml:"-"` // mutex for lazy fetch
fetched bool `json:"-" yaml:"-"` // flag to check if the secret has been fetched
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
}
var s Secret
if err := json.Unmarshal(data, &s); err != nil {
return err
}
d.Secret = &s
return nil
}
// Validate validates the dynamic secret
func (d *Dynamic) Validate() error {
d.m = &sync.Mutex{}
if d.TemplatePath == "" {
return errorutil.New(" template-path is required for dynamic secret")
}
if len(d.Variables) == 0 {
return errorutil.New("variables are required for dynamic secret")
}
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
}
// SetLazyFetchCallback sets the lazy fetch callback for the dynamic secret
func (d *Dynamic) SetLazyFetchCallback(callback LazyFetchSecret) {
d.fetchCallback = func(d *Dynamic) error {
err := callback(d)
d.fetched = true
if err != nil {
d.error = err
return err
}
if len(d.Extracted) == 0 {
return fmt.Errorf("no extracted values found for dynamic secret")
}
if d.Secret != nil {
if err := d.applyValuesToSecret(d.Secret); err != nil {
return err
}
}
for _, secret := range d.Secrets {
if err := d.applyValuesToSecret(secret); err != nil {
return err
}
}
return nil
}
}
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
}
// evaluate cookies
for i, cookie := range secret.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)
}
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
}
// 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
}
// 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
}
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
// if isFatal is true, it will stop the execution if the secret could not be fetched
func (d *Dynamic) Fetch(isFatal bool) error {
d.m.Lock()
defer d.m.Unlock()
if d.fetched {
return nil
}
d.error = d.fetchCallback(d)
if d.error != nil && isFatal {
gologger.Fatal().Msgf("Could not fetch dynamic secret: %s\n", d.error)
}
return d.error
}
// Error returns the error if any
func (d *Dynamic) Error() error {
return d.error
}