-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
feat: Added time based delay analyzer to fuzzing implementation #5781
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
75dd655
feat: added fuzzing output enhancements
Ice3man543 4a8f66f
changes as requested
Ice3man543 03a3113
misc
Ice3man543 927525b
feat: added dfp flag to display fuzz points + misc additions
Ice3man543 9592ec2
feat: added support for fuzzing nested path segments
Ice3man543 ddf2955
feat: added parts to fuzzing requests
Ice3man543 63c5fb3
feat: added tracking for parameter occurence frequency in fuzzing
Ice3man543 ef6557e
added cli flag for fuzz frequency
Ice3man543 a1af2d3
Merge branch 'dev' of https://github.com/projectdiscovery/nuclei into…
Ice3man543 6e2ad5c
fixed broken tests
Ice3man543 7a62bba
fixed path based sqli integration test
Ice3man543 12d116b
feat: added configurable fuzzing aggression level for payloads
Ice3man543 57bede4
fixed failing test
Ice3man543 76b5131
feat: added analyzers implementation for fuzzing
Ice3man543 cef7a5c
Merge branch 'dev' of https://github.com/projectdiscovery/nuclei into…
Ice3man543 aeb4cad
feat: misc changes to analyzer
Ice3man543 f6926b5
feat: misc additions of units + tests fix
Ice3man543 311198f
misc changes to implementation
Ice3man543 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package analyzers | ||
|
||
import ( | ||
"math/rand" | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
"github.com/projectdiscovery/nuclei/v3/pkg/fuzz" | ||
"github.com/projectdiscovery/retryablehttp-go" | ||
) | ||
|
||
// Analyzer is an interface for all the analyzers | ||
// that can be used for the fuzzer | ||
type Analyzer interface { | ||
// Name returns the name of the analyzer | ||
Name() string | ||
// ApplyTransformation applies the transformation to the initial payload. | ||
ApplyInitialTransformation(data string, params map[string]interface{}) string | ||
// Analyze is the main function for the analyzer | ||
Analyze(options *Options) (bool, string, error) | ||
} | ||
|
||
// AnalyzerTemplate is the template for the analyzer | ||
type AnalyzerTemplate struct { | ||
// description: | | ||
// Name is the name of the analyzer to use | ||
// values: | ||
// - time_delay | ||
Name string `json:"name" yaml:"name"` | ||
// description: | | ||
// Parameters is the parameters for the analyzer | ||
// | ||
// Parameters are different for each analyzer. For example, you can customize | ||
// time_delay analyzer with sleep_duration, time_slope_error_range, etc. Refer | ||
// to the docs for each analyzer to get an idea about parameters. | ||
Parameters map[string]interface{} `json:"parameters" yaml:"parameters"` | ||
} | ||
|
||
var ( | ||
analyzers map[string]Analyzer | ||
) | ||
|
||
// RegisterAnalyzer registers a new analyzer | ||
func RegisterAnalyzer(name string, analyzer Analyzer) { | ||
analyzers[name] = analyzer | ||
} | ||
|
||
// GetAnalyzer returns the analyzer for a given name | ||
func GetAnalyzer(name string) Analyzer { | ||
return analyzers[name] | ||
} | ||
|
||
func init() { | ||
analyzers = make(map[string]Analyzer) | ||
} | ||
|
||
// Options contains the options for the analyzer | ||
type Options struct { | ||
FuzzGenerated fuzz.GeneratedRequest | ||
HttpClient *retryablehttp.Client | ||
ResponseTimeDelay time.Duration | ||
AnalyzerParameters map[string]interface{} | ||
} | ||
|
||
var ( | ||
random = rand.New(rand.NewSource(time.Now().UnixNano())) | ||
) | ||
|
||
// ApplyPayloadTransformations applies the payload transformations to the payload | ||
// It supports the below payloads - | ||
// - [RANDNUM] => random number between 1000 and 9999 | ||
// - [RANDSTR] => random string of 4 characters | ||
func ApplyPayloadTransformations(value string) string { | ||
randomInt := GetRandomInteger() | ||
randomStr := randStringBytesMask(4) | ||
|
||
value = strings.ReplaceAll(value, "[RANDNUM]", strconv.Itoa(randomInt)) | ||
value = strings.ReplaceAll(value, "[RANDSTR]", randomStr) | ||
return value | ||
} | ||
|
||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" | ||
const ( | ||
letterIdxBits = 6 // 6 bits to represent a letter index | ||
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits | ||
) | ||
|
||
func randStringBytesMask(n int) string { | ||
b := make([]byte, n) | ||
for i := 0; i < n; { | ||
if idx := int(random.Int63() & letterIdxMask); idx < len(letterBytes) { | ||
b[i] = letterBytes[idx] | ||
i++ | ||
} | ||
} | ||
return string(b) | ||
} | ||
|
||
// GetRandomInteger returns a random integer between 1000 and 9999 | ||
func GetRandomInteger() int { | ||
return random.Intn(9000) + 1000 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
package time | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"net/http/httptrace" | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
"github.com/pkg/errors" | ||
"github.com/projectdiscovery/gologger" | ||
"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/analyzers" | ||
"github.com/projectdiscovery/retryablehttp-go" | ||
) | ||
|
||
// Analyzer is a time delay analyzer for the fuzzer | ||
type Analyzer struct{} | ||
|
||
const ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can add comments to explain why these are the default values --it's not obvious at first glance |
||
DefaultSleepDuration = int(5) | ||
DefaultRequestsLimit = int(4) | ||
DefaultTimeCorrelationErrorRange = float64(0.15) | ||
DefaultTimeSlopeErrorRange = float64(0.30) | ||
|
||
defaultSleepTimeDuration = 5 * time.Second | ||
) | ||
|
||
var _ analyzers.Analyzer = &Analyzer{} | ||
|
||
func init() { | ||
analyzers.RegisterAnalyzer("time_delay", &Analyzer{}) | ||
} | ||
|
||
// Name is the name of the analyzer | ||
func (a *Analyzer) Name() string { | ||
return "time_delay" | ||
} | ||
|
||
// ApplyInitialTransformation applies the transformation to the initial payload. | ||
// | ||
// It supports the below payloads - | ||
// - [SLEEPTIME] => sleep_duration | ||
// - [INFERENCE] => Inference payload for time delay analyzer | ||
// | ||
// It also applies the payload transformations to the payload | ||
// which includes [RANDNUM] and [RANDSTR] | ||
func (a *Analyzer) ApplyInitialTransformation(data string, params map[string]interface{}) string { | ||
duration := DefaultSleepDuration | ||
if len(params) > 0 { | ||
if v, ok := params["sleep_duration"]; ok { | ||
duration, ok = v.(int) | ||
if !ok { | ||
duration = DefaultSleepDuration | ||
gologger.Warning().Msgf("Invalid sleep_duration parameter type, using default value: %d", duration) | ||
} | ||
} | ||
} | ||
data = strings.ReplaceAll(data, "[SLEEPTIME]", strconv.Itoa(duration)) | ||
data = analyzers.ApplyPayloadTransformations(data) | ||
|
||
// Also support [INFERENCE] for the time delay analyzer | ||
if strings.Contains(data, "[INFERENCE]") { | ||
randInt := analyzers.GetRandomInteger() | ||
data = strings.ReplaceAll(data, "[INFERENCE]", fmt.Sprintf("%d=%d", randInt, randInt)) | ||
} | ||
return data | ||
} | ||
|
||
func (a *Analyzer) parseAnalyzerParameters(params map[string]interface{}) (int, int, float64, float64, error) { | ||
requestsLimit := DefaultRequestsLimit | ||
sleepDuration := DefaultSleepDuration | ||
timeCorrelationErrorRange := DefaultTimeCorrelationErrorRange | ||
timeSlopeErrorRange := DefaultTimeSlopeErrorRange | ||
|
||
if len(params) == 0 { | ||
return requestsLimit, sleepDuration, timeCorrelationErrorRange, timeSlopeErrorRange, nil | ||
} | ||
var ok bool | ||
for k, v := range params { | ||
switch k { | ||
case "sleep_duration": | ||
sleepDuration, ok = v.(int) | ||
case "requests_limit": | ||
requestsLimit, ok = v.(int) | ||
case "time_correlation_error_range": | ||
timeCorrelationErrorRange, ok = v.(float64) | ||
case "time_slope_error_range": | ||
timeSlopeErrorRange, ok = v.(float64) | ||
} | ||
if !ok { | ||
return 0, 0, 0, 0, errors.Errorf("invalid parameter type for %s", k) | ||
} | ||
} | ||
return requestsLimit, sleepDuration, timeCorrelationErrorRange, timeSlopeErrorRange, nil | ||
} | ||
|
||
// Analyze is the main function for the analyzer | ||
func (a *Analyzer) Analyze(options *analyzers.Options) (bool, string, error) { | ||
if options.ResponseTimeDelay < defaultSleepTimeDuration { | ||
return false, "", nil | ||
} | ||
|
||
// Parse parameters for this analyzer if any or use default values | ||
requestsLimit, sleepDuration, timeCorrelationErrorRange, timeSlopeErrorRange, err := | ||
a.parseAnalyzerParameters(options.AnalyzerParameters) | ||
if err != nil { | ||
return false, "", err | ||
} | ||
|
||
reqSender := func(delay int) (float64, error) { | ||
gr := options.FuzzGenerated | ||
replaced := strings.ReplaceAll(gr.OriginalPayload, "[SLEEPTIME]", strconv.Itoa(delay)) | ||
replaced = a.ApplyInitialTransformation(replaced, options.AnalyzerParameters) | ||
|
||
if err := gr.Component.SetValue(gr.Key, replaced); err != nil { | ||
return 0, errors.Wrap(err, "could not set value in component") | ||
} | ||
|
||
rebuilt, err := gr.Component.Rebuild() | ||
if err != nil { | ||
return 0, errors.Wrap(err, "could not rebuild request") | ||
} | ||
gologger.Verbose().Msgf("[%s] Sending request with %d delay for: %s", a.Name(), delay, rebuilt.URL.String()) | ||
|
||
timeTaken, err := doHTTPRequestWithTimeTracing(rebuilt, options.HttpClient) | ||
if err != nil { | ||
return 0, errors.Wrap(err, "could not do request with time tracing") | ||
} | ||
return timeTaken, nil | ||
} | ||
matched, matchReason, err := checkTimingDependency( | ||
requestsLimit, | ||
sleepDuration, | ||
timeCorrelationErrorRange, | ||
timeSlopeErrorRange, | ||
reqSender, | ||
) | ||
if err != nil { | ||
return false, "", err | ||
} | ||
if matched { | ||
return true, matchReason, nil | ||
} | ||
return false, "", nil | ||
} | ||
|
||
// doHTTPRequestWithTimeTracing does a http request with time tracing | ||
func doHTTPRequestWithTimeTracing(req *retryablehttp.Request, httpclient *retryablehttp.Client) (float64, error) { | ||
var ttfb time.Duration | ||
var start time.Time | ||
|
||
trace := &httptrace.ClientTrace{ | ||
GotFirstResponseByte: func() { ttfb = time.Since(start) }, | ||
} | ||
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace)) | ||
start = time.Now() | ||
resp, err := httpclient.Do(req) | ||
if err != nil { | ||
return 0, errors.Wrap(err, "could not do request") | ||
} | ||
|
||
_, err = io.ReadAll(resp.Body) | ||
if err != nil { | ||
return 0, errors.Wrap(err, "could not read response body") | ||
} | ||
return ttfb.Seconds(), nil | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should separate this from the Analyzer, as the Analyzer is not a Transformer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, we can separate Analyzer to another file to keep the code more clean.