Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/protocols/common/generators/generators.go
2838 views
1
// Inspired from https://github.com/ffuf/ffuf/blob/master/pkg/input/input.go
2
3
package generators
4
5
import (
6
"maps"
7
8
"github.com/pkg/errors"
9
10
"github.com/projectdiscovery/nuclei/v3/pkg/catalog"
11
"github.com/projectdiscovery/nuclei/v3/pkg/types"
12
)
13
14
// PayloadGenerator is the generator struct for generating payloads
15
type PayloadGenerator struct {
16
Type AttackType
17
catalog catalog.Catalog
18
payloads map[string][]string
19
options *types.Options
20
}
21
22
// New creates a new generator structure for payload generation
23
func New(payloads map[string]interface{}, attackType AttackType, templatePath string, catalog catalog.Catalog, customAttackType string, opts *types.Options) (*PayloadGenerator, error) {
24
if attackType.String() == "" {
25
attackType = BatteringRamAttack
26
}
27
28
// Resolve payload paths if they are files.
29
payloadsFinal := make(map[string]interface{})
30
for payloadName, v := range payloads {
31
switch value := v.(type) {
32
case map[interface{}]interface{}:
33
values, err := parsePayloadsWithAggression(payloadName, value, opts.FuzzAggressionLevel)
34
if err != nil {
35
return nil, errors.Wrap(err, "could not parse payloads with aggression")
36
}
37
maps.Copy(payloadsFinal, values)
38
default:
39
payloadsFinal[payloadName] = v
40
}
41
}
42
43
generator := &PayloadGenerator{catalog: catalog, options: opts}
44
if err := generator.validate(payloadsFinal, templatePath); err != nil {
45
return nil, err
46
}
47
48
compiled, err := generator.loadPayloads(payloadsFinal, templatePath)
49
if err != nil {
50
return nil, err
51
}
52
generator.Type = attackType
53
generator.payloads = compiled
54
55
if customAttackType != "" {
56
attackTypeNew, err := toAttackType(customAttackType)
57
if err != nil {
58
return nil, errors.Wrap(err, "could not parse custom attack-type")
59
}
60
generator.Type = attackTypeNew
61
}
62
// Validate the batteringram payload set
63
if attackType == BatteringRamAttack {
64
if len(payloads) != 1 {
65
return nil, errors.New("batteringram must have single payload set")
66
}
67
}
68
return generator, nil
69
}
70
71
type aggressionLevelToPayloads struct {
72
Low []interface{}
73
Medium []interface{}
74
High []interface{}
75
}
76
77
// parsePayloadsWithAggression parses the payloads with the aggression level
78
//
79
// Three aggression are supported -
80
// - low
81
// - medium
82
// - high
83
//
84
// low is the default level. If medium is specified, all templates from
85
// low and medium are executed. Similarly with high, including all templates
86
// from low, medium, high.
87
func parsePayloadsWithAggression(name string, v map[interface{}]interface{}, aggression string) (map[string]interface{}, error) {
88
payloadsLevels := &aggressionLevelToPayloads{}
89
90
for k, v := range v {
91
if _, ok := v.([]interface{}); !ok {
92
return nil, errors.Errorf("only lists are supported for aggression levels payloads")
93
}
94
var ok bool
95
switch k {
96
case "low":
97
payloadsLevels.Low, ok = v.([]interface{})
98
case "medium":
99
payloadsLevels.Medium, ok = v.([]interface{})
100
case "high":
101
payloadsLevels.High, ok = v.([]interface{})
102
default:
103
return nil, errors.Errorf("invalid aggression level %s specified for %s", k, name)
104
}
105
if !ok {
106
return nil, errors.Errorf("invalid aggression level %s specified for %s", k, name)
107
}
108
}
109
110
payloads := make(map[string]interface{})
111
switch aggression {
112
case "low":
113
payloads[name] = payloadsLevels.Low
114
case "medium":
115
payloads[name] = append(payloadsLevels.Low, payloadsLevels.Medium...)
116
case "high":
117
payloads[name] = append(payloadsLevels.Low, payloadsLevels.Medium...)
118
payloads[name] = append(payloads[name].([]interface{}), payloadsLevels.High...)
119
default:
120
return nil, errors.Errorf("invalid aggression level %s specified for %s", aggression, name)
121
}
122
return payloads, nil
123
}
124
125
// Iterator is a single instance of an iterator for a generator structure
126
type Iterator struct {
127
Type AttackType
128
position int
129
msbIterator int
130
total int
131
payloads []*payloadIterator
132
}
133
134
// NewIterator creates a new iterator for the payloads generator
135
func (g *PayloadGenerator) NewIterator() *Iterator {
136
var payloads []*payloadIterator
137
138
for name, values := range g.payloads {
139
payloads = append(payloads, &payloadIterator{name: name, values: values})
140
}
141
iterator := &Iterator{
142
Type: g.Type,
143
payloads: payloads,
144
}
145
iterator.total = iterator.Total()
146
return iterator
147
}
148
149
// Reset resets the iterator back to its initial value
150
func (i *Iterator) Reset() {
151
i.position = 0
152
i.msbIterator = 0
153
154
for _, payload := range i.payloads {
155
payload.resetPosition()
156
}
157
}
158
159
// Remaining returns the amount of requests left for the generator.
160
func (i *Iterator) Remaining() int {
161
return i.total - i.position
162
}
163
164
// Total returns the amount of input combinations available
165
func (i *Iterator) Total() int {
166
count := 0
167
switch i.Type {
168
case BatteringRamAttack:
169
for _, p := range i.payloads {
170
count += len(p.values)
171
}
172
case PitchForkAttack:
173
count = len(i.payloads[0].values)
174
for _, p := range i.payloads {
175
if count > len(p.values) {
176
count = len(p.values)
177
}
178
}
179
case ClusterBombAttack:
180
count = 1
181
for _, p := range i.payloads {
182
count *= len(p.values)
183
}
184
}
185
return count
186
}
187
188
// Value returns the next value for an iterator
189
func (i *Iterator) Value() (map[string]interface{}, bool) {
190
switch i.Type {
191
case BatteringRamAttack:
192
return i.batteringRamValue()
193
case PitchForkAttack:
194
return i.pitchforkValue()
195
case ClusterBombAttack:
196
return i.clusterbombValue()
197
default:
198
return i.batteringRamValue()
199
}
200
}
201
202
// batteringRamValue returns a list of all payloads for the iterator
203
func (i *Iterator) batteringRamValue() (map[string]interface{}, bool) {
204
values := make(map[string]interface{}, 1)
205
206
currentIndex := i.msbIterator
207
payload := i.payloads[currentIndex]
208
if !payload.next() {
209
i.msbIterator++
210
if i.msbIterator == len(i.payloads) {
211
return nil, false
212
}
213
return i.batteringRamValue()
214
}
215
values[payload.name] = payload.value()
216
payload.incrementPosition()
217
i.position++
218
return values, true
219
}
220
221
// pitchforkValue returns a map of keyword:value pairs in same index
222
func (i *Iterator) pitchforkValue() (map[string]interface{}, bool) {
223
values := make(map[string]interface{}, len(i.payloads))
224
225
for _, p := range i.payloads {
226
if !p.next() {
227
return nil, false
228
}
229
values[p.name] = p.value()
230
p.incrementPosition()
231
}
232
i.position++
233
return values, true
234
}
235
236
// clusterbombValue returns a combination of all input pairs in key:value format.
237
func (i *Iterator) clusterbombValue() (map[string]interface{}, bool) {
238
if i.position >= i.total {
239
return nil, false
240
}
241
values := make(map[string]interface{}, len(i.payloads))
242
243
// Should we signal the next InputProvider in the slice to increment
244
signalNext := false
245
first := true
246
for index, p := range i.payloads {
247
if signalNext {
248
p.incrementPosition()
249
signalNext = false
250
}
251
if !p.next() {
252
// No more inputs in this input provider
253
if index == i.msbIterator {
254
// Reset all previous wordlists and increment the msb counter
255
i.msbIterator++
256
i.clusterbombIteratorReset()
257
// Start again
258
return i.clusterbombValue()
259
}
260
p.resetPosition()
261
signalNext = true
262
}
263
values[p.name] = p.value()
264
if first {
265
p.incrementPosition()
266
first = false
267
}
268
}
269
i.position++
270
return values, true
271
}
272
273
func (i *Iterator) clusterbombIteratorReset() {
274
for index, p := range i.payloads {
275
if index < i.msbIterator {
276
p.resetPosition()
277
}
278
if index == i.msbIterator {
279
p.incrementPosition()
280
}
281
}
282
}
283
284
// payloadIterator is a single instance of an iterator for a single payload list.
285
type payloadIterator struct {
286
index int
287
name string
288
values []string
289
}
290
291
// next returns true if there are more values in payload iterator
292
func (i *payloadIterator) next() bool {
293
return i.index < len(i.values)
294
}
295
296
// resetPosition resets the position of the payload iterator
297
func (i *payloadIterator) resetPosition() {
298
i.index = 0
299
}
300
301
// incrementPosition increments the position of the payload iterator
302
func (i *payloadIterator) incrementPosition() {
303
i.index++
304
}
305
306
// value returns the value of the payload at an index
307
func (i *payloadIterator) value() string {
308
return i.values[i.index]
309
}
310
311