Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/fuzz/analyzers/time/analyzer.go
2070 views
1
package time
2
3
import (
4
"fmt"
5
"io"
6
"net/http/httptrace"
7
"strconv"
8
"strings"
9
"time"
10
11
"github.com/pkg/errors"
12
"github.com/projectdiscovery/gologger"
13
"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/analyzers"
14
"github.com/projectdiscovery/retryablehttp-go"
15
)
16
17
// Analyzer is a time delay analyzer for the fuzzer
18
type Analyzer struct {
19
}
20
21
const (
22
DefaultSleepDuration = int(7)
23
DefaultRequestsLimit = int(4)
24
DefaultTimeCorrelationErrorRange = float64(0.15)
25
DefaultTimeSlopeErrorRange = float64(0.30)
26
DefaultLowSleepTimeSeconds = float64(3)
27
28
defaultSleepTimeDuration = 7 * time.Second
29
)
30
31
var _ analyzers.Analyzer = &Analyzer{}
32
33
func init() {
34
analyzers.RegisterAnalyzer("time_delay", &Analyzer{})
35
}
36
37
// Name is the name of the analyzer
38
func (a *Analyzer) Name() string {
39
return "time_delay"
40
}
41
42
// ApplyInitialTransformation applies the transformation to the initial payload.
43
//
44
// It supports the below payloads -
45
// - [SLEEPTIME] => sleep_duration
46
// - [INFERENCE] => Inference payload for time delay analyzer
47
//
48
// It also applies the payload transformations to the payload
49
// which includes [RANDNUM] and [RANDSTR]
50
func (a *Analyzer) ApplyInitialTransformation(data string, params map[string]interface{}) string {
51
duration := DefaultSleepDuration
52
if len(params) > 0 {
53
if v, ok := params["sleep_duration"]; ok {
54
duration, ok = v.(int)
55
if !ok {
56
duration = DefaultSleepDuration
57
gologger.Warning().Msgf("Invalid sleep_duration parameter type, using default value: %d", duration)
58
}
59
}
60
}
61
data = strings.ReplaceAll(data, "[SLEEPTIME]", strconv.Itoa(duration))
62
data = analyzers.ApplyPayloadTransformations(data)
63
64
// Also support [INFERENCE] for the time delay analyzer
65
if strings.Contains(data, "[INFERENCE]") {
66
randInt := analyzers.GetRandomInteger()
67
data = strings.ReplaceAll(data, "[INFERENCE]", fmt.Sprintf("%d=%d", randInt, randInt))
68
}
69
return data
70
}
71
72
func (a *Analyzer) parseAnalyzerParameters(params map[string]interface{}) (int, int, float64, float64, error) {
73
requestsLimit := DefaultRequestsLimit
74
sleepDuration := DefaultSleepDuration
75
timeCorrelationErrorRange := DefaultTimeCorrelationErrorRange
76
timeSlopeErrorRange := DefaultTimeSlopeErrorRange
77
78
if len(params) == 0 {
79
return requestsLimit, sleepDuration, timeCorrelationErrorRange, timeSlopeErrorRange, nil
80
}
81
var ok bool
82
for k, v := range params {
83
switch k {
84
case "sleep_duration":
85
sleepDuration, ok = v.(int)
86
case "requests_limit":
87
requestsLimit, ok = v.(int)
88
case "time_correlation_error_range":
89
timeCorrelationErrorRange, ok = v.(float64)
90
case "time_slope_error_range":
91
timeSlopeErrorRange, ok = v.(float64)
92
}
93
if !ok {
94
return 0, 0, 0, 0, errors.Errorf("invalid parameter type for %s", k)
95
}
96
}
97
return requestsLimit, sleepDuration, timeCorrelationErrorRange, timeSlopeErrorRange, nil
98
}
99
100
// Analyze is the main function for the analyzer
101
func (a *Analyzer) Analyze(options *analyzers.Options) (bool, string, error) {
102
if options.ResponseTimeDelay < defaultSleepTimeDuration {
103
return false, "", nil
104
}
105
106
// Parse parameters for this analyzer if any or use default values
107
requestsLimit, sleepDuration, timeCorrelationErrorRange, timeSlopeErrorRange, err :=
108
a.parseAnalyzerParameters(options.AnalyzerParameters)
109
if err != nil {
110
return false, "", err
111
}
112
113
reqSender := func(delay int) (float64, error) {
114
gr := options.FuzzGenerated
115
replaced := strings.ReplaceAll(gr.OriginalPayload, "[SLEEPTIME]", strconv.Itoa(delay))
116
replaced = a.ApplyInitialTransformation(replaced, options.AnalyzerParameters)
117
118
if err := gr.Component.SetValue(gr.Key, replaced); err != nil {
119
return 0, errors.Wrap(err, "could not set value in component")
120
}
121
122
rebuilt, err := gr.Component.Rebuild()
123
if err != nil {
124
return 0, errors.Wrap(err, "could not rebuild request")
125
}
126
gologger.Verbose().Msgf("[%s] Sending request with %d delay for: %s", a.Name(), delay, rebuilt.String())
127
128
timeTaken, err := doHTTPRequestWithTimeTracing(rebuilt, options.HttpClient)
129
if err != nil {
130
return 0, errors.Wrap(err, "could not do request with time tracing")
131
}
132
return timeTaken, nil
133
}
134
135
// Check the baseline delay of the request by doing two requests
136
baselineDelay, err := getBaselineDelay(reqSender)
137
if err != nil {
138
return false, "", err
139
}
140
141
matched, matchReason, err := checkTimingDependency(
142
requestsLimit,
143
sleepDuration,
144
timeCorrelationErrorRange,
145
timeSlopeErrorRange,
146
baselineDelay,
147
reqSender,
148
)
149
if err != nil {
150
return false, "", err
151
}
152
if matched {
153
return true, matchReason, nil
154
}
155
return false, "", nil
156
}
157
158
func getBaselineDelay(reqSender timeDelayRequestSender) (float64, error) {
159
var delays []float64
160
// Use zero or a very small delay to measure baseline
161
for i := 0; i < 3; i++ {
162
delay, err := reqSender(0)
163
if err != nil {
164
return 0, errors.Wrap(err, "could not get baseline delay")
165
}
166
delays = append(delays, delay)
167
}
168
169
var total float64
170
for _, d := range delays {
171
total += d
172
}
173
avg := total / float64(len(delays))
174
return avg, nil
175
}
176
177
// doHTTPRequestWithTimeTracing does a http request with time tracing
178
func doHTTPRequestWithTimeTracing(req *retryablehttp.Request, httpclient *retryablehttp.Client) (float64, error) {
179
var serverTime time.Duration
180
var wroteRequest time.Time
181
182
trace := &httptrace.ClientTrace{
183
WroteHeaders: func() {
184
wroteRequest = time.Now()
185
},
186
GotFirstResponseByte: func() {
187
serverTime = time.Since(wroteRequest)
188
},
189
}
190
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
191
resp, err := httpclient.Do(req)
192
if err != nil {
193
return 0, errors.Wrap(err, "could not do request")
194
}
195
196
_, err = io.ReadAll(resp.Body)
197
if err != nil {
198
return 0, errors.Wrap(err, "could not read response body")
199
}
200
return serverTime.Seconds(), nil
201
}
202
203