mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-22 17:35:26 +00:00
duplicated params in self contained requests (#3608)
* fix duplicated params in self-contained+ export extracted values to file * add integration tests + fix percentage overflow in pb * fix integration test template id * integration test: validate if file exists
This commit is contained in:
parent
ea5f8a0638
commit
4e6ef4490e
18
integration_tests/http/save-extractor-values-to-file.yaml
Normal file
18
integration_tests/http/save-extractor-values-to-file.yaml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
id: save-extractor-values-to-file
|
||||||
|
|
||||||
|
info:
|
||||||
|
name: save extractor values to file
|
||||||
|
author: pdteam
|
||||||
|
severity: info
|
||||||
|
|
||||||
|
requests:
|
||||||
|
- method: GET
|
||||||
|
path:
|
||||||
|
- "{{BaseURL}}"
|
||||||
|
|
||||||
|
extractors:
|
||||||
|
- type: regex
|
||||||
|
part: body
|
||||||
|
regex:
|
||||||
|
- '[0-9]+'
|
||||||
|
to: output.txt
|
||||||
18
integration_tests/http/self-contained-with-params.yaml
Normal file
18
integration_tests/http/self-contained-with-params.yaml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
id: self-contained-with-params
|
||||||
|
|
||||||
|
info:
|
||||||
|
name: self contained with params
|
||||||
|
author: pd-team
|
||||||
|
severity: info
|
||||||
|
|
||||||
|
self-contained: true
|
||||||
|
requests:
|
||||||
|
- raw:
|
||||||
|
- |
|
||||||
|
GET http://127.0.0.1:5431/?something=here&key=value HTTP/1.1
|
||||||
|
Host: {{Hostname}}
|
||||||
|
|
||||||
|
matchers:
|
||||||
|
- type: word
|
||||||
|
words:
|
||||||
|
- This is self-contained response
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -19,6 +20,7 @@ import (
|
|||||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||||
"github.com/projectdiscovery/retryablehttp-go"
|
"github.com/projectdiscovery/retryablehttp-go"
|
||||||
errorutil "github.com/projectdiscovery/utils/errors"
|
errorutil "github.com/projectdiscovery/utils/errors"
|
||||||
|
fileutil "github.com/projectdiscovery/utils/file"
|
||||||
logutil "github.com/projectdiscovery/utils/log"
|
logutil "github.com/projectdiscovery/utils/log"
|
||||||
sliceutil "github.com/projectdiscovery/utils/slice"
|
sliceutil "github.com/projectdiscovery/utils/slice"
|
||||||
stringsutil "github.com/projectdiscovery/utils/strings"
|
stringsutil "github.com/projectdiscovery/utils/strings"
|
||||||
@ -48,6 +50,7 @@ var httpTestcases = map[string]testutils.TestCase{
|
|||||||
"http/request-condition.yaml": &httpRequestCondition{},
|
"http/request-condition.yaml": &httpRequestCondition{},
|
||||||
"http/request-condition-new.yaml": &httpRequestCondition{},
|
"http/request-condition-new.yaml": &httpRequestCondition{},
|
||||||
"http/self-contained.yaml": &httpRequestSelfContained{},
|
"http/self-contained.yaml": &httpRequestSelfContained{},
|
||||||
|
"http/self-contained-with-params.yaml": &httpRequestSelfContainedWithParams{},
|
||||||
"http/self-contained-file-input.yaml": &httpRequestSelfContainedFileInput{},
|
"http/self-contained-file-input.yaml": &httpRequestSelfContainedFileInput{},
|
||||||
"http/get-case-insensitive.yaml": &httpGetCaseInsensitive{},
|
"http/get-case-insensitive.yaml": &httpGetCaseInsensitive{},
|
||||||
"http/get.yaml,http/get-case-insensitive.yaml": &httpGetCaseInsensitiveCluster{},
|
"http/get.yaml,http/get-case-insensitive.yaml": &httpGetCaseInsensitiveCluster{},
|
||||||
@ -69,6 +72,7 @@ var httpTestcases = map[string]testutils.TestCase{
|
|||||||
"http/get-without-scheme.yaml": &httpGetWithoutScheme{},
|
"http/get-without-scheme.yaml": &httpGetWithoutScheme{},
|
||||||
"http/cl-body-without-header.yaml": &httpCLBodyWithoutHeader{},
|
"http/cl-body-without-header.yaml": &httpCLBodyWithoutHeader{},
|
||||||
"http/cl-body-with-header.yaml": &httpCLBodyWithHeader{},
|
"http/cl-body-with-header.yaml": &httpCLBodyWithHeader{},
|
||||||
|
"http/save-extractor-values-to-file.yaml": &httpSaveExtractorValuesToFile{},
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpInteractshRequest struct{}
|
type httpInteractshRequest struct{}
|
||||||
@ -855,6 +859,42 @@ func (h *httpRequestSelfContained) Execute(filePath string) error {
|
|||||||
return expectResultsCount(results, 1)
|
return expectResultsCount(results, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testcase to check duplicated values in params
|
||||||
|
type httpRequestSelfContainedWithParams struct{}
|
||||||
|
|
||||||
|
// Execute executes a test case and returns an error if occurred
|
||||||
|
func (h *httpRequestSelfContainedWithParams) Execute(filePath string) error {
|
||||||
|
router := httprouter.New()
|
||||||
|
var err error
|
||||||
|
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
params := r.URL.Query()
|
||||||
|
// we intentionally use params["test"] instead of params.Get("test") to test the case where
|
||||||
|
// there are multiple parameters with the same name
|
||||||
|
if !reflect.DeepEqual(params["something"], []string{"here"}) {
|
||||||
|
err = errorutil.WrapfWithNil(err, "expected %v, got %v", []string{"here"}, params["something"])
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(params["key"], []string{"value"}) {
|
||||||
|
err = errorutil.WrapfWithNil(err, "expected %v, got %v", []string{"key"}, params["value"])
|
||||||
|
}
|
||||||
|
_, _ = w.Write([]byte("This is self-contained response"))
|
||||||
|
})
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: fmt.Sprintf("localhost:%d", defaultStaticPort),
|
||||||
|
Handler: router,
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
_ = server.ListenAndServe()
|
||||||
|
}()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "", debug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return expectResultsCount(results, 1)
|
||||||
|
}
|
||||||
|
|
||||||
type httpRequestSelfContainedFileInput struct{}
|
type httpRequestSelfContainedFileInput struct{}
|
||||||
|
|
||||||
func (h *httpRequestSelfContainedFileInput) Execute(filePath string) error {
|
func (h *httpRequestSelfContainedFileInput) Execute(filePath string) error {
|
||||||
@ -1262,3 +1302,31 @@ func (h *httpCLBodyWithHeader) Execute(filePath string) error {
|
|||||||
}
|
}
|
||||||
return expectResultsCount(got, 1)
|
return expectResultsCount(got, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type httpSaveExtractorValuesToFile struct{}
|
||||||
|
|
||||||
|
func (h *httpSaveExtractorValuesToFile) Execute(filePath string) error {
|
||||||
|
router := httprouter.New()
|
||||||
|
router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
var buff bytes.Buffer
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
buff.WriteString(fmt.Sprintf(`"value": %v`+"\n", i))
|
||||||
|
}
|
||||||
|
_, _ = w.Write(buff.Bytes())
|
||||||
|
})
|
||||||
|
ts := httptest.NewServer(router)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove output.txt file if exists
|
||||||
|
if !fileutil.FileExists("output.txt") {
|
||||||
|
return fmt.Errorf("extractor output file output.txt file does not exist")
|
||||||
|
} else {
|
||||||
|
_ = os.Remove("output.txt")
|
||||||
|
}
|
||||||
|
return expectResultsCount(results, 1)
|
||||||
|
}
|
||||||
|
|||||||
@ -3,9 +3,10 @@ package extractors
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/antchfx/htmlquery"
|
"github.com/antchfx/htmlquery"
|
||||||
"github.com/antchfx/xmlquery"
|
"github.com/antchfx/xmlquery"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||||
)
|
)
|
||||||
@ -29,6 +30,7 @@ func (e *Extractor) ExtractRegex(corpus string) map[string]struct{} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
e.SaveToFile(results)
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,6 +58,7 @@ func (e *Extractor) ExtractKval(data map[string]interface{}) map[string]struct{}
|
|||||||
results[itemString] = struct{}{}
|
results[itemString] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
e.SaveToFile(results)
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,6 +96,7 @@ func (e *Extractor) ExtractHTML(corpus string) map[string]struct{} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
e.SaveToFile(results)
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,6 +127,7 @@ func (e *Extractor) ExtractXML(corpus string) map[string]struct{} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
e.SaveToFile(results)
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,6 +164,7 @@ func (e *Extractor) ExtractJSON(corpus string) map[string]struct{} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
e.SaveToFile(results)
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,6 +185,6 @@ func (e *Extractor) ExtractDSL(data map[string]interface{}) map[string]struct{}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
e.SaveToFile(results)
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,14 @@
|
|||||||
package extractors
|
package extractors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/Knetic/govaluate"
|
"github.com/Knetic/govaluate"
|
||||||
"github.com/itchyny/gojq"
|
"github.com/itchyny/gojq"
|
||||||
|
"github.com/projectdiscovery/gologger"
|
||||||
|
fileutil "github.com/projectdiscovery/utils/file"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Extractor is used to extract part of response using a regex.
|
// Extractor is used to extract part of response using a regex.
|
||||||
@ -113,4 +117,36 @@ type Extractor struct {
|
|||||||
// - false
|
// - false
|
||||||
// - true
|
// - true
|
||||||
CaseInsensitive bool `yaml:"case-insensitive,omitempty" json:"case-insensitive,omitempty" jsonschema:"title=use case insensitive extract,description=use case insensitive extract"`
|
CaseInsensitive bool `yaml:"case-insensitive,omitempty" json:"case-insensitive,omitempty" jsonschema:"title=use case insensitive extract,description=use case insensitive extract"`
|
||||||
|
// description: |
|
||||||
|
// ToFile (to) saves extracted requests to file and if file is present values are appended to file.
|
||||||
|
ToFile string `yaml:"to,omitempty" json:"to,omitempty" jsonschema:"title=save extracted values to file,description=save extracted values to file"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveToFile saves extracted values to file if `to` is present and valid
|
||||||
|
func (e *Extractor) SaveToFile(data map[string]struct{}) {
|
||||||
|
if e.ToFile == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fileutil.FileExists(e.ToFile) {
|
||||||
|
baseDir := filepath.Dir(e.ToFile)
|
||||||
|
if baseDir != "." && !fileutil.FolderExists(baseDir) {
|
||||||
|
if err := fileutil.CreateFolder(baseDir); err != nil {
|
||||||
|
gologger.Error().Msgf("extractor: could not create folder %s: %s\n", baseDir, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file, err := os.OpenFile(e.ToFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
|
||||||
|
if err != nil {
|
||||||
|
gologger.Error().Msgf("extractor: could not open file %s: %s\n", e.ToFile, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
for k := range data {
|
||||||
|
if _, err = file.WriteString(k + "\n"); err != nil {
|
||||||
|
gologger.Error().Msgf("extractor: could not write to file %s: %s\n", e.ToFile, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -170,6 +170,11 @@ func (p *StatsTicker) makePrintCallback() func(stats clistats.StatisticsClient)
|
|||||||
requests, okRequests := stats.GetCounter("requests")
|
requests, okRequests := stats.GetCounter("requests")
|
||||||
total, okTotal := stats.GetCounter("total")
|
total, okTotal := stats.GetCounter("total")
|
||||||
|
|
||||||
|
// If input is not given, total is 0 which cause percentage overflow
|
||||||
|
if total == 0 {
|
||||||
|
total = requests
|
||||||
|
}
|
||||||
|
|
||||||
if okRequests && okTotal && duration > 0 && !p.cloud {
|
if okRequests && okTotal && duration > 0 && !p.cloud {
|
||||||
builder.WriteString(" | RPS: ")
|
builder.WriteString(" | RPS: ")
|
||||||
builder.WriteString(clistats.String(uint64(float64(requests) / duration.Seconds())))
|
builder.WriteString(clistats.String(uint64(float64(requests) / duration.Seconds())))
|
||||||
|
|||||||
@ -255,6 +255,11 @@ func (r *requestGenerator) generateHttpRequest(ctx context.Context, urlx *urluti
|
|||||||
// finalVars = contains all variables including generator and protocol specific variables
|
// finalVars = contains all variables including generator and protocol specific variables
|
||||||
// generatorValues = contains variables used in fuzzing or other generator specific values
|
// generatorValues = contains variables used in fuzzing or other generator specific values
|
||||||
func (r *requestGenerator) generateRawRequest(ctx context.Context, rawRequest string, baseURL *urlutil.URL, finalVars, generatorValues map[string]interface{}) (*generatedRequest, error) {
|
func (r *requestGenerator) generateRawRequest(ctx context.Context, rawRequest string, baseURL *urlutil.URL, finalVars, generatorValues map[string]interface{}) (*generatedRequest, error) {
|
||||||
|
// Unlike other requests parsedURL/ InputURL in self contained templates is extracted from raw request itself h
|
||||||
|
// and variables are supposed to be given from command line and not from inputURL
|
||||||
|
// ence this cause issues like duplicated params/paths.
|
||||||
|
// TODO: implement a generic raw request parser in rawhttp library (without variables and stuff)
|
||||||
|
baseURL.Params = nil // this fixes issue of duplicated params in self contained templates but not a appropriate fix
|
||||||
rawRequestData, err := raw.Parse(rawRequest, baseURL, r.request.Unsafe)
|
rawRequestData, err := raw.Parse(rawRequest, baseURL, r.request.Unsafe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user