Path: blob/dev/pkg/fuzz/analyzers/time/analyzer.go
2070 views
package time12import (3"fmt"4"io"5"net/http/httptrace"6"strconv"7"strings"8"time"910"github.com/pkg/errors"11"github.com/projectdiscovery/gologger"12"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/analyzers"13"github.com/projectdiscovery/retryablehttp-go"14)1516// Analyzer is a time delay analyzer for the fuzzer17type Analyzer struct {18}1920const (21DefaultSleepDuration = int(7)22DefaultRequestsLimit = int(4)23DefaultTimeCorrelationErrorRange = float64(0.15)24DefaultTimeSlopeErrorRange = float64(0.30)25DefaultLowSleepTimeSeconds = float64(3)2627defaultSleepTimeDuration = 7 * time.Second28)2930var _ analyzers.Analyzer = &Analyzer{}3132func init() {33analyzers.RegisterAnalyzer("time_delay", &Analyzer{})34}3536// Name is the name of the analyzer37func (a *Analyzer) Name() string {38return "time_delay"39}4041// ApplyInitialTransformation applies the transformation to the initial payload.42//43// It supports the below payloads -44// - [SLEEPTIME] => sleep_duration45// - [INFERENCE] => Inference payload for time delay analyzer46//47// It also applies the payload transformations to the payload48// which includes [RANDNUM] and [RANDSTR]49func (a *Analyzer) ApplyInitialTransformation(data string, params map[string]interface{}) string {50duration := DefaultSleepDuration51if len(params) > 0 {52if v, ok := params["sleep_duration"]; ok {53duration, ok = v.(int)54if !ok {55duration = DefaultSleepDuration56gologger.Warning().Msgf("Invalid sleep_duration parameter type, using default value: %d", duration)57}58}59}60data = strings.ReplaceAll(data, "[SLEEPTIME]", strconv.Itoa(duration))61data = analyzers.ApplyPayloadTransformations(data)6263// Also support [INFERENCE] for the time delay analyzer64if strings.Contains(data, "[INFERENCE]") {65randInt := analyzers.GetRandomInteger()66data = strings.ReplaceAll(data, "[INFERENCE]", fmt.Sprintf("%d=%d", randInt, randInt))67}68return data69}7071func (a *Analyzer) parseAnalyzerParameters(params map[string]interface{}) (int, int, float64, float64, error) {72requestsLimit := DefaultRequestsLimit73sleepDuration := DefaultSleepDuration74timeCorrelationErrorRange := DefaultTimeCorrelationErrorRange75timeSlopeErrorRange := DefaultTimeSlopeErrorRange7677if len(params) == 0 {78return requestsLimit, sleepDuration, timeCorrelationErrorRange, timeSlopeErrorRange, nil79}80var ok bool81for k, v := range params {82switch k {83case "sleep_duration":84sleepDuration, ok = v.(int)85case "requests_limit":86requestsLimit, ok = v.(int)87case "time_correlation_error_range":88timeCorrelationErrorRange, ok = v.(float64)89case "time_slope_error_range":90timeSlopeErrorRange, ok = v.(float64)91}92if !ok {93return 0, 0, 0, 0, errors.Errorf("invalid parameter type for %s", k)94}95}96return requestsLimit, sleepDuration, timeCorrelationErrorRange, timeSlopeErrorRange, nil97}9899// Analyze is the main function for the analyzer100func (a *Analyzer) Analyze(options *analyzers.Options) (bool, string, error) {101if options.ResponseTimeDelay < defaultSleepTimeDuration {102return false, "", nil103}104105// Parse parameters for this analyzer if any or use default values106requestsLimit, sleepDuration, timeCorrelationErrorRange, timeSlopeErrorRange, err :=107a.parseAnalyzerParameters(options.AnalyzerParameters)108if err != nil {109return false, "", err110}111112reqSender := func(delay int) (float64, error) {113gr := options.FuzzGenerated114replaced := strings.ReplaceAll(gr.OriginalPayload, "[SLEEPTIME]", strconv.Itoa(delay))115replaced = a.ApplyInitialTransformation(replaced, options.AnalyzerParameters)116117if err := gr.Component.SetValue(gr.Key, replaced); err != nil {118return 0, errors.Wrap(err, "could not set value in component")119}120121rebuilt, err := gr.Component.Rebuild()122if err != nil {123return 0, errors.Wrap(err, "could not rebuild request")124}125gologger.Verbose().Msgf("[%s] Sending request with %d delay for: %s", a.Name(), delay, rebuilt.String())126127timeTaken, err := doHTTPRequestWithTimeTracing(rebuilt, options.HttpClient)128if err != nil {129return 0, errors.Wrap(err, "could not do request with time tracing")130}131return timeTaken, nil132}133134// Check the baseline delay of the request by doing two requests135baselineDelay, err := getBaselineDelay(reqSender)136if err != nil {137return false, "", err138}139140matched, matchReason, err := checkTimingDependency(141requestsLimit,142sleepDuration,143timeCorrelationErrorRange,144timeSlopeErrorRange,145baselineDelay,146reqSender,147)148if err != nil {149return false, "", err150}151if matched {152return true, matchReason, nil153}154return false, "", nil155}156157func getBaselineDelay(reqSender timeDelayRequestSender) (float64, error) {158var delays []float64159// Use zero or a very small delay to measure baseline160for i := 0; i < 3; i++ {161delay, err := reqSender(0)162if err != nil {163return 0, errors.Wrap(err, "could not get baseline delay")164}165delays = append(delays, delay)166}167168var total float64169for _, d := range delays {170total += d171}172avg := total / float64(len(delays))173return avg, nil174}175176// doHTTPRequestWithTimeTracing does a http request with time tracing177func doHTTPRequestWithTimeTracing(req *retryablehttp.Request, httpclient *retryablehttp.Client) (float64, error) {178var serverTime time.Duration179var wroteRequest time.Time180181trace := &httptrace.ClientTrace{182WroteHeaders: func() {183wroteRequest = time.Now()184},185GotFirstResponseByte: func() {186serverTime = time.Since(wroteRequest)187},188}189req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))190resp, err := httpclient.Do(req)191if err != nil {192return 0, errors.Wrap(err, "could not do request")193}194195_, err = io.ReadAll(resp.Body)196if err != nil {197return 0, errors.Wrap(err, "could not read response body")198}199return serverTime.Seconds(), nil200}201202203