diff --git a/README.md b/README.md
index 750e2a14..bfa68972 100644
--- a/README.md
+++ b/README.md
@@ -90,30 +90,29 @@ TARGET:
-l, -list string path to file containing a list of target URLs/hosts to scan (one per line)
TEMPLATES:
- -tl list all available templates
-t, -templates string[] template or template directory paths to include in the scan
- -w, -workflows string[] list of workflows to run
- -nt, -new-templates run newly added templates only
+ -nt, -new-templates run only new templates added in latest nuclei-templates release
+ -w, -workflows string[] workflow or workflow directory paths to include in the scan
-validate validate the passed templates to nuclei
+ -tl list all available templates
FILTERING:
- -tags string[] execute a subset of templates that contain the provided tags
- -include-tags string[] tags from the default deny list that permit executing more intrusive templates
- -etags, -exclude-tags string[] exclude templates with the provided tags
- -include-templates string[] templates to be executed even if they are excluded either by default or configuration
- -exclude-templates, -exclude string[] template or template directory paths to exclude
- -severity, -impact value[] Templates to run based on severity. Possible values: info, low, medium, high, critical
- -author string[] execute templates that are (co-)created by the specified authors
+ -tags string[] execute a subset of templates that contain the provided tags
+ -etags, -exclude-tags string[] exclude templates with the provided tags
+ -itags, -include-tags string[] tags from the default deny list that permit executing more intrusive templates
+ -et, -exclude-templates string[] template or template directory paths to exclude
+ -it, -include-templates string[] templates to be executed even if they are excluded either by default or configuration
+ -s, -severity value[] Templates to run based on severity. Possible values - info,low,medium,high,critical
+ -es, -exclude-severity value[] Templates to exclude based on severity. Possible values - info,low,medium,high,critical
+ -a, -author string[] execute templates that are (co-)created by the specified authors
OUTPUT:
-o, -output string output file to write found issues/vulnerabilities
-silent display findings only
- -v, -verbose show verbose output
- -vv display extra verbose information
-nc, -no-color disable output content coloring (ANSI escape codes)
-json write output in JSONL(ines) format
-irr, -include-rr include request/response pairs in the JSONL output (for findings only)
- -nm, -no-meta don't display match metadata in CLI output
+ -nm, -no-meta don't display match metadata
-nts, -no-timestamp don't display timestamp metadata in CLI output
-rdb, -report-db string local nuclei reporting database (always use this to persist report data)
-me, -markdown-export string directory to export results in markdown format
@@ -125,37 +124,39 @@ CONFIGURATIONS:
-H, -header string[] custom headers in header:value format
-V, -var value custom vars in var=value format
-r, -resolvers string file containing resolver list for nuclei
- -system-resolvers use system DNS resolving as error fallback
+ -sr, -system-resolvers use system DNS resolving as error fallback
-passive enable passive HTTP response processing mode
- -env-vars enable environment variables support
+ -ev, -env-vars enable environment variables to be used in template
INTERACTSH:
- -no-interactsh disable interactsh server for OOB testing
- -interactsh-url string interactsh server url for self-hosted instance (default "https://interactsh.com")
- -interactsh-token string authentication token for self-hosted interactsh server
- -interactions-cache-size int number of requests to keep in the interactions cache (default 5000)
- -interactions-eviction int number of seconds to wait before evicting requests from cache (default 60)
- -interactions-poll-duration int number of seconds to wait before each interaction poll request (default 5)
- -interactions-cooldown-period int extra time for interaction polling before exiting (default 5)
+ -iserver, -interactsh-server string interactsh server url for self-hosted instance (default "https://interactsh.com")
+ -itoken, -interactsh-token string authentication token for self-hosted interactsh server
+ -interactions-cache-size int number of requests to keep in the interactions cache (default 5000)
+ -interactions-eviction int number of seconds to wait before evicting requests from cache (default 60)
+ -interactions-poll-duration int number of seconds to wait before each interaction poll request (default 5)
+ -interactions-cooldown-period int extra time for interaction polling before exiting (default 5)
+ -ni, -no-interactsh disable interactsh server for OAST testing, exclude OAST based templates
RATE-LIMIT:
-rl, -rate-limit int maximum number of requests to send per second (default 150)
-rlm, -rate-limit-minute int maximum number of requests to send per minute
-bs, -bulk-size int maximum number of hosts to be analyzed in parallel per template (default 25)
- -c, -concurrency int maximum number of templates to be executed in parallel (default 10)
+ -c, -concurrency int maximum number of templates to be executed in parallel (default 25)
OPTIMIZATIONS:
-timeout int time to wait in seconds before timeout (default 5)
-retries int number of times to retry a failed request (default 1)
- -max-host-error int max errors for a host before skipping from scan (default 30)
+ -mhe, -max-host-error int max errors for a host before skipping from scan (default 30)
-project use a project folder to avoid sending same request multiple times
- -project-path string set a specific project path (default "$TMPDIR/")
+ -project-path string set a specific project path
-spm, -stop-at-first-path stop processing HTTP requests after the first match (may break template/workflow logic)
+ -stream Stream mode - start elaborating without sorting the input
HEADLESS:
- -headless enable templates that require headless browser support
- -page-timeout int seconds to wait for each page in headless mode (default 20)
- -show-browser show the browser on the screen when running templates with headless mode
+ -headless enable templates that require headless browser support
+ -page-timeout int seconds to wait for each page in headless mode (default 20)
+ -sb, -show-browser show the browser on the screen when running templates with headless mode
+ -sc, -system-chrome Use local installed chrome browser instead of nuclei installed
DEBUG:
-debug show all requests and responses
@@ -163,22 +164,24 @@ DEBUG:
-debug-resp show all received responses
-proxy, -proxy-url string URL of the HTTP proxy server
-proxy-socks-url string URL of the SOCKS proxy server
- -trace-log string file to write sent requests trace log
+ -tlog, -trace-log string file to write sent requests trace log
-version show nuclei version
+ -v, -verbose show verbose output
+ -vv display extra verbose information
-tv, -templates-version shows the version of the installed nuclei-templates
UPDATE:
- -update update nuclei to the latest released version
- -ut, -update-templates update the community templates to latest released version
- -nut, -no-update-templates do not check for nuclei-templates updates
- -ud, -update-directory string overwrite the default nuclei-templates directory (default "$HOME/nuclei-templates")
+ -update update nuclei engine to the latest released version
+ -ut, -update-templates update nuclei-templates to latest released version
+ -ud, -update-directory string overwrite the default directory to install nuclei-templates
+ -duc, -disable-update-check disable automatic nuclei/templates update check
STATISTICS:
-stats display statistics about the running scan
- -stats-json write statistics data to an output file in JSONL(ines) format
+ -sj, -stats-json write statistics data to an output file in JSONL(ines) format
-si, -stats-interval int number of seconds to wait between showing a statistics update (default 5)
- -metrics expose nuclei metrics on a port
- -metrics-port int port to expose nuclei metrics on (default 9092)
+ -m, -metrics expose nuclei metrics on a port
+ -mp, -metrics-port int port to expose nuclei metrics on (default 9092)
```
### Running Nuclei
diff --git a/SYNTAX-REFERENCE.md b/SYNTAX-REFERENCE.md
index 30ed998f..a3fffe35 100755
--- a/SYNTAX-REFERENCE.md
+++ b/SYNTAX-REFERENCE.md
@@ -2392,6 +2392,31 @@ read-size: 2048
```
+
+
+
+
+
+
+read-all
bool
+
+
+
+
+ReadAll determines if the data stream should be read till the end regardless of the size
+
+Default value for read-all is false.
+
+
+
+Examples:
+
+
+```yaml
+read-all: false
+```
+
+
diff --git a/integration_tests/http/interactsh.yaml b/integration_tests/http/interactsh.yaml
new file mode 100644
index 00000000..28d9c560
--- /dev/null
+++ b/integration_tests/http/interactsh.yaml
@@ -0,0 +1,19 @@
+id: interactsh-integration-test
+
+info:
+ name: Interactsh Integration Test
+ author: pdteam
+ severity: info
+
+requests:
+ - method: GET
+ path:
+ - "{{BaseURL}}"
+ headers:
+ url: 'http://{{interactsh-url}}'
+
+ matchers:
+ - type: word
+ part: interactsh_protocol # Confirms the HTTP Interaction
+ words:
+ - "http"
\ No newline at end of file
diff --git a/nuclei-jsonschema.json b/nuclei-jsonschema.json
index 56253b7e..1db207d7 100755
--- a/nuclei-jsonschema.json
+++ b/nuclei-jsonschema.json
@@ -809,6 +809,11 @@
"title": "size of network response to read",
"description": "Size of response to read at the end. Default is 1024 bytes"
},
+ "read-all": {
+ "type": "boolean",
+ "title": "read all response stream",
+ "description": "Read all response stream till the server stops sending"
+ },
"matchers": {
"items": {
"$ref": "#/definitions/matchers.Matcher"
@@ -845,6 +850,7 @@
],
"properties": {
"id": {
+ "pattern": "^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$",
"type": "string",
"title": "id of the template",
"description": "The Unique ID for the template",
diff --git a/v2/cmd/integration-test/http.go b/v2/cmd/integration-test/http.go
index c3e23a7a..c8bc6acc 100644
--- a/v2/cmd/integration-test/http.go
+++ b/v2/cmd/integration-test/http.go
@@ -31,9 +31,36 @@ var httpTestcases = map[string]testutils.TestCase{
"http/raw-unsafe-request.yaml": &httpRawUnsafeRequest{},
"http/request-condition.yaml": &httpRequestCondition{},
"http/request-condition-new.yaml": &httpRequestCondition{},
+ "http/interactsh.yaml": &httpInteractshRequest{},
"http/self-contained.yaml": &httpRequestSelContained{},
}
+type httpInteractshRequest struct{}
+
+// Executes executes a test case and returns an error if occurred
+func (h *httpInteractshRequest) Execute(filePath string) error {
+ router := httprouter.New()
+ router.GET("/", httprouter.Handle(func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
+ value := r.Header.Get("url")
+ if value != "" {
+ if resp, _ := http.DefaultClient.Get(value); resp != nil {
+ resp.Body.Close()
+ }
+ }
+ }))
+ ts := httptest.NewServer(router)
+ defer ts.Close()
+
+ results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)
+ if err != nil {
+ return err
+ }
+ if len(results) != 1 {
+ return errIncorrectResultsCount(results)
+ }
+ return nil
+}
+
type httpGetHeaders struct{}
// Execute executes a test case and returns an error if occurred
diff --git a/v2/cmd/nuclei/main.go b/v2/cmd/nuclei/main.go
index c5170f20..d2f8ebb9 100644
--- a/v2/cmd/nuclei/main.go
+++ b/v2/cmd/nuclei/main.go
@@ -141,7 +141,7 @@ on extensive configurability, massive extensibility and ease of use.`)
flagSet.StringVarP(&options.TraceLogFile, "trace-log", "tlog", "", "file to write sent requests trace log"),
flagSet.BoolVar(&options.Version, "version", false, "show nuclei version"),
flagSet.BoolVarP(&options.Verbose, "verbose", "v", false, "show verbose output"),
- flagSet.BoolVar(&options.VerboseVerbose, "vv", false, "display extra verbose information"),
+ flagSet.BoolVar(&options.VerboseVerbose, "vv", false, "display templates loaded for scan"),
flagSet.BoolVarP(&options.TemplatesVersion, "templates-version", "tv", false, "shows the version of the installed nuclei-templates"),
)
diff --git a/v2/internal/runner/banner.go b/v2/internal/runner/banner.go
index b75f09c3..56bf4ea4 100644
--- a/v2/internal/runner/banner.go
+++ b/v2/internal/runner/banner.go
@@ -20,6 +20,6 @@ func showBanner() {
gologger.Print().Msgf("%s\n", banner)
gologger.Print().Msgf("\t\tprojectdiscovery.io\n\n")
- gologger.Error().Label("WRN").Msgf("Use with caution. You are responsible for your actions.\n")
- gologger.Error().Label("WRN").Msgf("Developers assume no liability and are not responsible for any misuse or damage.\n")
+ gologger.Print().Label("WRN").Msgf("Use with caution. You are responsible for your actions.\n")
+ gologger.Print().Label("WRN").Msgf("Developers assume no liability and are not responsible for any misuse or damage.\n")
}
diff --git a/v2/internal/runner/options.go b/v2/internal/runner/options.go
index 745f15d0..59791fe6 100644
--- a/v2/internal/runner/options.go
+++ b/v2/internal/runner/options.go
@@ -22,11 +22,6 @@ func ParseOptions(options *types.Options) {
// Check if stdin pipe was given
options.Stdin = hasStdin()
- // if VerboseVerbose is set, it implicitly enables the Verbose option as well
- if options.VerboseVerbose {
- options.Verbose = true
- }
-
// Read the inputs and configure the logging
configureOutput(options)
@@ -127,7 +122,7 @@ func isValidURL(urlString string) bool {
// configureOutput configures the output logging levels to be displayed on the screen
func configureOutput(options *types.Options) {
// If the user desires verbose output, show verbose output
- if options.Verbose || options.VerboseVerbose {
+ if options.Verbose {
gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose)
}
if options.Debug {
diff --git a/v2/pkg/catalog/config/config.go b/v2/pkg/catalog/config/config.go
index 41ce5361..91b153cf 100644
--- a/v2/pkg/catalog/config/config.go
+++ b/v2/pkg/catalog/config/config.go
@@ -26,7 +26,7 @@ type Config struct {
const nucleiConfigFilename = ".templates-config.json"
// Version is the current version of nuclei
-const Version = `2.5.3-dev`
+const Version = `2.5.4-dev`
func getConfigDetails() (string, error) {
homeDir, err := os.UserHomeDir()
diff --git a/v2/pkg/parsers/parser.go b/v2/pkg/parsers/parser.go
index b65cb79e..7cd0c7b9 100644
--- a/v2/pkg/parsers/parser.go
+++ b/v2/pkg/parsers/parser.go
@@ -5,6 +5,7 @@ import (
"io/ioutil"
"os"
"regexp"
+ "strings"
"gopkg.in/yaml.v2"
@@ -17,7 +18,10 @@ import (
"github.com/projectdiscovery/nuclei/v2/pkg/utils/stats"
)
-const mandatoryFieldMissingTemplate = "mandatory '%s' field is missing"
+const (
+ mandatoryFieldMissingTemplate = "mandatory '%s' field is missing"
+ invalidFieldFormatTemplate = "invalid field format for '%s' (allowed format is %s)"
+)
// LoadTemplate returns true if the template is valid and matches the filtering criteria.
func LoadTemplate(templatePath string, tagFilter *filter.TagFilter, extraTags []string) (bool, error) {
@@ -30,12 +34,12 @@ func LoadTemplate(templatePath string, tagFilter *filter.TagFilter, extraTags []
return false, nil
}
- templateInfo := template.Info
- if validationError := validateMandatoryInfoFields(&templateInfo); validationError != nil {
+ if validationError := validateTemplateFields(template); validationError != nil {
+ stats.Increment(SyntaxErrorStats)
return false, validationError
}
- return isTemplateInfoMetadataMatch(tagFilter, &templateInfo, extraTags)
+ return isTemplateInfoMetadataMatch(tagFilter, &template.Info, extraTags)
}
// LoadWorkflow returns true if the workflow is valid and matches the filtering criteria.
@@ -45,10 +49,8 @@ func LoadWorkflow(templatePath string) (bool, error) {
return false, templateParseError
}
- templateInfo := template.Info
-
if len(template.Workflows) > 0 {
- if validationError := validateMandatoryInfoFields(&templateInfo); validationError != nil {
+ if validationError := validateTemplateFields(template); validationError != nil {
return false, validationError
}
return true, nil
@@ -71,18 +73,29 @@ func isTemplateInfoMetadataMatch(tagFilter *filter.TagFilter, templateInfo *mode
return match, err
}
-func validateMandatoryInfoFields(info *model.Info) error {
- if info == nil {
- return fmt.Errorf(mandatoryFieldMissingTemplate, "info")
- }
+func validateTemplateFields(template *templates.Template) error {
+ info := template.Info
+
+ var errors []string
if utils.IsBlank(info.Name) {
- return fmt.Errorf(mandatoryFieldMissingTemplate, "name")
+ errors = append(errors, fmt.Sprintf(mandatoryFieldMissingTemplate, "name"))
}
if info.Authors.IsEmpty() {
- return fmt.Errorf(mandatoryFieldMissingTemplate, "author")
+ errors = append(errors, fmt.Sprintf(mandatoryFieldMissingTemplate, "author"))
}
+
+ if template.ID == "" {
+ errors = append(errors, fmt.Sprintf(mandatoryFieldMissingTemplate, "id"))
+ } else if !templateIDRegexp.MatchString(template.ID) {
+ errors = append(errors, fmt.Sprintf(invalidFieldFormatTemplate, "id", templateIDRegexp.String()))
+ }
+
+ if len(errors) > 0 {
+ return fmt.Errorf(strings.Join(errors, ", "))
+ }
+
return nil
}
@@ -90,6 +103,7 @@ var (
parsedTemplatesCache *cache.Templates
ShouldValidate bool
fieldErrorRegexp = regexp.MustCompile(`not found in`)
+ templateIDRegexp = regexp.MustCompile(`^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$`)
)
const (
diff --git a/v2/pkg/parsers/parser_test.go b/v2/pkg/parsers/parser_test.go
new file mode 100644
index 00000000..ef74a317
--- /dev/null
+++ b/v2/pkg/parsers/parser_test.go
@@ -0,0 +1,110 @@
+package parsers
+
+import (
+ "errors"
+ "fmt"
+ "testing"
+
+ "github.com/projectdiscovery/nuclei/v2/pkg/catalog/loader/filter"
+ "github.com/projectdiscovery/nuclei/v2/pkg/model"
+ "github.com/projectdiscovery/nuclei/v2/pkg/model/types/stringslice"
+ "github.com/projectdiscovery/nuclei/v2/pkg/templates"
+ "github.com/stretchr/testify/require"
+)
+
+func TestLoadTemplate(t *testing.T) {
+ origTemplatesCache := parsedTemplatesCache
+ defer func() { parsedTemplatesCache = origTemplatesCache }()
+
+ tt := []struct {
+ name string
+ template *templates.Template
+ templateErr error
+
+ expectedErr error
+ }{
+ {
+ name: "valid",
+ template: &templates.Template{
+ ID: "CVE-2021-27330",
+ Info: model.Info{
+ Name: "Valid template",
+ Authors: stringslice.StringSlice{Value: "Author"},
+ },
+ },
+ },
+ {
+ name: "emptyTemplate",
+ template: &templates.Template{},
+ expectedErr: errors.New("mandatory 'name' field is missing, mandatory 'author' field is missing, mandatory 'id' field is missing"),
+ },
+ {
+ name: "emptyNameWithInvalidID",
+ template: &templates.Template{
+ ID: "invalid id",
+ Info: model.Info{
+ Authors: stringslice.StringSlice{Value: "Author"},
+ },
+ },
+ expectedErr: errors.New("mandatory 'name' field is missing, invalid field format for 'id' (allowed format is ^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$)"),
+ },
+ }
+
+ for _, tc := range tt {
+ t.Run(tc.name, func(t *testing.T) {
+ parsedTemplatesCache.Store(tc.name, tc.template, tc.templateErr)
+
+ tagFilter := filter.New(&filter.Config{})
+ success, err := LoadTemplate(tc.name, tagFilter, nil)
+ if tc.expectedErr == nil {
+ require.NoError(t, err)
+ require.True(t, success)
+ } else {
+ require.Equal(t, tc.expectedErr, err)
+ require.False(t, success)
+ }
+ })
+ }
+
+ t.Run("invalidTemplateID", func(t *testing.T) {
+ tt := []struct {
+ id string
+ success bool
+ }{
+ {id: "A-B-C", success: true},
+ {id: "A-B-C-1", success: true},
+ {id: "CVE_2021_27330", success: true},
+ {id: "ABC DEF", success: false},
+ {id: "_-__AAA_", success: false},
+ {id: " CVE-2021-27330", success: false},
+ {id: "CVE-2021-27330 ", success: false},
+ {id: "CVE-2021-27330-", success: false},
+ {id: "-CVE-2021-27330-", success: false},
+ {id: "CVE-2021--27330", success: false},
+ {id: "CVE-2021+27330", success: false},
+ }
+ for i, tc := range tt {
+ name := fmt.Sprintf("regexp%d", i)
+ t.Run(name, func(t *testing.T) {
+ template := &templates.Template{
+ ID: tc.id,
+ Info: model.Info{
+ Name: "Valid template",
+ Authors: stringslice.StringSlice{Value: "Author"},
+ },
+ }
+ parsedTemplatesCache.Store(name, template, nil)
+
+ tagFilter := filter.New(&filter.Config{})
+ success, err := LoadTemplate(name, tagFilter, nil)
+ if tc.success {
+ require.NoError(t, err)
+ require.True(t, success)
+ } else {
+ require.Equal(t, errors.New("invalid field format for 'id' (allowed format is ^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$)"), err)
+ require.False(t, success)
+ }
+ })
+ }
+ })
+}
diff --git a/v2/pkg/protocols/headless/engine/http_client.go b/v2/pkg/protocols/headless/engine/http_client.go
index b4747d54..4c53ebee 100644
--- a/v2/pkg/protocols/headless/engine/http_client.go
+++ b/v2/pkg/protocols/headless/engine/http_client.go
@@ -1,13 +1,18 @@
package engine
import (
+ "context"
"crypto/tls"
+ "fmt"
+ "net"
"net/http"
+ "net/http/cookiejar"
"net/url"
"time"
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate"
"github.com/projectdiscovery/nuclei/v2/pkg/types"
+ "golang.org/x/net/proxy"
)
// newhttpClient creates a new http client for headless communication with a timeout
@@ -28,7 +33,35 @@ func newhttpClient(options *types.Options) *http.Client {
if proxyURL, err := url.Parse(options.ProxyURL); err == nil {
transport.Proxy = http.ProxyURL(proxyURL)
}
+ } else if options.ProxySocksURL != "" {
+ var proxyAuth *proxy.Auth
+
+ socksURL, proxyErr := url.Parse(options.ProxySocksURL)
+ if proxyErr == nil {
+ proxyAuth = &proxy.Auth{}
+ proxyAuth.User = socksURL.User.Username()
+ proxyAuth.Password, _ = socksURL.User.Password()
+ }
+ dialer, proxyErr := proxy.SOCKS5("tcp", fmt.Sprintf("%s:%s", socksURL.Hostname(), socksURL.Port()), proxyAuth, proxy.Direct)
+ dc := dialer.(interface {
+ DialContext(ctx context.Context, network, addr string) (net.Conn, error)
+ })
+ if proxyErr == nil {
+ transport.DialContext = dc.DialContext
+ }
}
- return &http.Client{Transport: transport, Timeout: time.Duration(options.Timeout*3) * time.Second}
+ jar, _ := cookiejar.New(nil)
+
+ httpclient := &http.Client{
+ Transport: transport,
+ Timeout: time.Duration(options.Timeout*3) * time.Second,
+ Jar: jar,
+ CheckRedirect: func(req *http.Request, via []*http.Request) error {
+ // the browser should follow redirects not us
+ return http.ErrUseLastResponse
+ },
+ }
+
+ return httpclient
}
diff --git a/v2/pkg/protocols/headless/engine/rules.go b/v2/pkg/protocols/headless/engine/rules.go
index a254902e..a802a64a 100644
--- a/v2/pkg/protocols/headless/engine/rules.go
+++ b/v2/pkg/protocols/headless/engine/rules.go
@@ -8,6 +8,9 @@ import (
// routingRuleHandler handles proxy rule for actions related to request/response modification
func (p *Page) routingRuleHandler(ctx *rod.Hijack) {
+ // usually browsers don't use chunked transfer encoding so we set the content-length nevertheless
+ ctx.Request.Req().ContentLength = int64(len(ctx.Request.Body()))
+
for _, rule := range p.rules {
if rule.Part != "request" {
continue
diff --git a/v2/pkg/protocols/http/request.go b/v2/pkg/protocols/http/request.go
index 1a619c17..3451e5f3 100644
--- a/v2/pkg/protocols/http/request.go
+++ b/v2/pkg/protocols/http/request.go
@@ -384,7 +384,7 @@ func (request *Request) executeRequest(reqURL string, generatedRequest *generate
}()
var curlCommand string
- if !request.Unsafe && resp != nil && generatedRequest.request != nil {
+ if !request.Unsafe && resp != nil && generatedRequest.request != nil && resp.Request != nil {
bodyBytes, _ := generatedRequest.request.BodyBytes()
resp.Request.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes))
command, _ := http2curl.GetCurlCommand(resp.Request)
diff --git a/v2/pkg/protocols/network/network.go b/v2/pkg/protocols/network/network.go
index a5274eff..12094ae6 100644
--- a/v2/pkg/protocols/network/network.go
+++ b/v2/pkg/protocols/network/network.go
@@ -60,6 +60,13 @@ type Request struct {
// examples:
// - value: "2048"
ReadSize int `yaml:"read-size,omitempty" jsonschema:"title=size of network response to read,description=Size of response to read at the end. Default is 1024 bytes"`
+ // description: |
+ // ReadAll determines if the data stream should be read till the end regardless of the size
+ //
+ // Default value for read-all is false.
+ // examples:
+ // - value: false
+ ReadAll bool `yaml:"read-all,omitempty" jsonschema:"title=read all response stream,description=Read all response stream till the server stops sending"`
// description: |
// SelfContained specifies if the request is self contained.
diff --git a/v2/pkg/protocols/network/request.go b/v2/pkg/protocols/network/request.go
index acbbac9a..66da31e9 100644
--- a/v2/pkg/protocols/network/request.go
+++ b/v2/pkg/protocols/network/request.go
@@ -6,6 +6,7 @@ import (
"io"
"net"
"net/url"
+ "os"
"strings"
"time"
@@ -200,13 +201,48 @@ func (request *Request) executeRequestWithPayloads(actualAddress, address, input
if request.ReadSize != 0 {
bufferSize = request.ReadSize
}
- final := make([]byte, bufferSize)
- n, err := conn.Read(final)
- if err != nil && err != io.EOF {
- request.options.Output.Request(request.options.TemplateID, address, "network", err)
- return errors.Wrap(err, "could not read from server")
+
+ var (
+ final []byte
+ n int
+ )
+
+ if request.ReadAll {
+ readInterval := time.NewTimer(time.Second * 1)
+ // stop the timer and drain the channel
+ closeTimer := func(t *time.Timer) {
+ if !t.Stop() {
+ <-t.C
+ }
+ }
+ read_socket:
+ for {
+ select {
+ case <-readInterval.C:
+ closeTimer(readInterval)
+ break read_socket
+ default:
+ buf := make([]byte, bufferSize)
+ nBuf, err := conn.Read(buf)
+ if err != nil && !os.IsTimeout(err) {
+ request.options.Output.Request(request.options.TemplateID, address, "network", err)
+ closeTimer(readInterval)
+ return errors.Wrap(err, "could not read from server")
+ }
+ responseBuilder.Write(buf[:nBuf])
+ final = append(final, buf...)
+ n += nBuf
+ }
+ }
+ } else {
+ final = make([]byte, bufferSize)
+ n, err = conn.Read(final)
+ if err != nil && err != io.EOF {
+ request.options.Output.Request(request.options.TemplateID, address, "network", err)
+ return errors.Wrap(err, "could not read from server")
+ }
+ responseBuilder.Write(final[:n])
}
- responseBuilder.Write(final[:n])
response := responseBuilder.String()
outputEvent := request.responseToDSLMap(reqBuilder.String(), string(final[:n]), response, input, actualAddress)
diff --git a/v2/pkg/reporting/trackers/github/github.go b/v2/pkg/reporting/trackers/github/github.go
index 672f62ed..8d6cdb86 100644
--- a/v2/pkg/reporting/trackers/github/github.go
+++ b/v2/pkg/reporting/trackers/github/github.go
@@ -58,6 +58,9 @@ func New(options *Options) (*Integration, error) {
if err != nil {
return nil, errors.Wrap(err, "could not parse custom baseurl")
}
+ if !strings.HasSuffix(parsed.Path, "/") {
+ parsed.Path += "/"
+ }
client.BaseURL = parsed
}
return &Integration{client: client, options: options}, nil
diff --git a/v2/pkg/templates/templates.go b/v2/pkg/templates/templates.go
index a74b6737..85213aa0 100644
--- a/v2/pkg/templates/templates.go
+++ b/v2/pkg/templates/templates.go
@@ -29,7 +29,7 @@ type Template struct {
// examples:
// - name: ID Example
// value: "\"CVE-2021-19520\""
- ID string `yaml:"id" jsonschema:"title=id of the template,description=The Unique ID for the template,example=cve-2021-19520"`
+ ID string `yaml:"id" jsonschema:"title=id of the template,description=The Unique ID for the template,example=cve-2021-19520,pattern=^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$"`
// description: |
// Info contains metadata information about the template.
// examples:
diff --git a/v2/pkg/templates/templates_doc.go b/v2/pkg/templates/templates_doc.go
index 0b19e675..d6042dfb 100644
--- a/v2/pkg/templates/templates_doc.go
+++ b/v2/pkg/templates/templates_doc.go
@@ -843,7 +843,7 @@ func init() {
FieldName: "network",
},
}
- NETWORKRequestDoc.Fields = make([]encoder.Doc, 9)
+ NETWORKRequestDoc.Fields = make([]encoder.Doc, 10)
NETWORKRequestDoc.Fields[0].Name = "id"
NETWORKRequestDoc.Fields[0].Type = "string"
NETWORKRequestDoc.Fields[0].Note = ""
@@ -883,22 +883,29 @@ func init() {
NETWORKRequestDoc.Fields[5].Comments[encoder.LineComment] = "ReadSize is the size of response to read at the end"
NETWORKRequestDoc.Fields[5].AddExample("", 2048)
- NETWORKRequestDoc.Fields[6].Name = "matchers"
- NETWORKRequestDoc.Fields[6].Type = "[]matchers.Matcher"
+ NETWORKRequestDoc.Fields[6].Name = "read-all"
+ NETWORKRequestDoc.Fields[6].Type = "bool"
NETWORKRequestDoc.Fields[6].Note = ""
- NETWORKRequestDoc.Fields[6].Description = "Matchers contains the detection mechanism for the request to identify\nwhether the request was successful by doing pattern matching\non request/responses.\n\nMultiple matchers can be combined with `matcher-condition` flag\nwhich accepts either `and` or `or` as argument."
- NETWORKRequestDoc.Fields[6].Comments[encoder.LineComment] = "Matchers contains the detection mechanism for the request to identify"
- NETWORKRequestDoc.Fields[7].Name = "extractors"
- NETWORKRequestDoc.Fields[7].Type = "[]extractors.Extractor"
+ NETWORKRequestDoc.Fields[6].Description = "ReadAll determines if the data stream should be read till the end regardless of the size\n\nDefault value for read-all is false."
+ NETWORKRequestDoc.Fields[6].Comments[encoder.LineComment] = "ReadAll determines if the data stream should be read till the end regardless of the size"
+
+ NETWORKRequestDoc.Fields[6].AddExample("", false)
+ NETWORKRequestDoc.Fields[7].Name = "matchers"
+ NETWORKRequestDoc.Fields[7].Type = "[]matchers.Matcher"
NETWORKRequestDoc.Fields[7].Note = ""
- NETWORKRequestDoc.Fields[7].Description = "Extractors contains the extraction mechanism for the request to identify\nand extract parts of the response."
- NETWORKRequestDoc.Fields[7].Comments[encoder.LineComment] = "Extractors contains the extraction mechanism for the request to identify"
- NETWORKRequestDoc.Fields[8].Name = "matchers-condition"
- NETWORKRequestDoc.Fields[8].Type = "string"
+ NETWORKRequestDoc.Fields[7].Description = "Matchers contains the detection mechanism for the request to identify\nwhether the request was successful by doing pattern matching\non request/responses.\n\nMultiple matchers can be combined with `matcher-condition` flag\nwhich accepts either `and` or `or` as argument."
+ NETWORKRequestDoc.Fields[7].Comments[encoder.LineComment] = "Matchers contains the detection mechanism for the request to identify"
+ NETWORKRequestDoc.Fields[8].Name = "extractors"
+ NETWORKRequestDoc.Fields[8].Type = "[]extractors.Extractor"
NETWORKRequestDoc.Fields[8].Note = ""
- NETWORKRequestDoc.Fields[8].Description = "MatchersCondition is the condition between the matchers. Default is OR."
- NETWORKRequestDoc.Fields[8].Comments[encoder.LineComment] = "MatchersCondition is the condition between the matchers. Default is OR."
- NETWORKRequestDoc.Fields[8].Values = []string{
+ NETWORKRequestDoc.Fields[8].Description = "Extractors contains the extraction mechanism for the request to identify\nand extract parts of the response."
+ NETWORKRequestDoc.Fields[8].Comments[encoder.LineComment] = "Extractors contains the extraction mechanism for the request to identify"
+ NETWORKRequestDoc.Fields[9].Name = "matchers-condition"
+ NETWORKRequestDoc.Fields[9].Type = "string"
+ NETWORKRequestDoc.Fields[9].Note = ""
+ NETWORKRequestDoc.Fields[9].Description = "MatchersCondition is the condition between the matchers. Default is OR."
+ NETWORKRequestDoc.Fields[9].Comments[encoder.LineComment] = "MatchersCondition is the condition between the matchers. Default is OR."
+ NETWORKRequestDoc.Fields[9].Values = []string{
"and",
"or",
}