From 13e9072c50d7897b18ee6f74d74467970f58986b Mon Sep 17 00:00:00 2001 From: "alban.stourbe stourbe" Date: Thu, 6 Jun 2024 17:30:57 +0200 Subject: [PATCH 1/2] Add AutoFillSuggestion for select,textarea in form and perfom autofill test --- pkg/engine/parser/parser.go | 10 +-- pkg/utils/formfill.go | 169 ++++++++++++++++++++++++++++++++++++ pkg/utils/formfill_test.go | 32 +++++-- 3 files changed, 200 insertions(+), 11 deletions(-) diff --git a/pkg/engine/parser/parser.go b/pkg/engine/parser/parser.go index d621a6de..9fe442ab 100644 --- a/pkg/engine/parser/parser.go +++ b/pkg/engine/parser/parser.go @@ -527,16 +527,16 @@ func bodyFormTagParser(resp *navigation.Response) (navigationRequests []*navigat multipartWriter = multipart.NewWriter(&sb) } - // Get the form field suggestions for all inputs - formInputs := []utils.FormInput{} - item.Find("input").Each(func(index int, item *goquery.Selection) { + // Get the form field suggestions for all elements in the form + formFields := []interface{}{} + item.Find("input, select, textarea").Each(func(index int, item *goquery.Selection) { if len(item.Nodes) == 0 { return } - formInputs = append(formInputs, utils.ConvertGoquerySelectionToFormInput(item)) + formFields = append(formFields, utils.ConvertGoquerySelectionToFormField(item)) }) - dataMap := utils.FormInputFillSuggestions(formInputs) + dataMap := utils.FormFillSuggestions(formFields) dataMap.Iterate(func(key, value string) bool { if key == "" { return true diff --git a/pkg/utils/formfill.go b/pkg/utils/formfill.go index 1ace9a35..57f4e56d 100644 --- a/pkg/utils/formfill.go +++ b/pkg/utils/formfill.go @@ -41,6 +41,25 @@ type FormInput struct { Attributes mapsutil.OrderedMap[string, string] } +// FormOption is an option for a select input +type FormOption struct { + Value string + Selected string + Attributes mapsutil.OrderedMap[string, string] +} + +// FormSelect is a select input for a form field +type FormSelect struct { + Name string + Attributes mapsutil.OrderedMap[string, string] + FormOptions []FormOption +} + +type FormTextArea struct { + Name string + Attributes mapsutil.OrderedMap[string, string] +} + // FormInputFillSuggestions returns a list of form filling suggestions // for inputs returning the specified recommended values. func FormInputFillSuggestions(inputs []FormInput) mapsutil.OrderedMap[string, string] { @@ -106,6 +125,78 @@ func FormInputFillSuggestions(inputs []FormInput) mapsutil.OrderedMap[string, st return data } +// FormSelectFill fills a map with selected values from a slice of FormSelect structs. +// It iterates over each FormSelect struct in the inputs slice and checks for a selected option. +// If a selected option is found, it adds the corresponding value to the map using the input's name as the key. +// If no option is selected, it selects the first option and adds its value to the map. +// The function returns the filled map. +func FormSelectFill(inputs []FormSelect) mapsutil.OrderedMap[string, string] { + data := mapsutil.NewOrderedMap[string, string]() + for _, input := range inputs { + for _, option := range input.FormOptions { + if option.Selected != "" { + data.Set(input.Name, option.Value) + break + } + } + + // If no option is selected, select the first one + if !data.Has(input.Name) && len(input.FormOptions) > 0 { + data.Set(input.Name, input.FormOptions[0].Value) + } + } + return data +} + +// FormTextAreaFill fills the form text areas with placeholder values. +// It takes a slice of FormTextArea structs as input and returns an OrderedMap +// containing the form field names as keys and the placeholder values as values. +func FormTextAreaFill(inputs []FormTextArea) mapsutil.OrderedMap[string, string] { + data := mapsutil.NewOrderedMap[string, string]() + for _, input := range inputs { + data.Set(input.Name, FormData.Placeholder) + } + return data +} + +// FormFillSuggestions takes a slice of form fields and returns an ordered map +// containing suggestions for filling those form fields. The function iterates +// over each form field and based on its type, calls the corresponding fill +// function to generate suggestions. The suggestions are then merged into a +// single ordered map and returned. +// +// Parameters: +// - formFields: A slice of form fields. +// +// Returns: +// An ordered map containing suggestions for filling the form fields. +func FormFillSuggestions(formFields []interface{}) mapsutil.OrderedMap[string, string] { + merged := mapsutil.NewOrderedMap[string, string]() + for _, item := range formFields { + switch v := item.(type) { + case FormInput: + dataMapInputs := FormInputFillSuggestions([]FormInput{v}) + dataMapInputs.Iterate(func(key, value string) bool { + merged.Set(key, value) + return true + }) + case FormSelect: + dataMapSelects := FormSelectFill([]FormSelect{v}) + dataMapSelects.Iterate(func(key, value string) bool { + merged.Set(key, value) + return true + }) + case FormTextArea: + dataMapTextArea := FormTextAreaFill([]FormTextArea{v}) + dataMapTextArea.Iterate(func(key, value string) bool { + merged.Set(key, value) + return true + }) + } + } + return merged +} + // ConvertGoquerySelectionToFormInput converts goquery selection to form input func ConvertGoquerySelectionToFormInput(item *goquery.Selection) FormInput { attrs := item.Nodes[0].Attr @@ -125,3 +216,81 @@ func ConvertGoquerySelectionToFormInput(item *goquery.Selection) FormInput { } return input } + +// ConvertGoquerySelectionToFormOption converts a goquery.Selection object to a FormOption object. +// It extracts the attributes from the goquery.Selection object and populates a FormOption object with the extracted values. +func ConvertGoquerySelectionToFormOption(item *goquery.Selection) FormOption { + attrs := item.Nodes[0].Attr + input := FormOption{Attributes: mapsutil.NewOrderedMap[string, string]()} + for _, attribute := range attrs { + switch attribute.Key { + case "value": + input.Value = attribute.Val + + case "selected": + input.Selected = attribute.Key + default: + input.Attributes.Set(attribute.Key, attribute.Val) + } + } + return input +} + +// ConvertGoquerySelectionToFormSelect converts a goquery.Selection object to a FormSelect object. +// It extracts the attributes and form options from the goquery.Selection and populates them in the FormSelect object. +// The converted FormSelect object is then returned. +func ConvertGoquerySelectionToFormSelect(item *goquery.Selection) FormSelect { + attrs := item.Nodes[0].Attr + input := FormSelect{Attributes: mapsutil.NewOrderedMap[string, string]()} + for _, attribute := range attrs { + switch attribute.Key { + case "name": + input.Name = attribute.Val + default: + input.Attributes.Set(attribute.Key, attribute.Val) + } + } + + input.FormOptions = []FormOption{} + item.Find("option").Each(func(_ int, option *goquery.Selection) { + input.FormOptions = append(input.FormOptions, ConvertGoquerySelectionToFormOption(option)) + }) + return input +} + +// ConvertGoquerySelectionToFormTextArea converts a goquery.Selection object to a FormTextArea struct. +// It extracts the attributes from the first node of the selection and populates a FormTextArea object with the extracted data. +// The "name" attribute is assigned to the Name field of the FormTextArea, while other attributes are added to the Attributes map. +func ConvertGoquerySelectionToFormTextArea(item *goquery.Selection) FormTextArea { + attrs := item.Nodes[0].Attr + input := FormTextArea{Attributes: mapsutil.NewOrderedMap[string, string]()} + for _, attribute := range attrs { + switch attribute.Key { + case "name": + input.Name = attribute.Val + default: + input.Attributes.Set(attribute.Key, attribute.Val) + } + } + return input +} + +// ConvertGoquerySelectionToFormField converts a goquery.Selection object to a form field. +// It checks the type of the selection and calls the appropriate conversion function. +// If the selection is an input, it calls ConvertGoquerySelectionToFormInput. +// If the selection is a select, it calls ConvertGoquerySelectionToFormSelect. +// If the selection is a textarea, it calls ConvertGoquerySelectionToFormTextArea. +// If the selection is of any other type, it returns nil. +func ConvertGoquerySelectionToFormField(item *goquery.Selection) interface{} { + if item.Is("input") { + return ConvertGoquerySelectionToFormInput(item) + } + if item.Is("select") { + return ConvertGoquerySelectionToFormSelect(item) + } + if item.Is("textarea") { + return ConvertGoquerySelectionToFormTextArea(item) + } + + return nil +} diff --git a/pkg/utils/formfill_test.go b/pkg/utils/formfill_test.go index 268f654c..10de174b 100644 --- a/pkg/utils/formfill_test.go +++ b/pkg/utils/formfill_test.go @@ -33,7 +33,27 @@ var htmlFormInputExample = ` -

