Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/fuzz/dataformat/form.go
2070 views
1
package dataformat
2
3
import (
4
"fmt"
5
"regexp"
6
"strconv"
7
"strings"
8
9
"github.com/projectdiscovery/gologger"
10
mapsutil "github.com/projectdiscovery/utils/maps"
11
urlutil "github.com/projectdiscovery/utils/url"
12
)
13
14
const (
15
normalizedRegex = `_(\d+)$`
16
)
17
18
var (
19
reNormalized = regexp.MustCompile(normalizedRegex)
20
)
21
22
// == Handling Duplicate Query Parameters / Form Data ==
23
// Nuclei supports fuzzing duplicate query parameters by internally normalizing
24
// them and denormalizing them back when creating request this normalization
25
// can be leveraged to specify custom fuzzing behaviour in template as well
26
// if a query like `?foo=bar&foo=baz&foo=fuzzz` is provided, it will be normalized to
27
// foo_1=bar , foo_2=baz , foo=fuzzz (i.e last value is given original key which is usual behaviour in HTTP and its implementations)
28
// this way this change does not break any existing rules in template given by keys-regex or keys
29
// At same time if user wants to specify 2nd or 1st duplicate value in template, they can use foo_1 or foo_2 in keys-regex or keys
30
// Note: By default all duplicate query parameters are fuzzed
31
32
type Form struct{}
33
34
var (
35
_ DataFormat = &Form{}
36
)
37
38
// NewForm returns a new Form encoder
39
func NewForm() *Form {
40
return &Form{}
41
}
42
43
// IsType returns true if the data is Form encoded
44
func (f *Form) IsType(data string) bool {
45
return false
46
}
47
48
// Encode encodes the data into Form format
49
func (f *Form) Encode(data KV) (string, error) {
50
params := urlutil.NewOrderedParams()
51
52
data.Iterate(func(key string, value any) bool {
53
params.Add(key, fmt.Sprint(value))
54
return true
55
})
56
57
normalized := map[string]map[string]string{}
58
// Normalize the data
59
for _, origKey := range data.OrderedMap.GetKeys() {
60
// here origKey is base key without _1, _2 etc.
61
if origKey != "" && !reNormalized.MatchString(origKey) {
62
params.Iterate(func(key string, value []string) bool {
63
if strings.HasPrefix(key, origKey) && reNormalized.MatchString(key) {
64
m := map[string]string{}
65
if normalized[origKey] != nil {
66
m = normalized[origKey]
67
}
68
if len(value) == 1 {
69
m[key] = value[0]
70
} else {
71
m[key] = ""
72
}
73
normalized[origKey] = m
74
params.Del(key)
75
}
76
return true
77
})
78
}
79
}
80
81
if len(normalized) > 0 {
82
for k, v := range normalized {
83
maxIndex := -1
84
for key := range v {
85
matches := reNormalized.FindStringSubmatch(key)
86
if len(matches) == 2 {
87
dataIdx, err := strconv.Atoi(matches[1])
88
if err != nil {
89
gologger.Verbose().Msgf("error converting normalized index(%v) to integer: %v", matches[1], err)
90
continue
91
}
92
if dataIdx > maxIndex {
93
maxIndex = dataIdx
94
}
95
}
96
}
97
if maxIndex >= 0 { // Ensure the slice is only created if maxIndex is valid
98
data := make([]string, maxIndex+1) // Ensure the slice is large enough
99
for key, value := range v {
100
matches := reNormalized.FindStringSubmatch(key)
101
if len(matches) == 2 {
102
dataIdx, err := strconv.Atoi(matches[1]) // Error already checked above
103
if err != nil {
104
gologger.Verbose().Msgf("error converting data index to integer: %v", err)
105
continue
106
}
107
// Validate dataIdx to avoid index out of range errors
108
if dataIdx > 0 && dataIdx <= len(data) {
109
data[dataIdx-1] = value // Use dataIdx-1 since slice is 0-indexed
110
} else {
111
gologger.Verbose().Msgf("data index out of range: %d", dataIdx)
112
}
113
}
114
}
115
if len(params.Get(k)) > 0 {
116
data[maxIndex] = fmt.Sprint(params.Get(k)) // Use maxIndex which is the last index
117
}
118
// remove existing
119
params.Del(k)
120
if len(data) > 0 {
121
params.Add(k, data...)
122
}
123
}
124
}
125
}
126
127
encoded := params.Encode()
128
return encoded, nil
129
}
130
131
// Decode decodes the data from Form format
132
func (f *Form) Decode(data string) (KV, error) {
133
ordered_params := urlutil.NewOrderedParams()
134
ordered_params.Merge(data)
135
136
values := mapsutil.NewOrderedMap[string, any]()
137
ordered_params.Iterate(func(key string, value []string) bool {
138
if len(value) == 1 {
139
values.Set(key, value[0])
140
} else {
141
// in case of multiple query params in form data
142
// last value is considered and previous values are exposed with _1, _2, _3 etc.
143
// note that last value will not be included in _1, _2, _3 etc.
144
for i := 0; i < len(value)-1; i++ {
145
values.Set(key+"_"+strconv.Itoa(i+1), value[i])
146
}
147
values.Set(key, value[len(value)-1])
148
}
149
return true
150
})
151
return KVOrderedMap(&values), nil
152
}
153
154
// Name returns the name of the encoder
155
func (f *Form) Name() string {
156
return FormDataFormat
157
}
158
159