From 25225bddc3d24b30e5e1b7fb7780b32e3c9ffad8 Mon Sep 17 00:00:00 2001 From: yusei-wy <31252054+yusei-wy@users.noreply.github.com> Date: Wed, 20 Aug 2025 11:37:40 +0900 Subject: [PATCH 1/2] fix: handle duplicate field names in multipart form encoding --- pkg/fuzz/dataformat/multipart.go | 27 ++++++-- pkg/fuzz/dataformat/multipart_test.go | 94 +++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 pkg/fuzz/dataformat/multipart_test.go diff --git a/pkg/fuzz/dataformat/multipart.go b/pkg/fuzz/dataformat/multipart.go index 97af6207f7..24046521e3 100644 --- a/pkg/fuzz/dataformat/multipart.go +++ b/pkg/fuzz/dataformat/multipart.go @@ -76,15 +76,28 @@ func (m *MultiPartForm) Encode(data KV) (string, error) { return true } - // Add field - if fw, err = w.CreateFormField(key); err != nil { - Itererr = err - return false + // Handle form field values - can be string or []string for duplicate fields + var values []string + switch v := value.(type) { + case string: + values = []string{v} + case []string: + values = v + default: + // Fallback: attempt string conversion + values = []string{fmt.Sprint(v)} } - if _, err = fw.Write([]byte(value.(string))); err != nil { - Itererr = err - return false + // Write all values for this field + for _, val := range values { + if fw, err = w.CreateFormField(key); err != nil { + Itererr = err + return false + } + if _, err = fw.Write([]byte(val)); err != nil { + Itererr = err + return false + } } return true }) diff --git a/pkg/fuzz/dataformat/multipart_test.go b/pkg/fuzz/dataformat/multipart_test.go new file mode 100644 index 0000000000..27c277a354 --- /dev/null +++ b/pkg/fuzz/dataformat/multipart_test.go @@ -0,0 +1,94 @@ +package dataformat + +import ( + "testing" + + mapsutil "github.com/projectdiscovery/utils/maps" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMultiPartFormEncode(t *testing.T) { + tests := []struct { + name string + fields map[string]any + wantErr bool + contains []string // strings that should appear in encoded output + }{ + { + name: "duplicate fields ([]string) - checkbox scenario", + fields: map[string]any{ + "interests": []string{"sports", "music", "reading"}, + "colors": []string{"red", "blue"}, + }, + contains: []string{"interests", "sports", "music", "reading", "colors", "red", "blue"}, + }, + { + name: "single string fields - backward compatibility", + fields: map[string]any{ + "username": "john", + "email": "john@example.com", + }, + contains: []string{"username", "john", "email", "john@example.com"}, + }, + { + name: "mixed types", + fields: map[string]any{ + "string": "text", + "array": []string{"item1", "item2"}, + "number": 42, // tests fmt.Sprint fallback + }, + contains: []string{"string", "text", "array", "item1", "item2", "number", "42"}, + }, + { + name: "empty array - should not appear in output", + fields: map[string]any{ + "emptyArray": []string{}, + "normalField": "value", + }, + contains: []string{"normalField", "value"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + form := NewMultiPartForm() + form.boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW" + + kv := mapsutil.NewOrderedMap[string, any]() + for k, v := range tt.fields { + kv.Set(k, v) + } + + encoded, err := form.Encode(KVOrderedMap(&kv)) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + for _, expected := range tt.contains { + assert.Contains(t, encoded, expected) + } + }) + } +} + +func TestMultiPartFormRoundTrip(t *testing.T) { + form := NewMultiPartForm() + form.boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW" + + original := mapsutil.NewOrderedMap[string, any]() + original.Set("username", "john") + original.Set("interests", []string{"sports", "music", "reading"}) + + encoded, err := form.Encode(KVOrderedMap(&original)) + require.NoError(t, err) + + decoded, err := form.Decode(encoded) + require.NoError(t, err) + + assert.Equal(t, "john", decoded.Get("username")) + assert.ElementsMatch(t, []string{"sports", "music", "reading"}, decoded.Get("interests")) +} From f5957f25b1d01d0b8b6959342897c6566d8cc1ce Mon Sep 17 00:00:00 2001 From: yusei-wy <31252054+yusei-wy@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:28:22 +0900 Subject: [PATCH 2/2] fix: properly handle array types in multipart form encoding - Add support for []any type to create separate fields for each element - Fix issue where arrays were converted to single string representation - Maintain backward compatibility for string and []string types --- pkg/fuzz/dataformat/multipart.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/fuzz/dataformat/multipart.go b/pkg/fuzz/dataformat/multipart.go index 24046521e3..05f8fa831b 100644 --- a/pkg/fuzz/dataformat/multipart.go +++ b/pkg/fuzz/dataformat/multipart.go @@ -83,6 +83,12 @@ func (m *MultiPartForm) Encode(data KV) (string, error) { values = []string{v} case []string: values = v + case []any: + // Handle []any by converting each element to string + values = make([]string, len(v)) + for i, elem := range v { + values[i] = fmt.Sprint(elem) + } default: // Fallback: attempt string conversion values = []string{fmt.Sprint(v)}