Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/fuzz/frequency/tracker.go
2070 views
1
package frequency
2
3
import (
4
"net"
5
"net/url"
6
"os"
7
"strings"
8
"sync"
9
"sync/atomic"
10
11
"github.com/bluele/gcache"
12
"github.com/projectdiscovery/gologger"
13
)
14
15
// Tracker implements a frequency tracker for a given input
16
// which is used to determine uninteresting input parameters
17
// which are not that interesting from fuzzing perspective for a template
18
// and target combination.
19
//
20
// This is used to reduce the number of requests made during fuzzing
21
// for parameters that are less likely to give results for a rule.
22
type Tracker struct {
23
frequencies gcache.Cache
24
paramOccurenceThreshold int
25
26
isDebug bool
27
}
28
29
const (
30
DefaultMaxTrackCount = 10000
31
DefaultParamOccurenceThreshold = 10
32
)
33
34
type cacheItem struct {
35
errors atomic.Int32
36
sync.Once
37
}
38
39
// New creates a new frequency tracker with a given maximum
40
// number of params to track in LRU fashion with a max error threshold
41
func New(maxTrackCount, paramOccurenceThreshold int) *Tracker {
42
gc := gcache.New(maxTrackCount).ARC().Build()
43
44
var isDebug bool
45
if os.Getenv("FREQ_DEBUG") != "" {
46
isDebug = true
47
}
48
return &Tracker{
49
isDebug: isDebug,
50
frequencies: gc,
51
paramOccurenceThreshold: paramOccurenceThreshold,
52
}
53
}
54
55
func (t *Tracker) Close() {
56
t.frequencies.Purge()
57
}
58
59
// MarkParameter marks a parameter as frequently occuring once.
60
//
61
// The logic requires a parameter to be marked as frequently occuring
62
// multiple times before it's considered as frequently occuring.
63
func (t *Tracker) MarkParameter(parameter, target, template string) {
64
normalizedTarget := normalizeTarget(target)
65
key := getFrequencyKey(parameter, normalizedTarget, template)
66
67
if t.isDebug {
68
gologger.Verbose().Msgf("[%s] Marking %s as found uninteresting", template, key)
69
}
70
71
existingCacheItem, err := t.frequencies.GetIFPresent(key)
72
if err != nil || existingCacheItem == nil {
73
newItem := &cacheItem{errors: atomic.Int32{}}
74
newItem.errors.Store(1)
75
_ = t.frequencies.Set(key, newItem)
76
return
77
}
78
existingCacheItemValue := existingCacheItem.(*cacheItem)
79
existingCacheItemValue.errors.Add(1)
80
81
_ = t.frequencies.Set(key, existingCacheItemValue)
82
}
83
84
// IsParameterFrequent checks if a parameter is frequently occuring
85
// in the input with no much results.
86
func (t *Tracker) IsParameterFrequent(parameter, target, template string) bool {
87
normalizedTarget := normalizeTarget(target)
88
key := getFrequencyKey(parameter, normalizedTarget, template)
89
90
if t.isDebug {
91
gologger.Verbose().Msgf("[%s] Checking if %s is frequently found uninteresting", template, key)
92
}
93
94
existingCacheItem, err := t.frequencies.GetIFPresent(key)
95
if err != nil {
96
return false
97
}
98
existingCacheItemValue := existingCacheItem.(*cacheItem)
99
100
if existingCacheItemValue.errors.Load() >= int32(t.paramOccurenceThreshold) {
101
existingCacheItemValue.Do(func() {
102
gologger.Verbose().Msgf("[%s] Skipped %s from parameter for %s as found uninteresting %d times", template, parameter, target, existingCacheItemValue.errors.Load())
103
})
104
return true
105
}
106
return false
107
}
108
109
// UnmarkParameter unmarks a parameter as frequently occuring. This carries
110
// more weight and resets the frequency counter for the parameter causing
111
// it to be checked again. This is done when results are found.
112
func (t *Tracker) UnmarkParameter(parameter, target, template string) {
113
normalizedTarget := normalizeTarget(target)
114
key := getFrequencyKey(parameter, normalizedTarget, template)
115
116
if t.isDebug {
117
gologger.Verbose().Msgf("[%s] Unmarking %s as frequently found uninteresting", template, key)
118
}
119
120
_ = t.frequencies.Remove(key)
121
}
122
123
func getFrequencyKey(parameter, target, template string) string {
124
var sb strings.Builder
125
sb.WriteString(target)
126
sb.WriteString(":")
127
sb.WriteString(template)
128
sb.WriteString(":")
129
sb.WriteString(parameter)
130
str := sb.String()
131
return str
132
}
133
134
func normalizeTarget(value string) string {
135
finalValue := value
136
if strings.HasPrefix(value, "http") {
137
if parsed, err := url.Parse(value); err == nil {
138
hostname := parsed.Host
139
finalPort := parsed.Port()
140
if finalPort == "" {
141
if parsed.Scheme == "https" {
142
finalPort = "443"
143
} else {
144
finalPort = "80"
145
}
146
hostname = net.JoinHostPort(parsed.Host, finalPort)
147
}
148
finalValue = hostname
149
}
150
}
151
return finalValue
152
}
153
154