+

Kindly Select your favourite food

+ +

Kindly Select your favourite country

+ + + + + +

+ ` @@ -44,16 +64,16 @@ func TestFormInputFillSuggestions(t *testing.T) { document.Find("form[action]").Each(func(i int, item *goquery.Selection) { queryValuesWriter := make(url.Values) - formInputs := []FormInput{} + formFields := []interface{}{} - item.Find("input").Each(func(index int, item *goquery.Selection) { + item.Find("input, textarea, select").Each(func(index int, item *goquery.Selection) { if len(item.Nodes) == 0 { return } - formInputs = append(formInputs, ConvertGoquerySelectionToFormInput(item)) + formFields = append(formFields, ConvertGoquerySelectionToFormField(item)) }) - dataMap := FormInputFillSuggestions(formInputs) + dataMap := FormFillSuggestions(formFields) dataMap.Iterate(func(key, value string) bool { if key == "" || value == "" { return true @@ -62,6 +82,6 @@ func TestFormInputFillSuggestions(t *testing.T) { return true }) value := queryValuesWriter.Encode() - require.Equal(t, "Startdate=katana&color=red&firstname=katana&num=51&password=katana&sport1=cricket&sport2=tennis&sport3=football&telephone=katanaP%40assw0rd1&upclick=%23a52a2a", value, "could not get correct encoded form") + require.Equal(t, "Startdate=katana&color=green&country=india&firstname=katana&food=pasta&message=katana&num=51&password=katana&sport1=cricket&sport2=tennis&sport3=football&telephone=katanaP%40assw0rd1&upclick=%23a52a2a", value, "could not get correct encoded form") }) } From bee95ef12ce562934afa28ef57bd7db3083b3e78 Mon Sep 17 00:00:00 2001 From: "alban.stourbe stourbe" Date: Tue, 16 Jul 2024 09:52:19 +0200 Subject: [PATCH 2/2] Add a small Merge Func for formill and enhances code based on the review --- pkg/utils/formfill.go | 44 +++++++++++++++++------------------------- pkg/utils/maps.go | 10 ++++++++++ pkg/utils/maps_test.go | 32 ++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 26 deletions(-) create mode 100644 pkg/utils/maps.go create mode 100644 pkg/utils/maps_test.go diff --git a/pkg/utils/formfill.go b/pkg/utils/formfill.go index 57f4e56d..9d99002d 100644 --- a/pkg/utils/formfill.go +++ b/pkg/utils/formfill.go @@ -41,8 +41,8 @@ type FormInput struct { Attributes mapsutil.OrderedMap[string, string] } -// FormOption is an option for a select input -type FormOption struct { +// SelectOption is an option for a select input +type SelectOption struct { Value string Selected string Attributes mapsutil.OrderedMap[string, string] @@ -50,9 +50,9 @@ type FormOption struct { // FormSelect is a select input for a form field type FormSelect struct { - Name string - Attributes mapsutil.OrderedMap[string, string] - FormOptions []FormOption + Name string + Attributes mapsutil.OrderedMap[string, string] + SelectOptions []SelectOption } type FormTextArea struct { @@ -133,7 +133,7 @@ func FormInputFillSuggestions(inputs []FormInput) mapsutil.OrderedMap[string, st func FormSelectFill(inputs []FormSelect) mapsutil.OrderedMap[string, string] { data := mapsutil.NewOrderedMap[string, string]() for _, input := range inputs { - for _, option := range input.FormOptions { + for _, option := range input.SelectOptions { if option.Selected != "" { data.Set(input.Name, option.Value) break @@ -141,8 +141,8 @@ func FormSelectFill(inputs []FormSelect) mapsutil.OrderedMap[string, string] { } // If no option is selected, select the first one - if !data.Has(input.Name) && len(input.FormOptions) > 0 { - data.Set(input.Name, input.FormOptions[0].Value) + if !data.Has(input.Name) && len(input.SelectOptions) > 0 { + data.Set(input.Name, input.SelectOptions[0].Value) } } return data @@ -176,22 +176,14 @@ func FormFillSuggestions(formFields []interface{}) mapsutil.OrderedMap[string, s switch v := item.(type) { case FormInput: dataMapInputs := FormInputFillSuggestions([]FormInput{v}) - dataMapInputs.Iterate(func(key, value string) bool { - merged.Set(key, value) - return true - }) + MergeDataMaps(&merged, dataMapInputs) case FormSelect: dataMapSelects := FormSelectFill([]FormSelect{v}) - dataMapSelects.Iterate(func(key, value string) bool { - merged.Set(key, value) - return true - }) + MergeDataMaps(&merged, dataMapSelects) + case FormTextArea: dataMapTextArea := FormTextAreaFill([]FormTextArea{v}) - dataMapTextArea.Iterate(func(key, value string) bool { - merged.Set(key, value) - return true - }) + MergeDataMaps(&merged, dataMapTextArea) } } return merged @@ -217,11 +209,11 @@ func ConvertGoquerySelectionToFormInput(item *goquery.Selection) FormInput { return input } -// ConvertGoquerySelectionToFormOption converts a goquery.Selection object to a FormOption object. -// It extracts the attributes from the goquery.Selection object and populates a FormOption object with the extracted values. -func ConvertGoquerySelectionToFormOption(item *goquery.Selection) FormOption { +// ConvertGoquerySelectionToSelectOption converts a goquery.Selection object to a SelectOption object. +// It extracts the attributes from the goquery.Selection object and populates a SelectOption object with the extracted values. +func ConvertGoquerySelectionToSelectOption(item *goquery.Selection) SelectOption { attrs := item.Nodes[0].Attr - input := FormOption{Attributes: mapsutil.NewOrderedMap[string, string]()} + input := SelectOption{Attributes: mapsutil.NewOrderedMap[string, string]()} for _, attribute := range attrs { switch attribute.Key { case "value": @@ -251,9 +243,9 @@ func ConvertGoquerySelectionToFormSelect(item *goquery.Selection) FormSelect { } } - input.FormOptions = []FormOption{} + input.SelectOptions = []SelectOption{} item.Find("option").Each(func(_ int, option *goquery.Selection) { - input.FormOptions = append(input.FormOptions, ConvertGoquerySelectionToFormOption(option)) + input.SelectOptions = append(input.SelectOptions, ConvertGoquerySelectionToSelectOption(option)) }) return input } diff --git a/pkg/utils/maps.go b/pkg/utils/maps.go new file mode 100644 index 00000000..05cbe2b9 --- /dev/null +++ b/pkg/utils/maps.go @@ -0,0 +1,10 @@ +package utils + +import mapsutil "github.com/projectdiscovery/utils/maps" + +func MergeDataMaps(dataMap1 *mapsutil.OrderedMap[string, string], dataMap2 mapsutil.OrderedMap[string, string]) { + dataMap2.Iterate(func(key, value string) bool { + dataMap1.Set(key, value) + return true + }) +} diff --git a/pkg/utils/maps_test.go b/pkg/utils/maps_test.go new file mode 100644 index 00000000..2b1c37bc --- /dev/null +++ b/pkg/utils/maps_test.go @@ -0,0 +1,32 @@ +package utils + +import ( + "testing" + + mapsutil "github.com/projectdiscovery/utils/maps" + "github.com/stretchr/testify/require" +) + +func TestMergeDataMap(t *testing.T) { + // Create two data maps + dataMap1 := mapsutil.NewOrderedMap[string, string]() + dataMap1.Set("key1", "value1") + dataMap1.Set("key2", "value2") + + dataMap2 := mapsutil.NewOrderedMap[string, string]() + dataMap2.Set("key3", "value3") + dataMap2.Set("key4", "value4") + + // Merge the data maps + MergeDataMaps(&dataMap1, dataMap2) + + // Verify the merged map contains all the keys and values + value1, _ := dataMap1.Get("key1") + value2, _ := dataMap1.Get("key2") + value3, _ := dataMap1.Get("key3") + value4, _ := dataMap1.Get("key4") + require.Equal(t, "value1", value1) + require.Equal(t, "value2", value2) + require.Equal(t, "value3", value3) + require.Equal(t, "value4", value4) +}