mirror of https://github.com/daffainfo/nuclei.git
Merge pull request #1066 from projectdiscovery/more-protocols
Added websocket and SSL protocol support + engine refactordev
commit
304b63907e
|
@ -0,0 +1,16 @@
|
|||
id: basic-request
|
||||
|
||||
info:
|
||||
name: Basic Request
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
websocket:
|
||||
- address: '{{Scheme}}://{{Hostname}}'
|
||||
inputs:
|
||||
- data: hello
|
||||
matchers:
|
||||
- type: word
|
||||
words:
|
||||
- world
|
||||
part: response
|
|
@ -0,0 +1,16 @@
|
|||
id: basic-cswsh-request
|
||||
|
||||
info:
|
||||
name: Basic cswsh Request
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
websocket:
|
||||
- address: '{{Scheme}}://{{Hostname}}'
|
||||
headers:
|
||||
Origin: 'http://evil.com'
|
||||
matchers:
|
||||
- type: word
|
||||
words:
|
||||
- true
|
||||
part: success
|
|
@ -0,0 +1,16 @@
|
|||
id: basic-nocswsh-request
|
||||
|
||||
info:
|
||||
name: Basic Non-Vulnerable cswsh Request
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
websocket:
|
||||
- address: '{{Scheme}}://{{Hostname}}'
|
||||
headers:
|
||||
Origin: 'http://evil.com'
|
||||
matchers:
|
||||
- type: word
|
||||
words:
|
||||
- true
|
||||
part: success
|
|
@ -0,0 +1,16 @@
|
|||
id: basic-request-path
|
||||
|
||||
info:
|
||||
name: Basic Request Path
|
||||
author: pdteam
|
||||
severity: info
|
||||
|
||||
websocket:
|
||||
- address: '{{Scheme}}://{{Hostname}}'
|
||||
inputs:
|
||||
- data: hello
|
||||
matchers:
|
||||
- type: word
|
||||
words:
|
||||
- world
|
||||
part: response
|
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/logrusorgru/aurora"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
var dnsTestCases = map[string]testutils.TestCase{
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
var httpTestcases = map[string]testutils.TestCase{
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -22,11 +22,12 @@ func main() {
|
|||
failed := aurora.Red("[✘]").String()
|
||||
|
||||
protocolTests := map[string]map[string]testutils.TestCase{
|
||||
"http": httpTestcases,
|
||||
"network": networkTestcases,
|
||||
"dns": dnsTestCases,
|
||||
"workflow": workflowTestcases,
|
||||
"loader": loaderTestcases,
|
||||
"http": httpTestcases,
|
||||
"network": networkTestcases,
|
||||
"dns": dnsTestCases,
|
||||
"workflow": workflowTestcases,
|
||||
"loader": loaderTestcases,
|
||||
"websocket": websocketTestCases,
|
||||
}
|
||||
for proto, tests := range protocolTests {
|
||||
if protocol == "" || protocol == proto {
|
||||
|
|
|
@ -2,12 +2,13 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
var loaderTestcases = map[string]testutils.TestCase{
|
||||
|
|
|
@ -3,7 +3,7 @@ package main
|
|||
import (
|
||||
"net"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
var networkTestcases = map[string]testutils.TestCase{
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/gobwas/ws/wsutil"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
var websocketTestCases = map[string]testutils.TestCase{
|
||||
"websocket/basic.yaml": &websocketBasic{},
|
||||
"websocket/cswsh.yaml": &websocketCswsh{},
|
||||
"websocket/no-cswsh.yaml": &websocketNoCswsh{},
|
||||
"websocket/path.yaml": &websocketWithPath{},
|
||||
}
|
||||
|
||||
type websocketBasic struct{}
|
||||
|
||||
// Execute executes a test case and returns an error if occurred
|
||||
func (h *websocketBasic) Execute(filePath string) error {
|
||||
connHandler := func(conn net.Conn) {
|
||||
for {
|
||||
msg, op, _ := wsutil.ReadClientData(conn)
|
||||
if string(msg) != string("hello") {
|
||||
return
|
||||
}
|
||||
_ = wsutil.WriteServerMessage(conn, op, []byte("world"))
|
||||
}
|
||||
}
|
||||
originValidate := func(origin string) bool {
|
||||
return true
|
||||
}
|
||||
ts := testutils.NewWebsocketServer("", connHandler, originValidate)
|
||||
defer ts.Close()
|
||||
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, strings.ReplaceAll(ts.URL, "http", "ws"), debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(results) != 1 {
|
||||
return errIncorrectResultsCount(results)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type websocketCswsh struct{}
|
||||
|
||||
// Execute executes a test case and returns an error if occurred
|
||||
func (h *websocketCswsh) Execute(filePath string) error {
|
||||
connHandler := func(conn net.Conn) {
|
||||
|
||||
}
|
||||
originValidate := func(origin string) bool {
|
||||
return true
|
||||
}
|
||||
ts := testutils.NewWebsocketServer("", connHandler, originValidate)
|
||||
defer ts.Close()
|
||||
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, strings.ReplaceAll(ts.URL, "http", "ws"), debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(results) != 1 {
|
||||
return errIncorrectResultsCount(results)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type websocketNoCswsh struct{}
|
||||
|
||||
// Execute executes a test case and returns an error if occurred
|
||||
func (h *websocketNoCswsh) Execute(filePath string) error {
|
||||
connHandler := func(conn net.Conn) {
|
||||
|
||||
}
|
||||
originValidate := func(origin string) bool {
|
||||
return origin == "https://google.com"
|
||||
}
|
||||
ts := testutils.NewWebsocketServer("", connHandler, originValidate)
|
||||
defer ts.Close()
|
||||
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, strings.ReplaceAll(ts.URL, "http", "ws"), debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(results) != 0 {
|
||||
return errIncorrectResultsCount(results)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type websocketWithPath struct{}
|
||||
|
||||
// Execute executes a test case and returns an error if occurred
|
||||
func (h *websocketWithPath) Execute(filePath string) error {
|
||||
connHandler := func(conn net.Conn) {
|
||||
|
||||
}
|
||||
originValidate := func(origin string) bool {
|
||||
return origin == "https://google.com"
|
||||
}
|
||||
ts := testutils.NewWebsocketServer("/test", connHandler, originValidate)
|
||||
defer ts.Close()
|
||||
|
||||
results, err := testutils.RunNucleiTemplateAndGetResults(filePath, strings.ReplaceAll(ts.URL, "http", "ws"), debug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(results) != 0 {
|
||||
return errIncorrectResultsCount(results)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
var workflowTestcases = map[string]testutils.TestCase{
|
||||
|
|
|
@ -73,7 +73,7 @@ on extensive configurability, massive extensibility and ease of use.`)
|
|||
flagSet.VarP(&options.ExcludeSeverities, "exclude-severity", "es", fmt.Sprintf("Templates to exclude based on severity. Possible values: %s", severity.GetSupportedSeverities().String())),
|
||||
flagSet.VarP(&options.Protocols, "type", "pt", fmt.Sprintf("protocol types to be executed. Possible values: %s", templateTypes.GetSupportedProtocolTypes())),
|
||||
flagSet.VarP(&options.ExcludeProtocols, "exclude-type", "ept", fmt.Sprintf("protocol types to not be executed. Possible values: %s", templateTypes.GetSupportedProtocolTypes())),
|
||||
flagSet.NormalizedStringSliceVarP(&options.Author, "author", "a", []string{}, "execute templates that are (co-)created by the specified authors"),
|
||||
flagSet.NormalizedStringSliceVarP(&options.Authors, "author", "a", []string{}, "execute templates that are (co-)created by the specified authors"),
|
||||
)
|
||||
|
||||
createGroup(flagSet, "output", "Output",
|
||||
|
@ -118,6 +118,8 @@ on extensive configurability, massive extensibility and ease of use.`)
|
|||
flagSet.IntVarP(&options.RateLimitMinute, "rate-limit-minute", "rlm", 0, "maximum number of requests to send per minute"),
|
||||
flagSet.IntVarP(&options.BulkSize, "bulk-size", "bs", 25, "maximum number of hosts to be analyzed in parallel per template"),
|
||||
flagSet.IntVarP(&options.TemplateThreads, "concurrency", "c", 25, "maximum number of templates to be executed in parallel"),
|
||||
flagSet.IntVarP(&options.HeadlessBulkSize, "headless-bulk-size", "hbs", 10, "maximum number of headless hosts to be analyzed in parallel per template"),
|
||||
flagSet.IntVarP(&options.HeadlessTemplateThreads, "headless-concurrency", "hc", 10, "maximum number of headless templates to be executed in parallel"),
|
||||
)
|
||||
|
||||
createGroup(flagSet, "optimization", "Optimizations",
|
||||
|
|
56
v2/go.mod
56
v2/go.mod
|
@ -5,81 +5,76 @@ go 1.17
|
|||
require (
|
||||
github.com/Ice3man543/nvd v1.0.8
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible
|
||||
github.com/akrylysov/pogreb v0.10.1 // indirect
|
||||
github.com/alecthomas/jsonschema v0.0.0-20210818095345-1014919a589c
|
||||
github.com/alecthomas/jsonschema v0.0.0-20211022214203-8b29eab41725
|
||||
github.com/andygrunwald/go-jira v1.14.0
|
||||
github.com/antchfx/htmlquery v1.2.3
|
||||
github.com/antchfx/htmlquery v1.2.4
|
||||
github.com/apex/log v1.9.0
|
||||
github.com/blang/semver v3.5.1+incompatible
|
||||
github.com/bluele/gcache v0.0.2
|
||||
github.com/c4milo/unpackit v0.1.0 // indirect
|
||||
github.com/corpix/uarand v0.1.1
|
||||
github.com/go-rod/rod v0.101.7
|
||||
github.com/go-rod/rod v0.101.8
|
||||
github.com/gobwas/ws v1.1.0
|
||||
github.com/google/go-github v17.0.0+incompatible
|
||||
github.com/gosuri/uilive v0.0.4 // indirect
|
||||
github.com/gosuri/uiprogress v0.0.1 // indirect
|
||||
github.com/itchyny/gojq v0.12.4
|
||||
github.com/itchyny/gojq v0.12.5
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
github.com/karlseguin/ccache v2.0.3+incompatible
|
||||
github.com/karrick/godirwalk v1.16.1
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/miekg/dns v1.1.43
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/owenrumney/go-sarif v1.0.11
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/projectdiscovery/clistats v0.0.8
|
||||
github.com/projectdiscovery/fastdialer v0.0.13-0.20210917073912-cad93d88e69e
|
||||
github.com/projectdiscovery/cryptoutil v0.0.0-20210805184155-b5d2512f9345
|
||||
github.com/projectdiscovery/fastdialer v0.0.13
|
||||
github.com/projectdiscovery/filekv v0.0.0-20210915124239-3467ef45dd08
|
||||
github.com/projectdiscovery/fileutil v0.0.0-20210928100737-cab279c5d4b5
|
||||
github.com/projectdiscovery/goflags v0.0.8-0.20211007103353-9b9229e8a240
|
||||
github.com/projectdiscovery/goflags v0.0.8-0.20211028121123-edf02bc05b1a
|
||||
github.com/projectdiscovery/gologger v1.1.4
|
||||
github.com/projectdiscovery/hmap v0.0.2-0.20210917080408-0fd7bd286bfa
|
||||
github.com/projectdiscovery/interactsh v0.0.6
|
||||
github.com/projectdiscovery/nuclei-updatecheck-api v0.0.0-20210914222811-0a072d262f77
|
||||
github.com/projectdiscovery/nuclei-updatecheck-api v0.0.0-20211006155443-c0a8d610a4df
|
||||
github.com/projectdiscovery/rawhttp v0.0.7
|
||||
github.com/projectdiscovery/retryabledns v1.0.13-0.20210916165024-76c5b76fd59a
|
||||
github.com/projectdiscovery/retryablehttp-go v1.0.2
|
||||
github.com/projectdiscovery/stringsutil v0.0.0-20211013053023-e7b2e104d80d
|
||||
github.com/projectdiscovery/stringsutil v0.0.0-20210830151154-f567170afdd9
|
||||
github.com/projectdiscovery/yamldoc-go v1.0.2
|
||||
github.com/remeh/sizedwaitgroup v1.0.0
|
||||
github.com/rs/xid v1.3.0
|
||||
github.com/segmentio/ksuid v1.0.4
|
||||
github.com/shirou/gopsutil/v3 v3.21.7
|
||||
github.com/shirou/gopsutil/v3 v3.21.9
|
||||
github.com/spaolacci/murmur3 v1.1.0
|
||||
github.com/spf13/cast v1.4.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/syndtr/goleveldb v1.0.0
|
||||
github.com/tj/go-update v2.2.5-0.20200519121640-62b4b798fd68+incompatible
|
||||
github.com/valyala/fasttemplate v1.2.1
|
||||
github.com/xanzy/go-gitlab v0.50.3
|
||||
github.com/weppos/publicsuffix-go v0.15.1-0.20210928183822-5ee35905bd95
|
||||
github.com/xanzy/go-gitlab v0.51.1
|
||||
github.com/ysmood/gson v0.6.4 // indirect
|
||||
github.com/ysmood/leakless v0.7.0 // indirect
|
||||
go.uber.org/atomic v1.9.0
|
||||
go.uber.org/multierr v1.7.0
|
||||
go.uber.org/ratelimit v0.2.0
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8
|
||||
golang.org/x/oauth2 v0.0.0-20210817223510-7df4dd6e12ab
|
||||
golang.org/x/sys v0.0.0-20210915083310-ed5796bab164 // indirect
|
||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1
|
||||
golang.org/x/text v0.3.7
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
moul.io/http2curl v1.0.0
|
||||
)
|
||||
|
||||
require github.com/weppos/publicsuffix-go v0.15.0
|
||||
|
||||
require (
|
||||
git.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a // indirect
|
||||
github.com/PuerkitoBio/goquery v1.6.0 // indirect
|
||||
github.com/StackExchange/wmi v1.2.1 // indirect
|
||||
github.com/akrylysov/pogreb v0.10.1 // indirect
|
||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
|
||||
github.com/andybalholm/cascadia v1.1.0 // indirect
|
||||
github.com/antchfx/xpath v1.1.6 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/antchfx/xpath v1.2.0 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.2.0 // indirect
|
||||
github.com/bits-and-blooms/bloom/v3 v3.0.1 // indirect
|
||||
github.com/c4milo/unpackit v0.1.0 // indirect
|
||||
github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
|
@ -87,13 +82,16 @@ require (
|
|||
github.com/eggsampler/acme/v3 v3.2.1 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.5 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.1+incompatible // indirect
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/go-querystring v1.0.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/gosuri/uilive v0.0.4 // indirect
|
||||
github.com/gosuri/uiprogress v0.0.1 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.6.8 // indirect
|
||||
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect
|
||||
|
@ -103,19 +101,17 @@ require (
|
|||
github.com/klauspost/compress v1.13.6 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.15 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/projectdiscovery/blackrock v0.0.0-20210415162320-b38689ae3a2e // indirect
|
||||
github.com/projectdiscovery/cryptoutil v0.0.0-20210805184155-b5d2512f9345 // indirect
|
||||
github.com/projectdiscovery/iputil v0.0.0-20210804143329-3a30fcde43f3 // indirect
|
||||
github.com/projectdiscovery/mapcidr v0.0.8 // indirect
|
||||
github.com/projectdiscovery/networkpolicy v0.0.1 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.7 // indirect
|
||||
github.com/tklauser/numcpus v0.2.3 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.9 // indirect
|
||||
github.com/tklauser/numcpus v0.3.0 // indirect
|
||||
github.com/trivago/tgo v1.0.7 // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
|
@ -123,7 +119,9 @@ require (
|
|||
github.com/ysmood/goob v0.3.0 // indirect
|
||||
github.com/zclconf/go-cty v1.8.4 // indirect
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
golang.org/x/sys v0.0.0-20210915083310-ed5796bab164 // indirect
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/corvus-ch/zbase32.v1 v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
|
|
72
v2/go.sum
72
v2/go.sum
|
@ -67,8 +67,9 @@ github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY
|
|||
github.com/akrylysov/pogreb v0.10.0/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI=
|
||||
github.com/akrylysov/pogreb v0.10.1 h1:FqlR8VR7uCbJdfUob916tPM+idpKgeESDXOA1K0DK4w=
|
||||
github.com/akrylysov/pogreb v0.10.1/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI=
|
||||
github.com/alecthomas/jsonschema v0.0.0-20210818095345-1014919a589c h1:oJsq4z4xKgZWWOhrSZuLZ5KyYfRFytddLL1E5+psfIY=
|
||||
github.com/alecthomas/jsonschema v0.0.0-20210818095345-1014919a589c/go.mod h1:/n6+1/DWPltRLWL/VKyUxg6tzsl5kHUCcraimt4vr60=
|
||||
github.com/alecthomas/jsonschema v0.0.0-20211022214203-8b29eab41725 h1:NjwIgLQlD46o79bheVG4SCdRnnOz4XtgUN1WABX5DLA=
|
||||
github.com/alecthomas/jsonschema v0.0.0-20211022214203-8b29eab41725/go.mod h1:/n6+1/DWPltRLWL/VKyUxg6tzsl5kHUCcraimt4vr60=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
|
@ -79,10 +80,12 @@ github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5z
|
|||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/andygrunwald/go-jira v1.14.0 h1:7GT/3qhar2dGJ0kq8w0d63liNyHOnxZsUZ9Pe4+AKBI=
|
||||
github.com/andygrunwald/go-jira v1.14.0/go.mod h1:KMo2f4DgMZA1C9FdImuLc04x4WQhn5derQpnsuBFgqE=
|
||||
github.com/antchfx/htmlquery v1.2.3 h1:sP3NFDneHx2stfNXCKbhHFo8XgNjCACnU/4AO5gWz6M=
|
||||
github.com/antchfx/htmlquery v1.2.3/go.mod h1:B0ABL+F5irhhMWg54ymEZinzMSi0Kt3I2if0BLYa3V0=
|
||||
github.com/antchfx/xpath v1.1.6 h1:6sVh6hB5T6phw1pFpHRQ+C4bd8sNI+O58flqtg7h0R0=
|
||||
github.com/antchfx/htmlquery v1.2.4 h1:qLteofCMe/KGovBI6SQgmou2QNyedFUW+pE+BpeZ494=
|
||||
github.com/antchfx/htmlquery v1.2.4/go.mod h1:2xO6iu3EVWs7R2JYqBbp8YzG50gj/ofqs5/0VZoDZLc=
|
||||
github.com/antchfx/xpath v1.1.6/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
|
||||
github.com/antchfx/xpath v1.2.0 h1:mbwv7co+x0RwgeGAOHdrKy89GvHaGvxxBtPK0uF9Zr8=
|
||||
github.com/antchfx/xpath v1.2.0/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/apex/log v1.9.0 h1:FHtw/xuaM8AgmvDDTI9fiwoAL25Sq2cxojnZICUU8l0=
|
||||
|
@ -96,14 +99,11 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5
|
|||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
||||
github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
|
@ -232,14 +232,20 @@ github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
|
|||
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-rod/rod v0.91.1/go.mod h1:/W4lcZiCALPD603MnJGIvhtywP3R6yRB9EDfFfsHiiI=
|
||||
github.com/go-rod/rod v0.101.7 h1:kbI5CNvcRhf7feybBln4xDutsM0mbsF0ENNZfKcF6WA=
|
||||
github.com/go-rod/rod v0.101.7/go.mod h1:N/zlT53CfSpq74nb6rOR0K8UF0SPUPBmzBnArrms+mY=
|
||||
github.com/go-rod/rod v0.101.8 h1:oV0O97uwjkCVyAP0hD6K6bBE8FUMIjs0dtF7l6kEBsU=
|
||||
github.com/go-rod/rod v0.101.8/go.mod h1:N/zlT53CfSpq74nb6rOR0K8UF0SPUPBmzBnArrms+mY=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA=
|
||||
github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
|
||||
github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||
github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4=
|
||||
|
@ -328,8 +334,6 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
|
|||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
|
@ -385,8 +389,9 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/
|
|||
github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI=
|
||||
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
|
||||
github.com/itchyny/go-flags v1.5.0/go.mod h1:lenkYuCobuxLBAd/HGFE4LRoW8D3B6iXRQfWYJ+MNbA=
|
||||
github.com/itchyny/gojq v0.12.4 h1:8zgOZWMejEWCLjbF/1mWY7hY7QEARm7dtuhC6Bp4R8o=
|
||||
github.com/itchyny/gojq v0.12.4/go.mod h1:EQUSKgW/YaOxmXpAwGiowFDO4i2Rmtk5+9dFyeiymAg=
|
||||
github.com/itchyny/gojq v0.12.5 h1:6SJ1BQ1VAwJAlIvLSIZmqHP/RUEq3qfVWvsRxrqhsD0=
|
||||
github.com/itchyny/gojq v0.12.5/go.mod h1:3e1hZXv+Kwvdp6V9HXpVrvddiHVApi5EDZwS+zLFeiE=
|
||||
github.com/itchyny/timefmt-go v0.1.3 h1:7M3LGVDsqcd0VZH2U+x393obrzZisp7C0uEe921iRkU=
|
||||
github.com/itchyny/timefmt-go v0.1.3/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A=
|
||||
github.com/jasonlvhit/gocron v0.0.1 h1:qTt5qF3b3srDjeOIR4Le1LfeyvoYzJlYpqvG7tJX5YU=
|
||||
|
@ -479,8 +484,6 @@ github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.
|
|||
github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
||||
github.com/microcosm-cc/bluemonday v1.0.15 h1:J4uN+qPng9rvkBZBoBb8YGR+ijuklIMpSOZZLjYpbeY=
|
||||
github.com/microcosm-cc/bluemonday v1.0.15/go.mod h1:ZLvAzeakRwrGnzQEvstVzVt3ZpqOF2+sdFr0Om+ce30=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
|
@ -573,8 +576,8 @@ github.com/projectdiscovery/cryptoutil v0.0.0-20210805184155-b5d2512f9345 h1:jT6
|
|||
github.com/projectdiscovery/cryptoutil v0.0.0-20210805184155-b5d2512f9345/go.mod h1:clhQmPnt35ziJW1AhJRKyu8aygXCSoyWj6dtmZBRjjc=
|
||||
github.com/projectdiscovery/fastdialer v0.0.12/go.mod h1:RkRbxqDCcCFhfNUbkzBIz/ieD4uda2JuUA4WJ+RLee0=
|
||||
github.com/projectdiscovery/fastdialer v0.0.13-0.20210824195254-0113c1406542/go.mod h1:TuapmLiqtunJOxpM7g0tpTy/TUF/0S+XFyx0B0Wx0DQ=
|
||||
github.com/projectdiscovery/fastdialer v0.0.13-0.20210917073912-cad93d88e69e h1:xMAFYJgRxopAwKrj7HDwMBKJGCGDbHqopS8f959xges=
|
||||
github.com/projectdiscovery/fastdialer v0.0.13-0.20210917073912-cad93d88e69e/go.mod h1:O1l6+vAQy1QRo9FqyuyJ57W3CwpIXXg7oGo14Le6ZYQ=
|
||||
github.com/projectdiscovery/fastdialer v0.0.13 h1:BCe7JsFxRk1kAUQcy4X+9lqEuT7Y6LRSlHXfia03XOo=
|
||||
github.com/projectdiscovery/fastdialer v0.0.13/go.mod h1:Mex24omi3RxrmhA8Ote7rw+6LWMiaBvbJq8CNp0ksII=
|
||||
github.com/projectdiscovery/filekv v0.0.0-20210915124239-3467ef45dd08 h1:NwD1R/du1dqrRKN3SJl9kT6tN3K9puuWFXEvYF2ihew=
|
||||
github.com/projectdiscovery/filekv v0.0.0-20210915124239-3467ef45dd08/go.mod h1:paLCnwV8sL7ppqIwVQodQrk3F6mnWafwTDwRd7ywZwQ=
|
||||
github.com/projectdiscovery/fileutil v0.0.0-20210804142714-ebba15fa53ca/go.mod h1:U+QCpQnX8o2N2w0VUGyAzjM3yBAe4BKedVElxiImsx0=
|
||||
|
@ -582,8 +585,8 @@ github.com/projectdiscovery/fileutil v0.0.0-20210914153648-31f843feaad4/go.mod h
|
|||
github.com/projectdiscovery/fileutil v0.0.0-20210928100737-cab279c5d4b5 h1:2dbm7UhrAKnccZttr78CAmG768sSCd+MBn4ayLVDeqA=
|
||||
github.com/projectdiscovery/fileutil v0.0.0-20210928100737-cab279c5d4b5/go.mod h1:U+QCpQnX8o2N2w0VUGyAzjM3yBAe4BKedVElxiImsx0=
|
||||
github.com/projectdiscovery/goflags v0.0.7/go.mod h1:Jjwsf4eEBPXDSQI2Y+6fd3dBumJv/J1U0nmpM+hy2YY=
|
||||
github.com/projectdiscovery/goflags v0.0.8-0.20211007103353-9b9229e8a240 h1:b7zDUSsgN5f4/IlhKF6RVGsp/NkHIuty0o1YjzAMKUs=
|
||||
github.com/projectdiscovery/goflags v0.0.8-0.20211007103353-9b9229e8a240/go.mod h1:Jjwsf4eEBPXDSQI2Y+6fd3dBumJv/J1U0nmpM+hy2YY=
|
||||
github.com/projectdiscovery/goflags v0.0.8-0.20211028121123-edf02bc05b1a h1:EzwVm8i4zmzqZX55vrDtyfogwHh8AAZ3cWCJe4fEduk=
|
||||
github.com/projectdiscovery/goflags v0.0.8-0.20211028121123-edf02bc05b1a/go.mod h1:Jjwsf4eEBPXDSQI2Y+6fd3dBumJv/J1U0nmpM+hy2YY=
|
||||
github.com/projectdiscovery/gologger v1.0.1/go.mod h1:Ok+axMqK53bWNwDSU1nTNwITLYMXMdZtRc8/y1c7sWE=
|
||||
github.com/projectdiscovery/gologger v1.1.4 h1:qWxGUq7ukHWT849uGPkagPKF3yBPYAsTtMKunQ8O2VI=
|
||||
github.com/projectdiscovery/gologger v1.1.4/go.mod h1:Bhb6Bdx2PV1nMaFLoXNBmHIU85iROS9y1tBuv7T5pMY=
|
||||
|
@ -591,7 +594,6 @@ github.com/projectdiscovery/hmap v0.0.1/go.mod h1:VDEfgzkKQdq7iGTKz8Ooul0NuYHQ8q
|
|||
github.com/projectdiscovery/hmap v0.0.2-0.20210616215655-7b78e7f33d1f/go.mod h1:FH+MS/WNKTXJQtdRn+/Zg5WlKCiMN0Z1QUedUIuM5n8=
|
||||
github.com/projectdiscovery/hmap v0.0.2-0.20210727180307-d63d35146e97/go.mod h1:FH+MS/WNKTXJQtdRn+/Zg5WlKCiMN0Z1QUedUIuM5n8=
|
||||
github.com/projectdiscovery/hmap v0.0.2-0.20210825180603-fca7166c158f/go.mod h1:RLM8b1z2HEq74u5AXN1Lbvfq+1BZWpnTQJcwLnMLA54=
|
||||
github.com/projectdiscovery/hmap v0.0.2-0.20210917073634-bfb0e9c03800/go.mod h1:FH+MS/WNKTXJQtdRn+/Zg5WlKCiMN0Z1QUedUIuM5n8=
|
||||
github.com/projectdiscovery/hmap v0.0.2-0.20210917080408-0fd7bd286bfa h1:9sZWFUAshIa/ea0RKjGRuuZiS5PzYXAFjTRUnSbezr0=
|
||||
github.com/projectdiscovery/hmap v0.0.2-0.20210917080408-0fd7bd286bfa/go.mod h1:lV5f/PNPmCCjCN/dR317/chN9s7VG5h/xcbFfXOz8Fo=
|
||||
github.com/projectdiscovery/interactsh v0.0.4/go.mod h1:PtJrddeBW1/LeOVgTvvnjUl3Hu/17jTkoIi8rXeEODE=
|
||||
|
@ -609,8 +611,8 @@ github.com/projectdiscovery/mapcidr v0.0.8 h1:16U05F2x3o/jSTsxSCY2hCuCs9xOSwVxjo
|
|||
github.com/projectdiscovery/mapcidr v0.0.8/go.mod h1:7CzdUdjuLVI0s33dQ33lWgjg3vPuLFw2rQzZ0RxkT00=
|
||||
github.com/projectdiscovery/networkpolicy v0.0.1 h1:RGRuPlxE8WLFF9tdKSjTsYiTIKHNHW20Kl0nGGiRb1I=
|
||||
github.com/projectdiscovery/networkpolicy v0.0.1/go.mod h1:asvdg5wMy3LPVMGALatebKeOYH5n5fV5RCTv6DbxpIs=
|
||||
github.com/projectdiscovery/nuclei-updatecheck-api v0.0.0-20210914222811-0a072d262f77 h1:SNtAiRRrJtDJJDroaa/bFXt/Tix2LA6+rHRib0ORlJQ=
|
||||
github.com/projectdiscovery/nuclei-updatecheck-api v0.0.0-20210914222811-0a072d262f77/go.mod h1:pxWVDgq88t9dWv4+J2AIaWgY+EqOE1AyfHS0Tn23w4M=
|
||||
github.com/projectdiscovery/nuclei-updatecheck-api v0.0.0-20211006155443-c0a8d610a4df h1:CvTNAUD5JbLMqpMFoGNgfk2gOcN0NC57ICu0+oK84vs=
|
||||
github.com/projectdiscovery/nuclei-updatecheck-api v0.0.0-20211006155443-c0a8d610a4df/go.mod h1:pxWVDgq88t9dWv4+J2AIaWgY+EqOE1AyfHS0Tn23w4M=
|
||||
github.com/projectdiscovery/nuclei/v2 v2.5.1/go.mod h1:sU2qcY0MQFS0CqP1BgkR8ZnUyFhqK0BdnY6bvTKNjXY=
|
||||
github.com/projectdiscovery/rawhttp v0.0.7 h1:5m4peVgjbl7gqDcRYMTVEuX+Xs/nh76ohTkkvufucLg=
|
||||
github.com/projectdiscovery/rawhttp v0.0.7/go.mod h1:PQERZAhAv7yxI/hR6hdDPgK1WTU56l204BweXrBec+0=
|
||||
|
@ -623,9 +625,8 @@ github.com/projectdiscovery/retryablehttp-go v1.0.2 h1:LV1/KAQU+yeWhNVlvveaYFsjB
|
|||
github.com/projectdiscovery/retryablehttp-go v1.0.2/go.mod h1:dx//aY9V247qHdsRf0vdWHTBZuBQ2vm6Dq5dagxrDYI=
|
||||
github.com/projectdiscovery/stringsutil v0.0.0-20210804142656-fd3c28dbaafe/go.mod h1:oTRc18WBv9t6BpaN9XBY+QmG28PUpsyDzRht56Qf49I=
|
||||
github.com/projectdiscovery/stringsutil v0.0.0-20210823090203-2f5f137e8e1d/go.mod h1:oTRc18WBv9t6BpaN9XBY+QmG28PUpsyDzRht56Qf49I=
|
||||
github.com/projectdiscovery/stringsutil v0.0.0-20210830151154-f567170afdd9 h1:xbL1/7h0k6HE3RzPdYk9W/8pUxESrGWewTaZdIB5Pes=
|
||||
github.com/projectdiscovery/stringsutil v0.0.0-20210830151154-f567170afdd9/go.mod h1:oTRc18WBv9t6BpaN9XBY+QmG28PUpsyDzRht56Qf49I=
|
||||
github.com/projectdiscovery/stringsutil v0.0.0-20211013053023-e7b2e104d80d h1:YBYwsm8MrSp9t7mLehyqGwUKZWB08fG+YRePQRo5iFw=
|
||||
github.com/projectdiscovery/stringsutil v0.0.0-20211013053023-e7b2e104d80d/go.mod h1:JK4F9ACNPgO+Lbm80khX2q1ABInBMbwIOmbsEE61Sn4=
|
||||
github.com/projectdiscovery/yamldoc-go v1.0.2 h1:SKb7PHgSOXm27Zci05ba0FxpyQiu6bGEiVMEcjCK1rQ=
|
||||
github.com/projectdiscovery/yamldoc-go v1.0.2/go.mod h1:7uSxfMXaBmzvw8m5EhOEjB6nhz0rK/H9sUjq1ciZu24=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
|
@ -664,8 +665,6 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
|
|||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI=
|
||||
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
|
@ -674,8 +673,9 @@ github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c
|
|||
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shirou/gopsutil/v3 v3.21.7 h1:PnTqQamUjwEDSgn+nBGu0qSDV/CfvyiR/gwTH3i7HTU=
|
||||
github.com/shirou/gopsutil/v3 v3.21.7/go.mod h1:RGl11Y7XMTQPmHh8F0ayC6haKNBgH4PXMJuTAcMOlz4=
|
||||
github.com/shirou/gopsutil/v3 v3.21.9 h1:Vn4MUz2uXhqLSiCbGFRc0DILbMVLAY92DSkT8bsYrHg=
|
||||
github.com/shirou/gopsutil/v3 v3.21.9/go.mod h1:YWp/H8Qs5fVmf17v7JNZzA0mPJ+mS2e9JdiUF9LlKzQ=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
|
@ -726,10 +726,12 @@ github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPf
|
|||
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
|
||||
github.com/tj/go-update v2.2.5-0.20200519121640-62b4b798fd68+incompatible h1:guTq1YxwB8XSILkI9q4IrOmrCOS6Hc1L3hmOhi4Swcs=
|
||||
github.com/tj/go-update v2.2.5-0.20200519121640-62b4b798fd68+incompatible/go.mod h1:waFwwyiAhGey2e+dNoYQ/iLhIcFqhCW7zL/+vDU1WLo=
|
||||
github.com/tklauser/go-sysconf v0.3.7 h1:HT7h4+536gjqeq1ZIJPgOl1rg1XFatQGVZWp7Py53eg=
|
||||
github.com/tklauser/go-sysconf v0.3.7/go.mod h1:JZIdXh4RmBvZDBZ41ld2bGxRV3n4daiiqA3skYhAoQ4=
|
||||
github.com/tklauser/numcpus v0.2.3 h1:nQ0QYpiritP6ViFhrKYsiv6VVxOpum2Gks5GhnJbS/8=
|
||||
github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
|
||||
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
|
||||
github.com/tklauser/numcpus v0.2.3/go.mod h1:vpEPS/JC+oZGGQ/My/vJnNsvMDQL6PwOqt8dsCw5j+E=
|
||||
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
|
||||
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM=
|
||||
github.com/trivago/tgo v1.0.7/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc=
|
||||
|
@ -750,12 +752,13 @@ github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+
|
|||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
|
||||
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
|
||||
github.com/weppos/publicsuffix-go v0.15.0 h1:2uQCwDczZ8YZe5uD0mM3sXRoZYA74xxPuiKK8LdPcGQ=
|
||||
github.com/weppos/publicsuffix-go v0.15.0/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE=
|
||||
github.com/weppos/publicsuffix-go v0.15.1-0.20210928183822-5ee35905bd95 h1:DyAZOw3JsVd6LJHqhl4MpKQdYQEmat0C6pPPwom39Ow=
|
||||
github.com/weppos/publicsuffix-go v0.15.1-0.20210928183822-5ee35905bd95/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE=
|
||||
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlVjOeLOBz+CPAI8dnbqNSVwUwRrkp7vQ=
|
||||
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM=
|
||||
github.com/xanzy/go-gitlab v0.50.3 h1:M7ncgNhCN4jaFNyXxarJhCLa9Qi6fdmCxFFhMTQPZiY=
|
||||
github.com/xanzy/go-gitlab v0.50.3/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE=
|
||||
github.com/xanzy/go-gitlab v0.51.1 h1:wWKLalwx4omxFoHh3PLs9zDgAD4GXDP/uoxwMRCSiWM=
|
||||
github.com/xanzy/go-gitlab v0.51.1/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
|
@ -924,16 +927,18 @@ golang.org/x/net v0.0.0-20210521195947-fe42d452be8f/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309 h1:A0lJIi+hcTR6aajJH4YqKWwohY4aW9RO7oRMcdv+HKI=
|
||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210817223510-7df4dd6e12ab h1:llrcWN/wOwO+6gAyfBzxb5hZ+c3mriU/0+KNgYu6adA=
|
||||
golang.org/x/oauth2 v0.0.0-20210817223510-7df4dd6e12ab/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 h1:B333XXssMuKQeBwiNODx4TupZy7bf4sxFZnN2ZOcvUE=
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -997,6 +1002,7 @@ golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -1008,7 +1014,9 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210915083310-ed5796bab164 h1:7ZDGnxgHAMw7thfC5bEos0RDAccZKxioiWBhfIe+tvw=
|
||||
golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
|
|
@ -45,13 +45,6 @@ func ParseOptions(options *types.Options) {
|
|||
gologger.Fatal().Msgf("Program exiting: %s\n", err)
|
||||
}
|
||||
|
||||
// Auto adjust rate limits when using headless mode if the user
|
||||
// hasn't specified any custom limits.
|
||||
if options.Headless && options.BulkSize == 25 && options.TemplateThreads == 10 {
|
||||
options.BulkSize = 2
|
||||
options.TemplateThreads = 2
|
||||
}
|
||||
|
||||
// Load the resolvers if user asked for them
|
||||
loadResolvers(options)
|
||||
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
package runner
|
||||
|
||||
import (
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/templates"
|
||||
"github.com/remeh/sizedwaitgroup"
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
// processSelfContainedTemplates execute a self-contained template.
|
||||
func (r *Runner) processSelfContainedTemplates(template *templates.Template) bool {
|
||||
match, err := template.Executer.Execute("")
|
||||
if err != nil {
|
||||
gologger.Warning().Msgf("[%s] Could not execute step: %s\n", r.colorizer.BrightBlue(template.ID), err)
|
||||
}
|
||||
return match
|
||||
}
|
||||
|
||||
// processTemplateWithList execute a template against the list of user provided targets
|
||||
func (r *Runner) processTemplateWithList(template *templates.Template) bool {
|
||||
results := &atomic.Bool{}
|
||||
wg := sizedwaitgroup.New(r.options.BulkSize)
|
||||
processItem := func(k, _ []byte) error {
|
||||
URL := string(k)
|
||||
|
||||
// Skip if the host has had errors
|
||||
if r.hostErrors != nil && r.hostErrors.Check(URL) {
|
||||
return nil
|
||||
}
|
||||
wg.Add()
|
||||
go func(URL string) {
|
||||
defer wg.Done()
|
||||
|
||||
match, err := template.Executer.Execute(URL)
|
||||
if err != nil {
|
||||
gologger.Warning().Msgf("[%s] Could not execute step: %s\n", r.colorizer.BrightBlue(template.ID), err)
|
||||
}
|
||||
results.CAS(false, match)
|
||||
}(URL)
|
||||
return nil
|
||||
}
|
||||
if r.options.Stream {
|
||||
_ = r.hostMapStream.Scan(processItem)
|
||||
} else {
|
||||
r.hostMap.Scan(processItem)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return results.Load()
|
||||
}
|
||||
|
||||
// processTemplateWithList process a template on the URL list
|
||||
func (r *Runner) processWorkflowWithList(template *templates.Template) bool {
|
||||
results := &atomic.Bool{}
|
||||
wg := sizedwaitgroup.New(r.options.BulkSize)
|
||||
|
||||
processItem := func(k, _ []byte) error {
|
||||
URL := string(k)
|
||||
|
||||
// Skip if the host has had errors
|
||||
if r.hostErrors != nil && r.hostErrors.Check(URL) {
|
||||
return nil
|
||||
}
|
||||
wg.Add()
|
||||
go func(URL string) {
|
||||
defer wg.Done()
|
||||
match := template.CompiledWorkflow.RunWorkflow(URL)
|
||||
results.CAS(false, match)
|
||||
}(URL)
|
||||
return nil
|
||||
}
|
||||
|
||||
if r.options.Stream {
|
||||
_ = r.hostMapStream.Scan(processItem)
|
||||
} else {
|
||||
r.hostMap.Scan(processItem)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return results.Load()
|
||||
}
|
|
@ -2,7 +2,6 @@ package runner
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -10,27 +9,22 @@ import (
|
|||
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/remeh/sizedwaitgroup"
|
||||
"github.com/rs/xid"
|
||||
"go.uber.org/atomic"
|
||||
"go.uber.org/ratelimit"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/projectdiscovery/filekv"
|
||||
"github.com/projectdiscovery/fileutil"
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/hmap/store/hybrid"
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/colorizer"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/catalog"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/catalog/config"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/catalog/loader"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/core"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/core/inputs/hybrid"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/parsers"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/progress"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/projectfile"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/clusterer"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/hosterrorscache"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolinit"
|
||||
|
@ -38,7 +32,6 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/exporters/markdown"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/exporters/sarif"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/templates"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/utils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/utils/stats"
|
||||
|
@ -46,22 +39,20 @@ import (
|
|||
|
||||
// Runner is a client for running the enumeration process.
|
||||
type Runner struct {
|
||||
hostMap *hybrid.HybridMap
|
||||
hostMapStream *filekv.FileDB
|
||||
output output.Writer
|
||||
interactsh *interactsh.Client
|
||||
inputCount int64
|
||||
templatesConfig *config.Config
|
||||
options *types.Options
|
||||
projectFile *projectfile.ProjectFile
|
||||
catalog *catalog.Catalog
|
||||
progress progress.Progress
|
||||
colorizer aurora.Aurora
|
||||
issuesClient *reporting.Client
|
||||
addColor func(severity.Severity) string
|
||||
browser *engine.Browser
|
||||
ratelimiter ratelimit.Limiter
|
||||
hostErrors *hosterrorscache.Cache
|
||||
output output.Writer
|
||||
interactsh *interactsh.Client
|
||||
templatesConfig *config.Config
|
||||
options *types.Options
|
||||
projectFile *projectfile.ProjectFile
|
||||
catalog *catalog.Catalog
|
||||
progress progress.Progress
|
||||
colorizer aurora.Aurora
|
||||
issuesClient *reporting.Client
|
||||
addColor func(severity.Severity) string
|
||||
hmapInputProvider *hybrid.Input
|
||||
browser *engine.Browser
|
||||
ratelimiter ratelimit.Limiter
|
||||
hostErrors *hosterrorscache.Cache
|
||||
}
|
||||
|
||||
// New creates a new client for running enumeration process.
|
||||
|
@ -116,103 +107,13 @@ func New(options *types.Options) (*Runner, error) {
|
|||
if (len(options.Templates) == 0 || !options.NewTemplates || (options.TargetsFilePath == "" && !options.Stdin && len(options.Targets) == 0)) && options.UpdateTemplates {
|
||||
os.Exit(0)
|
||||
}
|
||||
hm, err := hybrid.New(hybrid.DefaultDiskOptions)
|
||||
|
||||
// Initialize the input source
|
||||
hmapInput, err := hybrid.New(options)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create temporary input file")
|
||||
}
|
||||
runner.hostMap = hm
|
||||
|
||||
if options.Stream {
|
||||
fkvOptions := filekv.DefaultOptions
|
||||
if tmpFileName, err := fileutil.GetTempFileName(); err != nil {
|
||||
return nil, errors.Wrap(err, "could not create temporary input file")
|
||||
} else {
|
||||
fkvOptions.Path = tmpFileName
|
||||
}
|
||||
fkv, err := filekv.Open(fkvOptions)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create temporary unsorted input file")
|
||||
}
|
||||
runner.hostMapStream = fkv
|
||||
}
|
||||
|
||||
runner.inputCount = 0
|
||||
dupeCount := 0
|
||||
|
||||
// Handle multiple targets
|
||||
if len(options.Targets) != 0 {
|
||||
for _, target := range options.Targets {
|
||||
url := strings.TrimSpace(target)
|
||||
if url == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := runner.hostMap.Get(url); ok {
|
||||
dupeCount++
|
||||
continue
|
||||
}
|
||||
|
||||
runner.inputCount++
|
||||
// nolint:errcheck // ignoring error
|
||||
runner.hostMap.Set(url, nil)
|
||||
if options.Stream {
|
||||
_ = runner.hostMapStream.Set([]byte(url), nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle stdin
|
||||
if options.Stdin {
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
for scanner.Scan() {
|
||||
url := strings.TrimSpace(scanner.Text())
|
||||
if url == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := runner.hostMap.Get(url); ok {
|
||||
dupeCount++
|
||||
continue
|
||||
}
|
||||
|
||||
runner.inputCount++
|
||||
// nolint:errcheck // ignoring error
|
||||
runner.hostMap.Set(url, nil)
|
||||
if options.Stream {
|
||||
_ = runner.hostMapStream.Set([]byte(url), nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle target file
|
||||
if options.TargetsFilePath != "" {
|
||||
input, inputErr := os.Open(options.TargetsFilePath)
|
||||
if inputErr != nil {
|
||||
return nil, errors.Wrap(inputErr, "could not open targets file")
|
||||
}
|
||||
scanner := bufio.NewScanner(input)
|
||||
for scanner.Scan() {
|
||||
url := strings.TrimSpace(scanner.Text())
|
||||
if url == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := runner.hostMap.Get(url); ok {
|
||||
dupeCount++
|
||||
continue
|
||||
}
|
||||
runner.inputCount++
|
||||
// nolint:errcheck // ignoring error
|
||||
runner.hostMap.Set(url, nil)
|
||||
if options.Stream {
|
||||
_ = runner.hostMapStream.Set([]byte(url), nil)
|
||||
}
|
||||
}
|
||||
input.Close()
|
||||
}
|
||||
|
||||
if dupeCount > 0 {
|
||||
gologger.Info().Msgf("Supplied input was automatically deduplicated (%d removed).", dupeCount)
|
||||
return nil, errors.Wrap(err, "could not create input provider")
|
||||
}
|
||||
runner.hmapInputProvider = hmapInput
|
||||
|
||||
// Create the output file if asked
|
||||
outputWriter, err := output.NewStandardWriter(!options.NoColor, options.NoMeta, options.NoTimestamp, options.JSON, options.JSONRequests, options.Output, options.TraceLogFile, options.ErrorLogFile)
|
||||
|
@ -244,18 +145,16 @@ func New(options *types.Options) (*Runner, error) {
|
|||
}
|
||||
|
||||
if !options.NoInteractsh {
|
||||
interactshClient, err := interactsh.New(&interactsh.Options{
|
||||
ServerURL: options.InteractshURL,
|
||||
Authorization: options.InteractshToken,
|
||||
CacheSize: int64(options.InteractionsCacheSize),
|
||||
Eviction: time.Duration(options.InteractionsEviction) * time.Second,
|
||||
ColldownPeriod: time.Duration(options.InteractionsCooldownPeriod) * time.Second,
|
||||
PollDuration: time.Duration(options.InteractionsPollDuration) * time.Second,
|
||||
Output: runner.output,
|
||||
IssuesClient: runner.issuesClient,
|
||||
Progress: runner.progress,
|
||||
Debug: runner.options.Debug,
|
||||
})
|
||||
opts := interactsh.NewDefaultOptions(runner.output, runner.issuesClient, runner.progress)
|
||||
opts.Debug = runner.options.Debug
|
||||
opts.ServerURL = options.InteractshURL
|
||||
opts.Authorization = options.InteractshToken
|
||||
opts.CacheSize = int64(options.InteractionsCacheSize)
|
||||
opts.Eviction = time.Duration(options.InteractionsEviction) * time.Second
|
||||
opts.ColldownPeriod = time.Duration(options.InteractionsCooldownPeriod) * time.Second
|
||||
opts.PollDuration = time.Duration(options.InteractionsPollDuration) * time.Second
|
||||
|
||||
interactshClient, err := interactsh.New(opts)
|
||||
if err != nil {
|
||||
gologger.Error().Msgf("Could not create interactsh client: %s", err)
|
||||
} else {
|
||||
|
@ -312,13 +211,10 @@ func (r *Runner) Close() {
|
|||
if r.output != nil {
|
||||
r.output.Close()
|
||||
}
|
||||
r.hostMap.Close()
|
||||
if r.projectFile != nil {
|
||||
r.projectFile.Close()
|
||||
}
|
||||
if r.options.Stream {
|
||||
r.hostMapStream.Close()
|
||||
}
|
||||
r.hmapInputProvider.Close()
|
||||
protocolinit.Close()
|
||||
}
|
||||
|
||||
|
@ -344,6 +240,9 @@ func (r *Runner) RunEnumeration() error {
|
|||
cache = hosterrorscache.New(r.options.MaxHostError, hosterrorscache.DefaultMaxHostsCount).SetVerbose(r.options.Verbose)
|
||||
}
|
||||
r.hostErrors = cache
|
||||
|
||||
// Create the executer options which will be used throughout the execution
|
||||
// stage by the nuclei engine modules.
|
||||
executerOpts := protocols.ExecuterOptions{
|
||||
Output: r.output,
|
||||
Options: r.options,
|
||||
|
@ -355,35 +254,18 @@ func (r *Runner) RunEnumeration() error {
|
|||
ProjectFile: r.projectFile,
|
||||
Browser: r.browser,
|
||||
HostErrorsCache: cache,
|
||||
Colorizer: r.colorizer,
|
||||
}
|
||||
engine := core.New(r.options)
|
||||
engine.SetExecuterOptions(executerOpts)
|
||||
|
||||
workflowLoader, err := parsers.NewLoader(&executerOpts)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Could not create loader.")
|
||||
}
|
||||
|
||||
executerOpts.WorkflowLoader = workflowLoader
|
||||
|
||||
loaderConfig := loader.Config{
|
||||
Templates: r.options.Templates,
|
||||
TemplateURLs: r.options.TemplateURLs,
|
||||
Workflows: r.options.Workflows,
|
||||
WorkflowURLs: r.options.WorkflowURLs,
|
||||
ExcludeTemplates: r.options.ExcludedTemplates,
|
||||
Tags: r.options.Tags,
|
||||
ExcludeTags: r.options.ExcludeTags,
|
||||
IncludeTemplates: r.options.IncludeTemplates,
|
||||
Authors: r.options.Author,
|
||||
Severities: r.options.Severities,
|
||||
ExcludeSeverities: r.options.ExcludeSeverities,
|
||||
IncludeTags: r.options.IncludeTags,
|
||||
TemplatesDirectory: r.options.TemplatesDirectory,
|
||||
Protocols: r.options.Protocols,
|
||||
ExcludeProtocols: r.options.ExcludeProtocols,
|
||||
Catalog: r.catalog,
|
||||
ExecutorOptions: executerOpts,
|
||||
}
|
||||
store, err := loader.New(&loaderConfig)
|
||||
store, err := loader.New(loader.NewConfig(r.options, r.catalog, executerOpts))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not load templates from config")
|
||||
}
|
||||
|
@ -401,6 +283,78 @@ func (r *Runner) RunEnumeration() error {
|
|||
return nil // exit
|
||||
}
|
||||
|
||||
r.displayExecutionInfo(store)
|
||||
|
||||
var unclusteredRequests int64
|
||||
for _, template := range store.Templates() {
|
||||
// workflows will dynamically adjust the totals while running, as
|
||||
// it can't be known in advance which requests will be called
|
||||
if len(template.Workflows) > 0 {
|
||||
continue
|
||||
}
|
||||
unclusteredRequests += int64(template.TotalRequests) * r.hmapInputProvider.Count()
|
||||
}
|
||||
|
||||
if r.options.VerboseVerbose {
|
||||
for _, template := range store.Templates() {
|
||||
r.logAvailableTemplate(template.Path)
|
||||
}
|
||||
for _, template := range store.Workflows() {
|
||||
r.logAvailableTemplate(template.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// Cluster the templates first because we want info on how many
|
||||
// templates did we cluster for showing to user in CLI
|
||||
originalTemplatesCount := len(store.Templates())
|
||||
finalTemplates, clusterCount := engine.ClusterTemplates(store.Templates())
|
||||
finalTemplates = append(finalTemplates, store.Workflows()...)
|
||||
|
||||
var totalRequests int64
|
||||
for _, t := range finalTemplates {
|
||||
if len(t.Workflows) > 0 {
|
||||
continue
|
||||
}
|
||||
totalRequests += int64(t.TotalRequests) * r.hmapInputProvider.Count()
|
||||
}
|
||||
if totalRequests < unclusteredRequests {
|
||||
gologger.Info().Msgf("Templates clustered: %d (Reduced %d HTTP Requests)", clusterCount, unclusteredRequests-totalRequests)
|
||||
}
|
||||
workflowCount := len(store.Workflows())
|
||||
templateCount := originalTemplatesCount + workflowCount
|
||||
|
||||
// 0 matches means no templates were found in directory
|
||||
if templateCount == 0 {
|
||||
return errors.New("no valid templates were found")
|
||||
}
|
||||
|
||||
// tracks global progress and captures stdout/stderr until p.Wait finishes
|
||||
r.progress.Init(r.hmapInputProvider.Count(), templateCount, totalRequests)
|
||||
|
||||
results := engine.ExecuteWithOpts(finalTemplates, r.hmapInputProvider, true)
|
||||
|
||||
if r.interactsh != nil {
|
||||
matched := r.interactsh.Close()
|
||||
if matched {
|
||||
results.CAS(false, true)
|
||||
}
|
||||
}
|
||||
r.progress.Stop()
|
||||
|
||||
if r.issuesClient != nil {
|
||||
r.issuesClient.Close()
|
||||
}
|
||||
if !results.Load() {
|
||||
gologger.Info().Msgf("No results found. Better luck next time!")
|
||||
}
|
||||
if r.browser != nil {
|
||||
r.browser.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// displayExecutionInfo displays misc info about the nuclei engine execution
|
||||
func (r *Runner) displayExecutionInfo(store *loader.Store) {
|
||||
// Display stats for any loaded templates' syntax warnings or errors
|
||||
stats.Display(parsers.SyntaxWarningStats)
|
||||
stats.Display(parsers.SyntaxErrorStats)
|
||||
|
@ -449,128 +403,6 @@ func (r *Runner) RunEnumeration() error {
|
|||
if len(store.Workflows()) > 0 {
|
||||
gologger.Info().Msgf("Workflows loaded for scan: %d", len(store.Workflows()))
|
||||
}
|
||||
|
||||
// pre-parse all the templates, apply filters
|
||||
finalTemplates := []*templates.Template{}
|
||||
|
||||
var unclusteredRequests int64
|
||||
for _, template := range store.Templates() {
|
||||
// workflows will dynamically adjust the totals while running, as
|
||||
// it can't be known in advance which requests will be called
|
||||
if len(template.Workflows) > 0 {
|
||||
continue
|
||||
}
|
||||
unclusteredRequests += int64(template.TotalRequests) * r.inputCount
|
||||
}
|
||||
|
||||
if r.options.VerboseVerbose {
|
||||
for _, template := range store.Templates() {
|
||||
r.logAvailableTemplate(template.Path)
|
||||
}
|
||||
for _, template := range store.Workflows() {
|
||||
r.logAvailableTemplate(template.Path)
|
||||
}
|
||||
}
|
||||
templatesMap := make(map[string]*templates.Template)
|
||||
for _, v := range store.Templates() {
|
||||
templatesMap[v.Path] = v
|
||||
}
|
||||
originalTemplatesCount := len(store.Templates())
|
||||
clusterCount := 0
|
||||
clusters := clusterer.Cluster(templatesMap)
|
||||
for _, cluster := range clusters {
|
||||
if len(cluster) > 1 && !r.options.OfflineHTTP {
|
||||
executerOpts := protocols.ExecuterOptions{
|
||||
Output: r.output,
|
||||
Options: r.options,
|
||||
Progress: r.progress,
|
||||
Catalog: r.catalog,
|
||||
RateLimiter: r.ratelimiter,
|
||||
IssuesClient: r.issuesClient,
|
||||
Browser: r.browser,
|
||||
ProjectFile: r.projectFile,
|
||||
Interactsh: r.interactsh,
|
||||
HostErrorsCache: cache,
|
||||
}
|
||||
clusterID := fmt.Sprintf("cluster-%s", xid.New().String())
|
||||
|
||||
finalTemplates = append(finalTemplates, &templates.Template{
|
||||
ID: clusterID,
|
||||
RequestsHTTP: cluster[0].RequestsHTTP,
|
||||
Executer: clusterer.NewExecuter(cluster, &executerOpts),
|
||||
TotalRequests: len(cluster[0].RequestsHTTP),
|
||||
})
|
||||
clusterCount += len(cluster)
|
||||
} else {
|
||||
finalTemplates = append(finalTemplates, cluster...)
|
||||
}
|
||||
}
|
||||
|
||||
finalTemplates = append(finalTemplates, store.Workflows()...)
|
||||
|
||||
var totalRequests int64
|
||||
for _, t := range finalTemplates {
|
||||
if len(t.Workflows) > 0 {
|
||||
continue
|
||||
}
|
||||
totalRequests += int64(t.TotalRequests) * r.inputCount
|
||||
}
|
||||
if totalRequests < unclusteredRequests {
|
||||
gologger.Info().Msgf("Templates clustered: %d (Reduced %d HTTP Requests)", clusterCount, unclusteredRequests-totalRequests)
|
||||
}
|
||||
workflowCount := len(store.Workflows())
|
||||
templateCount := originalTemplatesCount + workflowCount
|
||||
|
||||
// 0 matches means no templates were found in directory
|
||||
if templateCount == 0 {
|
||||
return errors.New("no valid templates were found")
|
||||
}
|
||||
|
||||
/*
|
||||
TODO does it make sense to run the logic below if there are no targets specified?
|
||||
Can we safely assume the user is just experimenting with the template/workflow filters before running them?
|
||||
*/
|
||||
|
||||
results := &atomic.Bool{}
|
||||
wgtemplates := sizedwaitgroup.New(r.options.TemplateThreads)
|
||||
|
||||
// tracks global progress and captures stdout/stderr until p.Wait finishes
|
||||
r.progress.Init(r.inputCount, templateCount, totalRequests)
|
||||
|
||||
for _, t := range finalTemplates {
|
||||
wgtemplates.Add()
|
||||
go func(template *templates.Template) {
|
||||
defer wgtemplates.Done()
|
||||
|
||||
if template.SelfContained {
|
||||
results.CAS(false, r.processSelfContainedTemplates(template))
|
||||
} else if len(template.Workflows) > 0 {
|
||||
results.CAS(false, r.processWorkflowWithList(template))
|
||||
} else {
|
||||
results.CAS(false, r.processTemplateWithList(template))
|
||||
}
|
||||
}(t)
|
||||
}
|
||||
wgtemplates.Wait()
|
||||
|
||||
if r.interactsh != nil {
|
||||
matched := r.interactsh.Close()
|
||||
if matched {
|
||||
results.CAS(false, true)
|
||||
}
|
||||
}
|
||||
r.progress.Stop()
|
||||
|
||||
if r.issuesClient != nil {
|
||||
r.issuesClient.Close()
|
||||
}
|
||||
if !results.Load() {
|
||||
gologger.Info().Msgf("No results found. Better luck next time!")
|
||||
}
|
||||
if r.browser != nil {
|
||||
r.browser.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readNewTemplatesFile reads newly added templates from directory if it exists
|
||||
|
|
|
@ -13,8 +13,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/catalog/config"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
|
@ -10,7 +10,8 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/parsers"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/templates"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
|
||||
templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||
)
|
||||
|
||||
// Config contains the configuration options for the loader
|
||||
|
@ -24,8 +25,8 @@ type Config struct {
|
|||
|
||||
Tags []string
|
||||
ExcludeTags []string
|
||||
Protocols types.ProtocolTypes
|
||||
ExcludeProtocols types.ProtocolTypes
|
||||
Protocols templateTypes.ProtocolTypes
|
||||
ExcludeProtocols templateTypes.ProtocolTypes
|
||||
Authors []string
|
||||
Severities severity.Severities
|
||||
ExcludeSeverities severity.Severities
|
||||
|
@ -50,6 +51,30 @@ type Store struct {
|
|||
preprocessor templates.Preprocessor
|
||||
}
|
||||
|
||||
// NewConfig returns a new loader config
|
||||
func NewConfig(options *types.Options, catalog *catalog.Catalog, executerOpts protocols.ExecuterOptions) *Config {
|
||||
loaderConfig := Config{
|
||||
Templates: options.Templates,
|
||||
Workflows: options.Workflows,
|
||||
TemplateURLs: options.TemplateURLs,
|
||||
WorkflowURLs: options.WorkflowURLs,
|
||||
ExcludeTemplates: options.ExcludedTemplates,
|
||||
Tags: options.Tags,
|
||||
ExcludeTags: options.ExcludeTags,
|
||||
IncludeTemplates: options.IncludeTemplates,
|
||||
Authors: options.Authors,
|
||||
Severities: options.Severities,
|
||||
ExcludeSeverities: options.ExcludeSeverities,
|
||||
IncludeTags: options.IncludeTags,
|
||||
TemplatesDirectory: options.TemplatesDirectory,
|
||||
Protocols: options.Protocols,
|
||||
ExcludeProtocols: options.ExcludeProtocols,
|
||||
Catalog: catalog,
|
||||
ExecutorOptions: executerOpts,
|
||||
}
|
||||
return &loaderConfig
|
||||
}
|
||||
|
||||
// New creates a new template store based on provided configuration
|
||||
func New(config *Config) (*Store, error) {
|
||||
// Create a tag filter based on provided configuration
|
||||
|
@ -73,7 +98,8 @@ func New(config *Config) (*Store, error) {
|
|||
finalWorkflows: config.Workflows,
|
||||
}
|
||||
|
||||
if len(config.TemplateURLs) > 0 || len(config.WorkflowURLs) > 0 {
|
||||
urlbasedTemplatesProvided := len(config.TemplateURLs) > 0 || len(config.WorkflowURLs) > 0
|
||||
if urlbasedTemplatesProvided {
|
||||
remoteTemplates, remoteWorkflows, err := getRemoteTemplatesAndWorkflows(config.TemplateURLs, config.WorkflowURLs)
|
||||
if err != nil {
|
||||
return store, err
|
||||
|
@ -83,7 +109,7 @@ func New(config *Config) (*Store, error) {
|
|||
}
|
||||
|
||||
// Handle a case with no templates or workflows, where we use base directory
|
||||
if len(store.finalTemplates) == 0 && len(store.finalWorkflows) == 0 {
|
||||
if len(store.finalTemplates) == 0 && len(store.finalWorkflows) == 0 && !urlbasedTemplatesProvided {
|
||||
store.finalTemplates = []string{config.TemplatesDirectory}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,9 +3,10 @@ package loader
|
|||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ContentType string
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||
)
|
||||
|
||||
// Engine is an executer for running Nuclei Templates/Workflows.
|
||||
//
|
||||
// The engine contains multiple thread pools which allow using different
|
||||
// concurrency values per protocol executed.
|
||||
//
|
||||
// The engine does most of the heavy lifting of execution, from clustering
|
||||
// templates to leading to the final execution by the workpool, it is
|
||||
// handled by the engine.
|
||||
type Engine struct {
|
||||
workPool *WorkPool
|
||||
options *types.Options
|
||||
executerOpts protocols.ExecuterOptions
|
||||
}
|
||||
|
||||
// InputProvider is an input providing interface for the nuclei execution
|
||||
// engine.
|
||||
//
|
||||
// An example InputProvider implementation is provided in form of hybrid
|
||||
// input provider in pkg/core/inputs/hybrid/hmap.go
|
||||
type InputProvider interface {
|
||||
// Count returns the number of items for input provider
|
||||
Count() int64
|
||||
// Scan iterates the input and each found item is passed to the
|
||||
// callback consumer.
|
||||
Scan(callback func(value string))
|
||||
}
|
||||
|
||||
// New returns a new Engine instance
|
||||
func New(options *types.Options) *Engine {
|
||||
workPool := NewWorkPool(WorkPoolConfig{
|
||||
InputConcurrency: options.BulkSize,
|
||||
TypeConcurrency: options.TemplateThreads,
|
||||
HeadlessInputConcurrency: options.HeadlessBulkSize,
|
||||
HeadlessTypeConcurrency: options.HeadlessTemplateThreads,
|
||||
})
|
||||
engine := &Engine{
|
||||
options: options,
|
||||
workPool: workPool,
|
||||
}
|
||||
return engine
|
||||
}
|
||||
|
||||
// SetExecuterOptions sets the executer options for the engine. This is required
|
||||
// before using the engine to perform any execution.
|
||||
func (e *Engine) SetExecuterOptions(options protocols.ExecuterOptions) {
|
||||
e.executerOpts = options
|
||||
}
|
||||
|
||||
// ExecuterOptions returns protocols.ExecuterOptions for nuclei engine.
|
||||
func (e *Engine) ExecuterOptions() protocols.ExecuterOptions {
|
||||
return e.executerOpts
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package core
|
|
@ -0,0 +1,131 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/clusterer"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/templates"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
|
||||
"github.com/remeh/sizedwaitgroup"
|
||||
"github.com/rs/xid"
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
// Execute takes a list of templates/workflows that have been compiled
|
||||
// and executes them based on provided concurrency options.
|
||||
//
|
||||
// All the execution logic for the templates/workflows happens in this part
|
||||
// of the engine.
|
||||
func (e *Engine) Execute(templates []*templates.Template, target InputProvider) *atomic.Bool {
|
||||
return e.ExecuteWithOpts(templates, target, false)
|
||||
}
|
||||
|
||||
// ExecuteWithOpts is execute with the full options
|
||||
func (e *Engine) ExecuteWithOpts(templatesList []*templates.Template, target InputProvider, noCluster bool) *atomic.Bool {
|
||||
var finalTemplates []*templates.Template
|
||||
if !noCluster {
|
||||
finalTemplates, _ = e.ClusterTemplates(templatesList)
|
||||
} else {
|
||||
finalTemplates = templatesList
|
||||
}
|
||||
|
||||
results := &atomic.Bool{}
|
||||
for _, template := range finalTemplates {
|
||||
templateType := template.Type()
|
||||
|
||||
var wg *sizedwaitgroup.SizedWaitGroup
|
||||
if templateType == types.HeadlessProtocol {
|
||||
wg = e.workPool.Headless
|
||||
} else {
|
||||
wg = e.workPool.Default
|
||||
}
|
||||
|
||||
wg.Add()
|
||||
switch {
|
||||
case template.SelfContained:
|
||||
// Self Contained requests are executed here separately
|
||||
e.executeSelfContainedTemplateWithInput(template, results)
|
||||
default:
|
||||
// All other request types are executed here
|
||||
e.executeModelWithInput(templateType, template, target, results)
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
e.workPool.Wait()
|
||||
return results
|
||||
}
|
||||
|
||||
// processSelfContainedTemplates execute a self-contained template.
|
||||
func (e *Engine) executeSelfContainedTemplateWithInput(template *templates.Template, results *atomic.Bool) {
|
||||
match, err := template.Executer.Execute("")
|
||||
if err != nil {
|
||||
gologger.Warning().Msgf("[%s] Could not execute step: %s\n", e.executerOpts.Colorizer.BrightBlue(template.ID), err)
|
||||
}
|
||||
results.CAS(false, match)
|
||||
}
|
||||
|
||||
// executeModelWithInput executes a type of template with input
|
||||
func (e *Engine) executeModelWithInput(templateType types.ProtocolType, template *templates.Template, target InputProvider, results *atomic.Bool) {
|
||||
wg := e.workPool.InputPool(templateType)
|
||||
|
||||
target.Scan(func(scannedValue string) {
|
||||
// Skip if the host has had errors
|
||||
if e.executerOpts.HostErrorsCache != nil && e.executerOpts.HostErrorsCache.Check(scannedValue) {
|
||||
return
|
||||
}
|
||||
|
||||
wg.Waitgroup.Add()
|
||||
go func(value string) {
|
||||
defer wg.Waitgroup.Done()
|
||||
|
||||
var match bool
|
||||
var err error
|
||||
switch templateType {
|
||||
case types.WorkflowProtocol:
|
||||
match = e.executeWorkflow(value, template.CompiledWorkflow)
|
||||
default:
|
||||
match, err = template.Executer.Execute(value)
|
||||
}
|
||||
if err != nil {
|
||||
gologger.Warning().Msgf("[%s] Could not execute step: %s\n", e.executerOpts.Colorizer.BrightBlue(template.ID), err)
|
||||
}
|
||||
results.CAS(false, match)
|
||||
}(scannedValue)
|
||||
})
|
||||
wg.Waitgroup.Wait()
|
||||
}
|
||||
|
||||
// ClusterTemplates performs identical http requests clustering for a list of templates
|
||||
func (e *Engine) ClusterTemplates(templatesList []*templates.Template) ([]*templates.Template, int) {
|
||||
if e.options.OfflineHTTP {
|
||||
return templatesList, 0
|
||||
}
|
||||
|
||||
templatesMap := make(map[string]*templates.Template)
|
||||
for _, v := range templatesList {
|
||||
templatesMap[v.Path] = v
|
||||
}
|
||||
clusterCount := 0
|
||||
|
||||
finalTemplatesList := make([]*templates.Template, 0, len(templatesList))
|
||||
clusters := clusterer.Cluster(templatesMap)
|
||||
for _, cluster := range clusters {
|
||||
if len(cluster) > 1 {
|
||||
executerOpts := e.ExecuterOptions()
|
||||
|
||||
clusterID := fmt.Sprintf("cluster-%s", xid.New().String())
|
||||
|
||||
finalTemplatesList = append(finalTemplatesList, &templates.Template{
|
||||
ID: clusterID,
|
||||
RequestsHTTP: cluster[0].RequestsHTTP,
|
||||
Executer: clusterer.NewExecuter(cluster, &executerOpts),
|
||||
TotalRequests: len(cluster[0].RequestsHTTP),
|
||||
})
|
||||
clusterCount += len(cluster)
|
||||
} else {
|
||||
finalTemplatesList = append(finalTemplatesList, cluster...)
|
||||
}
|
||||
}
|
||||
return finalTemplatesList, clusterCount
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
// Package hybrid implements a hybrid hmap/filekv backed input provider
|
||||
// for nuclei that can either stream or store results using different kv stores.
|
||||
package hybrid
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/projectdiscovery/filekv"
|
||||
"github.com/projectdiscovery/fileutil"
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/hmap/store/hybrid"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||
)
|
||||
|
||||
// Input is a hmap/filekv backed nuclei Input provider
|
||||
type Input struct {
|
||||
inputCount int64
|
||||
dupeCount int64
|
||||
hostMap *hybrid.HybridMap
|
||||
hostMapStream *filekv.FileDB
|
||||
}
|
||||
|
||||
// New creates a new hmap backed nuclei Input Provider
|
||||
// and initializes it based on the passed options Model.
|
||||
func New(options *types.Options) (*Input, error) {
|
||||
hm, err := hybrid.New(hybrid.DefaultDiskOptions)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create temporary input file")
|
||||
}
|
||||
|
||||
input := &Input{hostMap: hm}
|
||||
if options.Stream {
|
||||
fkvOptions := filekv.DefaultOptions
|
||||
if tmpFileName, err := fileutil.GetTempFileName(); err != nil {
|
||||
return nil, errors.Wrap(err, "could not create temporary input file")
|
||||
} else {
|
||||
fkvOptions.Path = tmpFileName
|
||||
}
|
||||
fkv, err := filekv.Open(fkvOptions)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create temporary unsorted input file")
|
||||
}
|
||||
input.hostMapStream = fkv
|
||||
}
|
||||
if initErr := input.initializeInputSources(options); initErr != nil {
|
||||
return nil, initErr
|
||||
}
|
||||
if input.dupeCount > 0 {
|
||||
gologger.Info().Msgf("Supplied input was automatically deduplicated (%d removed).", input.dupeCount)
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
// Close closes the input provider
|
||||
func (i *Input) Close() {
|
||||
i.hostMap.Close()
|
||||
if i.hostMapStream != nil {
|
||||
i.hostMapStream.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// initializeInputSources initializes the input sources for hmap input
|
||||
func (i *Input) initializeInputSources(options *types.Options) error {
|
||||
// Handle targets flags
|
||||
for _, target := range options.Targets {
|
||||
i.normalizeStoreInputValue(target)
|
||||
}
|
||||
|
||||
// Handle stdin
|
||||
if options.Stdin {
|
||||
i.scanInputFromReader(os.Stdin)
|
||||
}
|
||||
|
||||
// Handle target file
|
||||
if options.TargetsFilePath != "" {
|
||||
input, inputErr := os.Open(options.TargetsFilePath)
|
||||
if inputErr != nil {
|
||||
return errors.Wrap(inputErr, "could not open targets file")
|
||||
}
|
||||
i.scanInputFromReader(input)
|
||||
input.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// scanInputFromReader scans a line of input from reader and passes it for storage
|
||||
func (i *Input) scanInputFromReader(reader io.Reader) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
for scanner.Scan() {
|
||||
i.normalizeStoreInputValue(scanner.Text())
|
||||
}
|
||||
}
|
||||
|
||||
// normalizeStoreInputValue normalizes and stores passed input values
|
||||
func (i *Input) normalizeStoreInputValue(value string) {
|
||||
url := strings.TrimSpace(value)
|
||||
if url == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := i.hostMap.Get(url); ok {
|
||||
i.dupeCount++
|
||||
return
|
||||
}
|
||||
|
||||
i.inputCount++
|
||||
_ = i.hostMap.Set(url, nil)
|
||||
if i.hostMapStream != nil {
|
||||
_ = i.hostMapStream.Set([]byte(url), nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Count returns the input count
|
||||
func (i *Input) Count() int64 {
|
||||
return i.inputCount
|
||||
}
|
||||
|
||||
// Scan iterates the input and each found item is passed to the
|
||||
// callback consumer.
|
||||
func (i *Input) Scan(callback func(value string)) {
|
||||
callbackFunc := func(k, _ []byte) error {
|
||||
callback(string(k))
|
||||
return nil
|
||||
}
|
||||
if i.hostMapStream != nil {
|
||||
_ = i.hostMapStream.Scan(callbackFunc)
|
||||
} else {
|
||||
i.hostMap.Scan(callbackFunc)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package inputs
|
||||
|
||||
type SimpleInputProvider struct {
|
||||
Inputs []string
|
||||
}
|
||||
|
||||
// Count returns the number of items for input provider
|
||||
func (s *SimpleInputProvider) Count() int64 {
|
||||
return int64(len(s.Inputs))
|
||||
}
|
||||
|
||||
// Scan calls a callback function till the input provider is exhausted
|
||||
func (s *SimpleInputProvider) Scan(callback func(value string)) {
|
||||
for _, v := range s.Inputs {
|
||||
callback(v)
|
||||
}
|
||||
}
|
|
@ -1,21 +1,22 @@
|
|||
package workflows
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/workflows"
|
||||
"github.com/remeh/sizedwaitgroup"
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
// RunWorkflow runs a workflow on an input and returns true or false
|
||||
func (w *Workflow) RunWorkflow(input string) bool {
|
||||
// executeWorkflow runs a workflow on an input and returns true or false
|
||||
func (e *Engine) executeWorkflow(input string, w *workflows.Workflow) bool {
|
||||
results := &atomic.Bool{}
|
||||
|
||||
swg := sizedwaitgroup.New(w.Options.Options.TemplateThreads)
|
||||
for _, template := range w.Workflows {
|
||||
swg.Add()
|
||||
func(template *WorkflowTemplate) {
|
||||
if err := w.runWorkflowStep(template, input, results, &swg); err != nil {
|
||||
func(template *workflows.WorkflowTemplate) {
|
||||
if err := e.runWorkflowStep(template, input, results, &swg, w); err != nil {
|
||||
gologger.Warning().Msgf("[%s] Could not execute workflow step: %s\n", template.Template, err)
|
||||
}
|
||||
swg.Done()
|
||||
|
@ -27,7 +28,7 @@ func (w *Workflow) RunWorkflow(input string) bool {
|
|||
|
||||
// runWorkflowStep runs a workflow step for the workflow. It executes the workflow
|
||||
// in a recursive manner running all subtemplates and matchers.
|
||||
func (w *Workflow) runWorkflowStep(template *WorkflowTemplate, input string, results *atomic.Bool, swg *sizedwaitgroup.SizedWaitGroup) error {
|
||||
func (e *Engine) runWorkflowStep(template *workflows.WorkflowTemplate, input string, results *atomic.Bool, swg *sizedwaitgroup.SizedWaitGroup, w *workflows.Workflow) error {
|
||||
var firstMatched bool
|
||||
var err error
|
||||
var mainErr error
|
||||
|
@ -90,8 +91,8 @@ func (w *Workflow) runWorkflowStep(template *WorkflowTemplate, input string, res
|
|||
for _, subtemplate := range matcher.Subtemplates {
|
||||
swg.Add()
|
||||
|
||||
go func(subtemplate *WorkflowTemplate) {
|
||||
if err := w.runWorkflowStep(subtemplate, input, results, swg); err != nil {
|
||||
go func(subtemplate *workflows.WorkflowTemplate) {
|
||||
if err := e.runWorkflowStep(subtemplate, input, results, swg, w); err != nil {
|
||||
gologger.Warning().Msgf("[%s] Could not execute workflow step: %s\n", subtemplate.Template, err)
|
||||
}
|
||||
swg.Done()
|
||||
|
@ -114,8 +115,8 @@ func (w *Workflow) runWorkflowStep(template *WorkflowTemplate, input string, res
|
|||
for _, subtemplate := range template.Subtemplates {
|
||||
swg.Add()
|
||||
|
||||
go func(template *WorkflowTemplate) {
|
||||
if err := w.runWorkflowStep(template, input, results, swg); err != nil {
|
||||
go func(template *workflows.WorkflowTemplate) {
|
||||
if err := e.runWorkflowStep(template, input, results, swg, w); err != nil {
|
||||
gologger.Warning().Msgf("[%s] Could not execute workflow step: %s\n", template.Template, err)
|
||||
}
|
||||
swg.Done()
|
|
@ -1,4 +1,4 @@
|
|||
package workflows
|
||||
package core
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
@ -10,18 +10,20 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/progress"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/workflows"
|
||||
)
|
||||
|
||||
func TestWorkflowsSimple(t *testing.T) {
|
||||
progressBar, _ := progress.NewStatsTicker(0, false, false, false, 0)
|
||||
|
||||
workflow := &Workflow{Options: &protocols.ExecuterOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*WorkflowTemplate{
|
||||
{Executers: []*ProtocolExecuterPair{{
|
||||
workflow := &workflows.Workflow{Options: &protocols.ExecuterOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{
|
||||
{Executers: []*workflows.ProtocolExecuterPair{{
|
||||
Executer: &mockExecuter{result: true}, Options: &protocols.ExecuterOptions{Progress: progressBar}},
|
||||
}},
|
||||
}}
|
||||
|
||||
matched := workflow.RunWorkflow("https://test.com")
|
||||
engine := &Engine{}
|
||||
matched := engine.executeWorkflow("https://test.com", workflow)
|
||||
require.True(t, matched, "could not get correct match value")
|
||||
}
|
||||
|
||||
|
@ -29,20 +31,21 @@ func TestWorkflowsSimpleMultiple(t *testing.T) {
|
|||
progressBar, _ := progress.NewStatsTicker(0, false, false, false, 0)
|
||||
|
||||
var firstInput, secondInput string
|
||||
workflow := &Workflow{Options: &protocols.ExecuterOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*WorkflowTemplate{
|
||||
{Executers: []*ProtocolExecuterPair{{
|
||||
workflow := &workflows.Workflow{Options: &protocols.ExecuterOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{
|
||||
{Executers: []*workflows.ProtocolExecuterPair{{
|
||||
Executer: &mockExecuter{result: true, executeHook: func(input string) {
|
||||
firstInput = input
|
||||
}}, Options: &protocols.ExecuterOptions{Progress: progressBar}},
|
||||
}},
|
||||
{Executers: []*ProtocolExecuterPair{{
|
||||
{Executers: []*workflows.ProtocolExecuterPair{{
|
||||
Executer: &mockExecuter{result: true, executeHook: func(input string) {
|
||||
secondInput = input
|
||||
}}, Options: &protocols.ExecuterOptions{Progress: progressBar}},
|
||||
}},
|
||||
}}
|
||||
|
||||
matched := workflow.RunWorkflow("https://test.com")
|
||||
engine := &Engine{}
|
||||
matched := engine.executeWorkflow("https://test.com", workflow)
|
||||
require.True(t, matched, "could not get correct match value")
|
||||
|
||||
require.Equal(t, "https://test.com", firstInput, "could not get correct first input")
|
||||
|
@ -53,21 +56,22 @@ func TestWorkflowsSubtemplates(t *testing.T) {
|
|||
progressBar, _ := progress.NewStatsTicker(0, false, false, false, 0)
|
||||
|
||||
var firstInput, secondInput string
|
||||
workflow := &Workflow{Options: &protocols.ExecuterOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*WorkflowTemplate{
|
||||
{Executers: []*ProtocolExecuterPair{{
|
||||
workflow := &workflows.Workflow{Options: &protocols.ExecuterOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{
|
||||
{Executers: []*workflows.ProtocolExecuterPair{{
|
||||
Executer: &mockExecuter{result: true, executeHook: func(input string) {
|
||||
firstInput = input
|
||||
}, outputs: []*output.InternalWrappedEvent{
|
||||
{OperatorsResult: &operators.Result{}, Results: []*output.ResultEvent{{}}},
|
||||
}}, Options: &protocols.ExecuterOptions{Progress: progressBar}},
|
||||
}, Subtemplates: []*WorkflowTemplate{{Executers: []*ProtocolExecuterPair{{
|
||||
}, Subtemplates: []*workflows.WorkflowTemplate{{Executers: []*workflows.ProtocolExecuterPair{{
|
||||
Executer: &mockExecuter{result: true, executeHook: func(input string) {
|
||||
secondInput = input
|
||||
}}, Options: &protocols.ExecuterOptions{Progress: progressBar}},
|
||||
}}}},
|
||||
}}
|
||||
|
||||
matched := workflow.RunWorkflow("https://test.com")
|
||||
engine := &Engine{}
|
||||
matched := engine.executeWorkflow("https://test.com", workflow)
|
||||
require.True(t, matched, "could not get correct match value")
|
||||
|
||||
require.Equal(t, "https://test.com", firstInput, "could not get correct first input")
|
||||
|
@ -78,19 +82,20 @@ func TestWorkflowsSubtemplatesNoMatch(t *testing.T) {
|
|||
progressBar, _ := progress.NewStatsTicker(0, false, false, false, 0)
|
||||
|
||||
var firstInput, secondInput string
|
||||
workflow := &Workflow{Options: &protocols.ExecuterOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*WorkflowTemplate{
|
||||
{Executers: []*ProtocolExecuterPair{{
|
||||
workflow := &workflows.Workflow{Options: &protocols.ExecuterOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{
|
||||
{Executers: []*workflows.ProtocolExecuterPair{{
|
||||
Executer: &mockExecuter{result: false, executeHook: func(input string) {
|
||||
firstInput = input
|
||||
}}, Options: &protocols.ExecuterOptions{Progress: progressBar}},
|
||||
}, Subtemplates: []*WorkflowTemplate{{Executers: []*ProtocolExecuterPair{{
|
||||
}, Subtemplates: []*workflows.WorkflowTemplate{{Executers: []*workflows.ProtocolExecuterPair{{
|
||||
Executer: &mockExecuter{result: true, executeHook: func(input string) {
|
||||
secondInput = input
|
||||
}}, Options: &protocols.ExecuterOptions{Progress: progressBar}},
|
||||
}}}},
|
||||
}}
|
||||
|
||||
matched := workflow.RunWorkflow("https://test.com")
|
||||
engine := &Engine{}
|
||||
matched := engine.executeWorkflow("https://test.com", workflow)
|
||||
require.False(t, matched, "could not get correct match value")
|
||||
|
||||
require.Equal(t, "https://test.com", firstInput, "could not get correct first input")
|
||||
|
@ -101,8 +106,8 @@ func TestWorkflowsSubtemplatesWithMatcher(t *testing.T) {
|
|||
progressBar, _ := progress.NewStatsTicker(0, false, false, false, 0)
|
||||
|
||||
var firstInput, secondInput string
|
||||
workflow := &Workflow{Options: &protocols.ExecuterOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*WorkflowTemplate{
|
||||
{Executers: []*ProtocolExecuterPair{{
|
||||
workflow := &workflows.Workflow{Options: &protocols.ExecuterOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{
|
||||
{Executers: []*workflows.ProtocolExecuterPair{{
|
||||
Executer: &mockExecuter{result: true, executeHook: func(input string) {
|
||||
firstInput = input
|
||||
}, outputs: []*output.InternalWrappedEvent{
|
||||
|
@ -111,14 +116,15 @@ func TestWorkflowsSubtemplatesWithMatcher(t *testing.T) {
|
|||
Extracts: map[string][]string{},
|
||||
}},
|
||||
}}, Options: &protocols.ExecuterOptions{Progress: progressBar}},
|
||||
}, Matchers: []*Matcher{{Name: "tomcat", Subtemplates: []*WorkflowTemplate{{Executers: []*ProtocolExecuterPair{{
|
||||
}, Matchers: []*workflows.Matcher{{Name: "tomcat", Subtemplates: []*workflows.WorkflowTemplate{{Executers: []*workflows.ProtocolExecuterPair{{
|
||||
Executer: &mockExecuter{result: true, executeHook: func(input string) {
|
||||
secondInput = input
|
||||
}}, Options: &protocols.ExecuterOptions{Progress: progressBar}},
|
||||
}}}}}},
|
||||
}}
|
||||
|
||||
matched := workflow.RunWorkflow("https://test.com")
|
||||
engine := &Engine{}
|
||||
matched := engine.executeWorkflow("https://test.com", workflow)
|
||||
require.True(t, matched, "could not get correct match value")
|
||||
|
||||
require.Equal(t, "https://test.com", firstInput, "could not get correct first input")
|
||||
|
@ -129,8 +135,8 @@ func TestWorkflowsSubtemplatesWithMatcherNoMatch(t *testing.T) {
|
|||
progressBar, _ := progress.NewStatsTicker(0, false, false, false, 0)
|
||||
|
||||
var firstInput, secondInput string
|
||||
workflow := &Workflow{Options: &protocols.ExecuterOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*WorkflowTemplate{
|
||||
{Executers: []*ProtocolExecuterPair{{
|
||||
workflow := &workflows.Workflow{Options: &protocols.ExecuterOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{
|
||||
{Executers: []*workflows.ProtocolExecuterPair{{
|
||||
Executer: &mockExecuter{result: true, executeHook: func(input string) {
|
||||
firstInput = input
|
||||
}, outputs: []*output.InternalWrappedEvent{
|
||||
|
@ -139,14 +145,15 @@ func TestWorkflowsSubtemplatesWithMatcherNoMatch(t *testing.T) {
|
|||
Extracts: map[string][]string{},
|
||||
}},
|
||||
}}, Options: &protocols.ExecuterOptions{Progress: progressBar}},
|
||||
}, Matchers: []*Matcher{{Name: "apache", Subtemplates: []*WorkflowTemplate{{Executers: []*ProtocolExecuterPair{{
|
||||
}, Matchers: []*workflows.Matcher{{Name: "apache", Subtemplates: []*workflows.WorkflowTemplate{{Executers: []*workflows.ProtocolExecuterPair{{
|
||||
Executer: &mockExecuter{result: true, executeHook: func(input string) {
|
||||
secondInput = input
|
||||
}}, Options: &protocols.ExecuterOptions{Progress: progressBar}},
|
||||
}}}}}},
|
||||
}}
|
||||
|
||||
matched := workflow.RunWorkflow("https://test.com")
|
||||
engine := &Engine{}
|
||||
matched := engine.executeWorkflow("https://test.com", workflow)
|
||||
require.False(t, matched, "could not get correct match value")
|
||||
|
||||
require.Equal(t, "https://test.com", firstInput, "could not get correct first input")
|
|
@ -0,0 +1,64 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
|
||||
"github.com/remeh/sizedwaitgroup"
|
||||
)
|
||||
|
||||
// WorkPool implements an execution pool for executing different
|
||||
// types of task with different concurreny requirements.
|
||||
//
|
||||
// It also allows Configuration of such requirements. This is used
|
||||
// for per-module like separate headless concurrency etc.
|
||||
type WorkPool struct {
|
||||
Headless *sizedwaitgroup.SizedWaitGroup
|
||||
Default *sizedwaitgroup.SizedWaitGroup
|
||||
config WorkPoolConfig
|
||||
}
|
||||
|
||||
// WorkPoolConfig is the configuration for workpool
|
||||
type WorkPoolConfig struct {
|
||||
// InputConcurrency is the concurrency for inputs values.
|
||||
InputConcurrency int
|
||||
// TypeConcurrency is the concurrency for the request type templates.
|
||||
TypeConcurrency int
|
||||
// HeadlessInputConcurrency is the concurrency for headless inputs values.
|
||||
HeadlessInputConcurrency int
|
||||
// TypeConcurrency is the concurrency for the headless request type templates.
|
||||
HeadlessTypeConcurrency int
|
||||
}
|
||||
|
||||
// NewWorkPool returns a new WorkPool instance
|
||||
func NewWorkPool(config WorkPoolConfig) *WorkPool {
|
||||
headlessWg := sizedwaitgroup.New(config.HeadlessTypeConcurrency)
|
||||
defaultWg := sizedwaitgroup.New(config.TypeConcurrency)
|
||||
|
||||
return &WorkPool{
|
||||
config: config,
|
||||
Headless: &headlessWg,
|
||||
Default: &defaultWg,
|
||||
}
|
||||
}
|
||||
|
||||
// Wait waits for all the workpool waitgroups to finish
|
||||
func (w *WorkPool) Wait() {
|
||||
w.Default.Wait()
|
||||
w.Headless.Wait()
|
||||
}
|
||||
|
||||
// InputWorkPool is a workpool per-input
|
||||
type InputWorkPool struct {
|
||||
Waitgroup *sizedwaitgroup.SizedWaitGroup
|
||||
}
|
||||
|
||||
// InputPool returns a workpool for an input type
|
||||
func (w *WorkPool) InputPool(templateType types.ProtocolType) *InputWorkPool {
|
||||
var count int
|
||||
if templateType == types.HeadlessProtocol {
|
||||
count = w.config.HeadlessInputConcurrency
|
||||
} else {
|
||||
count = w.config.InputConcurrency
|
||||
}
|
||||
swg := sizedwaitgroup.New(count)
|
||||
return &InputWorkPool{Waitgroup: &swg}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package dsl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
|
@ -31,21 +33,38 @@ const (
|
|||
withMaxRandArgsSize = withCutSetArgsSize
|
||||
)
|
||||
|
||||
var ErrDSLArguments = errors.New("invalid arguments provided to dsl")
|
||||
|
||||
var functions = map[string]govaluate.ExpressionFunction{
|
||||
"len": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
length := len(types.ToString(args[0]))
|
||||
return float64(length), nil
|
||||
},
|
||||
"toupper": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
return strings.ToUpper(types.ToString(args[0])), nil
|
||||
},
|
||||
"tolower": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
return strings.ToLower(types.ToString(args[0])), nil
|
||||
},
|
||||
"replace": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 3 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
return strings.ReplaceAll(types.ToString(args[0]), types.ToString(args[1]), types.ToString(args[2])), nil
|
||||
},
|
||||
"replace_regex": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 3 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
compiled, err := regexp.Compile(types.ToString(args[1]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -53,66 +72,133 @@ var functions = map[string]govaluate.ExpressionFunction{
|
|||
return compiled.ReplaceAllString(types.ToString(args[0]), types.ToString(args[2])), nil
|
||||
},
|
||||
"trim": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 2 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
return strings.Trim(types.ToString(args[0]), types.ToString(args[1])), nil
|
||||
},
|
||||
"trimleft": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 2 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
return strings.TrimLeft(types.ToString(args[0]), types.ToString(args[1])), nil
|
||||
},
|
||||
"trimright": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 2 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
return strings.TrimRight(types.ToString(args[0]), types.ToString(args[1])), nil
|
||||
},
|
||||
"trimspace": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
return strings.TrimSpace(types.ToString(args[0])), nil
|
||||
},
|
||||
"trimprefix": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 2 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
return strings.TrimPrefix(types.ToString(args[0]), types.ToString(args[1])), nil
|
||||
},
|
||||
"trimsuffix": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 2 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
return strings.TrimSuffix(types.ToString(args[0]), types.ToString(args[1])), nil
|
||||
},
|
||||
"reverse": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
return reverseString(types.ToString(args[0])), nil
|
||||
},
|
||||
// encoding
|
||||
"base64": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
sEnc := base64.StdEncoding.EncodeToString([]byte(types.ToString(args[0])))
|
||||
|
||||
return sEnc, nil
|
||||
},
|
||||
"gzip": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
buffer := &bytes.Buffer{}
|
||||
writer := gzip.NewWriter(buffer)
|
||||
if _, err := writer.Write([]byte(args[0].(string))); err != nil {
|
||||
return "", err
|
||||
}
|
||||
_ = writer.Close()
|
||||
|
||||
return buffer.String(), nil
|
||||
},
|
||||
// python encodes to base64 with lines of 76 bytes terminated by new line "\n"
|
||||
"base64_py": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
sEnc := base64.StdEncoding.EncodeToString([]byte(types.ToString(args[0])))
|
||||
return deserialization.InsertInto(sEnc, 76, '\n'), nil
|
||||
},
|
||||
"base64_decode": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
return base64.StdEncoding.DecodeString(types.ToString(args[0]))
|
||||
},
|
||||
"url_encode": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
return url.QueryEscape(types.ToString(args[0])), nil
|
||||
},
|
||||
"url_decode": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
return url.QueryUnescape(types.ToString(args[0]))
|
||||
},
|
||||
"hex_encode": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
return hex.EncodeToString([]byte(types.ToString(args[0]))), nil
|
||||
},
|
||||
"hex_decode": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
hx, _ := hex.DecodeString(types.ToString(args[0]))
|
||||
return string(hx), nil
|
||||
},
|
||||
"html_escape": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
return html.EscapeString(types.ToString(args[0])), nil
|
||||
},
|
||||
"html_unescape": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
return html.UnescapeString(types.ToString(args[0])), nil
|
||||
},
|
||||
// hashing
|
||||
"md5": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
hash := md5.Sum([]byte(types.ToString(args[0])))
|
||||
|
||||
return hex.EncodeToString(hash[:]), nil
|
||||
},
|
||||
"sha256": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
h := sha256.New()
|
||||
if _, err := h.Write([]byte(types.ToString(args[0]))); err != nil {
|
||||
return nil, err
|
||||
|
@ -120,6 +206,9 @@ var functions = map[string]govaluate.ExpressionFunction{
|
|||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
},
|
||||
"sha1": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
h := sha1.New()
|
||||
if _, err := h.Write([]byte(types.ToString(args[0]))); err != nil {
|
||||
return nil, err
|
||||
|
@ -127,13 +216,22 @@ var functions = map[string]govaluate.ExpressionFunction{
|
|||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
},
|
||||
"mmh3": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
return fmt.Sprintf("%d", int32(murmur3.Sum32WithSeed([]byte(types.ToString(args[0])), 0))), nil
|
||||
},
|
||||
// search
|
||||
"contains": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 2 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
return strings.Contains(types.ToString(args[0]), types.ToString(args[1])), nil
|
||||
},
|
||||
"regex": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 2 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
compiled, err := regexp.Compile(types.ToString(args[0]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -142,6 +240,9 @@ var functions = map[string]govaluate.ExpressionFunction{
|
|||
},
|
||||
// random generators
|
||||
"rand_char": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 2 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
chars := letters + numbers
|
||||
bad := ""
|
||||
if len(args) >= 1 {
|
||||
|
@ -154,6 +255,9 @@ var functions = map[string]govaluate.ExpressionFunction{
|
|||
return chars[rand.Intn(len(chars))], nil
|
||||
},
|
||||
"rand_base": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 3 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
l := 0
|
||||
bad := ""
|
||||
base := letters + numbers
|
||||
|
@ -171,6 +275,9 @@ var functions = map[string]govaluate.ExpressionFunction{
|
|||
return randSeq(base, l), nil
|
||||
},
|
||||
"rand_text_alphanumeric": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 2 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
l := 0
|
||||
bad := ""
|
||||
chars := letters + numbers
|
||||
|
@ -185,6 +292,9 @@ var functions = map[string]govaluate.ExpressionFunction{
|
|||
return randSeq(chars, l), nil
|
||||
},
|
||||
"rand_text_alpha": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 2 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
l := 0
|
||||
bad := ""
|
||||
chars := letters
|
||||
|
@ -199,6 +309,9 @@ var functions = map[string]govaluate.ExpressionFunction{
|
|||
return randSeq(chars, l), nil
|
||||
},
|
||||
"rand_text_numeric": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 2 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
l := 0
|
||||
bad := ""
|
||||
chars := numbers
|
||||
|
@ -213,6 +326,9 @@ var functions = map[string]govaluate.ExpressionFunction{
|
|||
return randSeq(chars, l), nil
|
||||
},
|
||||
"rand_int": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 2 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
min := 0
|
||||
max := math.MaxInt32
|
||||
|
||||
|
@ -231,16 +347,22 @@ var functions = map[string]govaluate.ExpressionFunction{
|
|||
}
|
||||
now := time.Now()
|
||||
offset := now.Add(time.Duration(seconds) * time.Second)
|
||||
return offset.Unix(), nil
|
||||
return float64(offset.Unix()), nil
|
||||
},
|
||||
// Time Functions
|
||||
"waitfor": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
seconds := args[0].(float64)
|
||||
time.Sleep(time.Duration(seconds) * time.Second)
|
||||
return true, nil
|
||||
},
|
||||
// deserialization Functions
|
||||
"generate_java_gadget": func(args ...interface{}) (interface{}, error) {
|
||||
if len(args) != 3 {
|
||||
return nil, ErrDSLArguments
|
||||
}
|
||||
gadget := args[0].(string)
|
||||
cmd := args[1].(string)
|
||||
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
package dsl
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Knetic/govaluate"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -17,3 +23,25 @@ func TestDSLURLEncodeDecode(t *testing.T) {
|
|||
require.Nil(t, err, "could not url encode")
|
||||
require.Equal(t, "&test\"", decoded, "could not get url decoded data")
|
||||
}
|
||||
|
||||
func TestDSLTimeComparison(t *testing.T) {
|
||||
compiled, err := govaluate.NewEvaluableExpressionWithFunctions("unixtime() > not_after", HelperFunctions())
|
||||
require.Nil(t, err, "could not compare time")
|
||||
|
||||
result, err := compiled.Evaluate(map[string]interface{}{"not_after": float64(time.Now().Unix() - 1000)})
|
||||
require.Nil(t, err, "could not evaluate compare time")
|
||||
require.Equal(t, true, result, "could not get url encoded data")
|
||||
}
|
||||
|
||||
func TestDSLGzipSerialize(t *testing.T) {
|
||||
compiled, err := govaluate.NewEvaluableExpressionWithFunctions("gzip(\"hello world\")", HelperFunctions())
|
||||
require.Nil(t, err, "could not compare time")
|
||||
|
||||
result, err := compiled.Evaluate(make(map[string]interface{}))
|
||||
require.Nil(t, err, "could not evaluate compare time")
|
||||
|
||||
reader, _ := gzip.NewReader(strings.NewReader(types.ToString(result)))
|
||||
data, _ := ioutil.ReadAll(reader)
|
||||
|
||||
require.Equal(t, "hello world", string(data), "could not get gzip encoded data")
|
||||
}
|
||||
|
|
|
@ -42,11 +42,6 @@ func (e *Extractor) CompileExtractors() error {
|
|||
e.jsonCompiled = append(e.jsonCompiled, compiled)
|
||||
}
|
||||
|
||||
// Set up the part of the request to match, if any.
|
||||
if e.Part == "" {
|
||||
e.Part = "body"
|
||||
}
|
||||
|
||||
if e.CaseInsensitive {
|
||||
if e.Type != "kval" {
|
||||
return fmt.Errorf("case-insensitive flag is supported only for 'kval' extractors (not '%s')", e.Type)
|
||||
|
|
|
@ -31,7 +31,7 @@ func (m *Matcher) CompileMatchers() error {
|
|||
}
|
||||
// By default, match on body if user hasn't provided any specific items
|
||||
if m.Part == "" {
|
||||
m.Part = "body"
|
||||
m.Part = "response"
|
||||
}
|
||||
|
||||
// Compile the regexes
|
||||
|
|
|
@ -18,7 +18,7 @@ func NewLoader(options *protocols.ExecuterOptions) (model.WorkflowLoader, error)
|
|||
tagFilter := filter.New(&filter.Config{
|
||||
Tags: options.Options.Tags,
|
||||
ExcludeTags: options.Options.ExcludeTags,
|
||||
Authors: options.Options.Author,
|
||||
Authors: options.Options.Authors,
|
||||
Severities: options.Options.Severities,
|
||||
IncludeTags: options.Options.IncludeTags,
|
||||
})
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
package generators
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/jsonschema"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// AttackType is the type of attack for payloads
|
||||
type AttackType int
|
||||
|
||||
// Supported values for the ProtocolType
|
||||
const (
|
||||
// BatteringRamAttack replaces same payload into all of the defined payload positions at once.
|
||||
BatteringRamAttack AttackType = iota + 1
|
||||
// PitchForkAttack replaces variables with positional value from multiple wordlists
|
||||
PitchForkAttack
|
||||
// ClusterbombAttack replaces variables with all possible combinations of values
|
||||
ClusterbombAttack
|
||||
limit
|
||||
)
|
||||
|
||||
// attackTypeMappings is a table for conversion of attack type from string.
|
||||
var attackTypeMappings = map[AttackType]string{
|
||||
BatteringRamAttack: "batteringram",
|
||||
PitchForkAttack: "pitchfork",
|
||||
ClusterbombAttack: "clusterbomb",
|
||||
}
|
||||
|
||||
func GetSupportedAttackTypes() []AttackType {
|
||||
var result []AttackType
|
||||
for index := AttackType(1); index < limit; index++ {
|
||||
result = append(result, index)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func toAttackType(valueToMap string) (AttackType, error) {
|
||||
normalizedValue := normalizeValue(valueToMap)
|
||||
for key, currentValue := range attackTypeMappings {
|
||||
if normalizedValue == currentValue {
|
||||
return key, nil
|
||||
}
|
||||
}
|
||||
return -1, errors.New("invalid attack type: " + valueToMap)
|
||||
}
|
||||
|
||||
func normalizeValue(value string) string {
|
||||
return strings.TrimSpace(strings.ToLower(value))
|
||||
}
|
||||
|
||||
func (t AttackType) String() string {
|
||||
return attackTypeMappings[t]
|
||||
}
|
||||
|
||||
// AttackTypeHolder is used to hold internal type of the protocol
|
||||
type AttackTypeHolder struct {
|
||||
Value AttackType
|
||||
}
|
||||
|
||||
func (holder AttackTypeHolder) JSONSchemaType() *jsonschema.Type {
|
||||
gotType := &jsonschema.Type{
|
||||
Type: "string",
|
||||
Title: "type of the attack",
|
||||
Description: "Type of the attack",
|
||||
}
|
||||
for _, types := range GetSupportedAttackTypes() {
|
||||
gotType.Enum = append(gotType.Enum, types.String())
|
||||
}
|
||||
return gotType
|
||||
}
|
||||
|
||||
func (holder *AttackTypeHolder) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var marshalledTypes string
|
||||
if err := unmarshal(&marshalledTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
computedType, err := toAttackType(marshalledTypes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
holder.Value = computedType
|
||||
return nil
|
||||
}
|
||||
|
||||
func (holder *AttackTypeHolder) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(holder.Value.String())
|
||||
}
|
||||
|
||||
func (holder AttackTypeHolder) MarshalYAML() (interface{}, error) {
|
||||
return holder.Value.String(), nil
|
||||
}
|
|
@ -2,49 +2,53 @@
|
|||
|
||||
package generators
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/catalog"
|
||||
)
|
||||
|
||||
// Generator is the generator struct for generating payloads
|
||||
type Generator struct {
|
||||
Type Type
|
||||
// PayloadGenerator is the generator struct for generating payloads
|
||||
type PayloadGenerator struct {
|
||||
Type AttackType
|
||||
payloads map[string][]string
|
||||
}
|
||||
|
||||
// Type is type of attack
|
||||
type Type int
|
||||
|
||||
const (
|
||||
// Batteringram replaces same payload into all of the defined payload positions at once.
|
||||
BatteringRam Type = iota + 1
|
||||
// PitchFork replaces variables with positional value from multiple wordlists
|
||||
PitchFork
|
||||
// ClusterBomb replaces variables with all possible combinations of values
|
||||
ClusterBomb
|
||||
)
|
||||
|
||||
// StringToType is a table for conversion of attack type from string.
|
||||
var StringToType = map[string]Type{
|
||||
"batteringram": BatteringRam,
|
||||
"pitchfork": PitchFork,
|
||||
"clusterbomb": ClusterBomb,
|
||||
}
|
||||
|
||||
// New creates a new generator structure for payload generation
|
||||
func New(payloads map[string]interface{}, payloadType Type, templatePath string) (*Generator, error) {
|
||||
generator := &Generator{}
|
||||
func New(payloads map[string]interface{}, attackType AttackType, templatePath string, catalog *catalog.Catalog) (*PayloadGenerator, error) {
|
||||
if attackType.String() == "" {
|
||||
attackType = BatteringRamAttack
|
||||
}
|
||||
|
||||
// Resolve payload paths if they are files.
|
||||
payloadsFinal := make(map[string]interface{})
|
||||
for name, payload := range payloads {
|
||||
payloadsFinal[name] = payload
|
||||
}
|
||||
for name, payload := range payloads {
|
||||
payloadStr, ok := payload.(string)
|
||||
if ok {
|
||||
final, resolveErr := catalog.ResolvePath(payloadStr, templatePath)
|
||||
if resolveErr != nil {
|
||||
return nil, errors.Wrap(resolveErr, "could not read payload file")
|
||||
}
|
||||
payloadsFinal[name] = final
|
||||
}
|
||||
}
|
||||
|
||||
generator := &PayloadGenerator{}
|
||||
if err := generator.validate(payloads, templatePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
compiled, err := loadPayloads(payloads)
|
||||
compiled, err := loadPayloads(payloadsFinal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
generator.Type = payloadType
|
||||
generator.Type = attackType
|
||||
generator.payloads = compiled
|
||||
|
||||
// Validate the batteringram payload set
|
||||
if payloadType == BatteringRam {
|
||||
if attackType == BatteringRamAttack {
|
||||
if len(payloads) != 1 {
|
||||
return nil, errors.New("batteringram must have single payload set")
|
||||
}
|
||||
|
@ -54,7 +58,7 @@ func New(payloads map[string]interface{}, payloadType Type, templatePath string)
|
|||
|
||||
// Iterator is a single instance of an iterator for a generator structure
|
||||
type Iterator struct {
|
||||
Type Type
|
||||
Type AttackType
|
||||
position int
|
||||
msbIterator int
|
||||
total int
|
||||
|
@ -62,7 +66,7 @@ type Iterator struct {
|
|||
}
|
||||
|
||||
// NewIterator creates a new iterator for the payloads generator
|
||||
func (g *Generator) NewIterator() *Iterator {
|
||||
func (g *PayloadGenerator) NewIterator() *Iterator {
|
||||
var payloads []*payloadIterator
|
||||
|
||||
for name, values := range g.payloads {
|
||||
|
@ -95,18 +99,18 @@ func (i *Iterator) Remaining() int {
|
|||
func (i *Iterator) Total() int {
|
||||
count := 0
|
||||
switch i.Type {
|
||||
case BatteringRam:
|
||||
case BatteringRamAttack:
|
||||
for _, p := range i.payloads {
|
||||
count += len(p.values)
|
||||
}
|
||||
case PitchFork:
|
||||
case PitchForkAttack:
|
||||
count = len(i.payloads[0].values)
|
||||
for _, p := range i.payloads {
|
||||
if count > len(p.values) {
|
||||
count = len(p.values)
|
||||
}
|
||||
}
|
||||
case ClusterBomb:
|
||||
case ClusterbombAttack:
|
||||
count = 1
|
||||
for _, p := range i.payloads {
|
||||
count *= len(p.values)
|
||||
|
@ -118,11 +122,11 @@ func (i *Iterator) Total() int {
|
|||
// Value returns the next value for an iterator
|
||||
func (i *Iterator) Value() (map[string]interface{}, bool) {
|
||||
switch i.Type {
|
||||
case BatteringRam:
|
||||
case BatteringRamAttack:
|
||||
return i.batteringRamValue()
|
||||
case PitchFork:
|
||||
case PitchForkAttack:
|
||||
return i.pitchforkValue()
|
||||
case ClusterBomb:
|
||||
case ClusterbombAttack:
|
||||
return i.clusterbombValue()
|
||||
default:
|
||||
return i.batteringRamValue()
|
||||
|
|
|
@ -3,13 +3,15 @@ package generators
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/catalog"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBatteringRamGenerator(t *testing.T) {
|
||||
usernames := []string{"admin", "password"}
|
||||
|
||||
generator, err := New(map[string]interface{}{"username": usernames}, BatteringRam, "")
|
||||
catalogInstance := catalog.New("")
|
||||
generator, err := New(map[string]interface{}{"username": usernames}, BatteringRamAttack, "", catalogInstance)
|
||||
require.Nil(t, err, "could not create generator")
|
||||
|
||||
iterator := generator.NewIterator()
|
||||
|
@ -28,7 +30,8 @@ func TestPitchforkGenerator(t *testing.T) {
|
|||
usernames := []string{"admin", "token"}
|
||||
passwords := []string{"password1", "password2", "password3"}
|
||||
|
||||
generator, err := New(map[string]interface{}{"username": usernames, "password": passwords}, PitchFork, "")
|
||||
catalogInstance := catalog.New("")
|
||||
generator, err := New(map[string]interface{}{"username": usernames, "password": passwords}, PitchForkAttack, "", catalogInstance)
|
||||
require.Nil(t, err, "could not create generator")
|
||||
|
||||
iterator := generator.NewIterator()
|
||||
|
@ -49,7 +52,8 @@ func TestClusterbombGenerator(t *testing.T) {
|
|||
usernames := []string{"admin"}
|
||||
passwords := []string{"admin", "password", "token"}
|
||||
|
||||
generator, err := New(map[string]interface{}{"username": usernames, "password": passwords}, ClusterBomb, "")
|
||||
catalogInstance := catalog.New("")
|
||||
generator, err := New(map[string]interface{}{"username": usernames, "password": passwords}, ClusterbombAttack, "", catalogInstance)
|
||||
require.Nil(t, err, "could not create generator")
|
||||
|
||||
iterator := generator.NewIterator()
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
)
|
||||
|
||||
// validate validates the payloads if any.
|
||||
func (g *Generator) validate(payloads map[string]interface{}, templatePath string) error {
|
||||
func (g *PayloadGenerator) validate(payloads map[string]interface{}, templatePath string) error {
|
||||
for name, payload := range payloads {
|
||||
switch pt := payload.(type) {
|
||||
case string:
|
||||
|
|
|
@ -48,15 +48,19 @@ func gadgetEncodingHelper(returnData []byte, encoding string) string {
|
|||
return hex.EncodeToString(returnData)
|
||||
case "gzip":
|
||||
buffer := &bytes.Buffer{}
|
||||
if _, err := gzip.NewWriter(buffer).Write(returnData); err != nil {
|
||||
writer := gzip.NewWriter(buffer)
|
||||
if _, err := writer.Write(returnData); err != nil {
|
||||
return ""
|
||||
}
|
||||
_ = writer.Close()
|
||||
return buffer.String()
|
||||
case "gzip-base64":
|
||||
buffer := &bytes.Buffer{}
|
||||
if _, err := gzip.NewWriter(buffer).Write(returnData); err != nil {
|
||||
writer := gzip.NewWriter(buffer)
|
||||
if _, err := writer.Write(returnData); err != nil {
|
||||
return ""
|
||||
}
|
||||
_ = writer.Close()
|
||||
return urlsafeBase64Encode(buffer.Bytes())
|
||||
case "base64-raw":
|
||||
return base64.StdEncoding.EncodeToString(returnData)
|
||||
|
|
|
@ -103,6 +103,20 @@ func New(options *Options) (*Client, error) {
|
|||
return interactClient, nil
|
||||
}
|
||||
|
||||
// NewDefaultOptions returns the default options for interactsh client
|
||||
func NewDefaultOptions(output output.Writer, reporting *reporting.Client, progress progress.Progress) *Options {
|
||||
return &Options{
|
||||
ServerURL: "https://interactsh.com",
|
||||
CacheSize: 5000,
|
||||
Eviction: 60 * time.Second,
|
||||
ColldownPeriod: 5 * time.Second,
|
||||
PollDuration: 5 * time.Second,
|
||||
Output: output,
|
||||
IssuesClient: reporting,
|
||||
Progress: progress,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) firstTimeInitializeClient() error {
|
||||
interactsh, err := client.New(&client.Options{
|
||||
ServerURL: c.options.ServerURL,
|
||||
|
|
|
@ -32,7 +32,7 @@ type Request struct {
|
|||
// - value: "\"{{FQDN}}\""
|
||||
Name string `yaml:"name,omitempty" jsonschema:"title=hostname to make dns request for,description=Name is the Hostname to make DNS request for"`
|
||||
// description: |
|
||||
// Type is the type of DNS request to make.
|
||||
// RequestType is the type of DNS request to make.
|
||||
// values:
|
||||
// - "A"
|
||||
// - "NS"
|
||||
|
@ -43,7 +43,7 @@ type Request struct {
|
|||
// - "MX"
|
||||
// - "TXT"
|
||||
// - "AAAA"
|
||||
Type string `yaml:"type,omitempty" jsonschema:"title=type of dns request to make,description=Type is the type of DNS request to make,enum=A,enum=NS,enum=DS,enum=CNAME,enum=SOA,enum=PTR,enum=MX,enum=TXT,enum=AAAA"`
|
||||
RequestType string `yaml:"type,omitempty" jsonschema:"title=type of dns request to make,description=Type is the type of DNS request to make,enum=A,enum=NS,enum=DS,enum=CNAME,enum=SOA,enum=PTR,enum=MX,enum=TXT,enum=AAAA"`
|
||||
// description: |
|
||||
// Class is the class of the DNS request.
|
||||
//
|
||||
|
@ -111,7 +111,7 @@ func (request *Request) Compile(options *protocols.ExecuterOptions) error {
|
|||
}
|
||||
request.class = classToInt(request.Class)
|
||||
request.options = options
|
||||
request.question = questionTypeToInt(request.Type)
|
||||
request.question = questionTypeToInt(request.RequestType)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -5,9 +5,9 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
func TestGenerateDNSVariables(t *testing.T) {
|
||||
|
@ -27,12 +27,12 @@ func TestDNSCompileMake(t *testing.T) {
|
|||
testutils.Init(options)
|
||||
const templateID = "testing-dns"
|
||||
request := &Request{
|
||||
Type: "A",
|
||||
Class: "INET",
|
||||
Retries: 5,
|
||||
ID: templateID,
|
||||
Recursion: false,
|
||||
Name: "{{FQDN}}",
|
||||
RequestType: "A",
|
||||
Class: "INET",
|
||||
Retries: 5,
|
||||
ID: templateID,
|
||||
Recursion: false,
|
||||
Name: "{{FQDN}}",
|
||||
}
|
||||
executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{
|
||||
ID: templateID,
|
||||
|
|
|
@ -8,13 +8,13 @@ import (
|
|||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
func TestResponseToDSLMap(t *testing.T) {
|
||||
|
@ -23,12 +23,12 @@ func TestResponseToDSLMap(t *testing.T) {
|
|||
testutils.Init(options)
|
||||
templateID := "testing-dns"
|
||||
request := &Request{
|
||||
Type: "A",
|
||||
Class: "INET",
|
||||
Retries: 5,
|
||||
ID: templateID,
|
||||
Recursion: false,
|
||||
Name: "{{FQDN}}",
|
||||
RequestType: "A",
|
||||
Class: "INET",
|
||||
Retries: 5,
|
||||
ID: templateID,
|
||||
Recursion: false,
|
||||
Name: "{{FQDN}}",
|
||||
}
|
||||
executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{
|
||||
ID: templateID,
|
||||
|
@ -55,12 +55,12 @@ func TestDNSOperatorMatch(t *testing.T) {
|
|||
testutils.Init(options)
|
||||
templateID := "testing-dns"
|
||||
request := &Request{
|
||||
Type: "A",
|
||||
Class: "INET",
|
||||
Retries: 5,
|
||||
ID: templateID,
|
||||
Recursion: false,
|
||||
Name: "{{FQDN}}",
|
||||
RequestType: "A",
|
||||
Class: "INET",
|
||||
Retries: 5,
|
||||
ID: templateID,
|
||||
Recursion: false,
|
||||
Name: "{{FQDN}}",
|
||||
}
|
||||
executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{
|
||||
ID: templateID,
|
||||
|
@ -166,12 +166,12 @@ func TestDNSOperatorExtract(t *testing.T) {
|
|||
testutils.Init(options)
|
||||
templateID := "testing-dns"
|
||||
request := &Request{
|
||||
Type: "A",
|
||||
Class: "INET",
|
||||
Retries: 5,
|
||||
ID: templateID,
|
||||
Recursion: false,
|
||||
Name: "{{FQDN}}",
|
||||
RequestType: "A",
|
||||
Class: "INET",
|
||||
Retries: 5,
|
||||
ID: templateID,
|
||||
Recursion: false,
|
||||
Name: "{{FQDN}}",
|
||||
}
|
||||
executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{
|
||||
ID: templateID,
|
||||
|
@ -223,12 +223,12 @@ func TestDNSMakeResult(t *testing.T) {
|
|||
testutils.Init(options)
|
||||
templateID := "testing-dns"
|
||||
request := &Request{
|
||||
Type: "A",
|
||||
Class: "INET",
|
||||
Retries: 5,
|
||||
ID: templateID,
|
||||
Recursion: false,
|
||||
Name: "{{FQDN}}",
|
||||
RequestType: "A",
|
||||
Class: "INET",
|
||||
Retries: 5,
|
||||
ID: templateID,
|
||||
Recursion: false,
|
||||
Name: "{{FQDN}}",
|
||||
Operators: operators.Operators{
|
||||
Matchers: []*matchers.Matcher{{
|
||||
Name: "test",
|
||||
|
|
|
@ -12,10 +12,16 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/responsehighlighter"
|
||||
templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
|
||||
)
|
||||
|
||||
var _ protocols.Request = &Request{}
|
||||
|
||||
// Type returns the type of the protocol request
|
||||
func (request *Request) Type() templateTypes.ProtocolType {
|
||||
return templateTypes.DNSProtocol
|
||||
}
|
||||
|
||||
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
|
||||
func (request *Request) ExecuteWithResults(input string, metadata /*TODO review unused parameter*/, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
|
||||
// Parse the URL and return domain if URL.
|
||||
|
@ -29,7 +35,7 @@ func (request *Request) ExecuteWithResults(input string, metadata /*TODO review
|
|||
// Compile each request for the template based on the URL
|
||||
compiledRequest, err := request.Make(domain)
|
||||
if err != nil {
|
||||
request.options.Output.Request(request.options.TemplatePath, domain, "dns", err)
|
||||
request.options.Output.Request(request.options.TemplatePath, domain, request.Type().String(), err)
|
||||
request.options.Progress.IncrementFailedRequestsBy(1)
|
||||
return errors.Wrap(err, "could not build request")
|
||||
}
|
||||
|
@ -47,7 +53,7 @@ func (request *Request) ExecuteWithResults(input string, metadata /*TODO review
|
|||
// Send the request to the target servers
|
||||
response, err := request.dnsClient.Do(compiledRequest)
|
||||
if err != nil {
|
||||
request.options.Output.Request(request.options.TemplatePath, domain, "dns", err)
|
||||
request.options.Output.Request(request.options.TemplatePath, domain, request.Type().String(), err)
|
||||
request.options.Progress.IncrementFailedRequestsBy(1)
|
||||
}
|
||||
if response == nil {
|
||||
|
@ -55,7 +61,7 @@ func (request *Request) ExecuteWithResults(input string, metadata /*TODO review
|
|||
}
|
||||
request.options.Progress.IncrementRequests()
|
||||
|
||||
request.options.Output.Request(request.options.TemplatePath, domain, "dns", err)
|
||||
request.options.Output.Request(request.options.TemplatePath, domain, request.Type().String(), err)
|
||||
gologger.Verbose().Msgf("[%s] Sent DNS request to %s\n", request.options.TemplateID, domain)
|
||||
|
||||
outputEvent := request.responseToDSLMap(compiledRequest, response, input, input)
|
||||
|
|
|
@ -5,13 +5,13 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
func TestDNSExecuteWithResults(t *testing.T) {
|
||||
|
@ -20,12 +20,12 @@ func TestDNSExecuteWithResults(t *testing.T) {
|
|||
testutils.Init(options)
|
||||
templateID := "testing-dns"
|
||||
request := &Request{
|
||||
Type: "A",
|
||||
Class: "INET",
|
||||
Retries: 5,
|
||||
ID: templateID,
|
||||
Recursion: false,
|
||||
Name: "{{FQDN}}",
|
||||
RequestType: "A",
|
||||
Class: "INET",
|
||||
Retries: 5,
|
||||
ID: templateID,
|
||||
Recursion: false,
|
||||
Name: "{{FQDN}}",
|
||||
Operators: operators.Operators{
|
||||
Matchers: []*matchers.Matcher{{
|
||||
Name: "test",
|
||||
|
|
|
@ -5,9 +5,9 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
func TestFileCompile(t *testing.T) {
|
||||
|
|
|
@ -8,9 +8,9 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
func TestFindInputPaths(t *testing.T) {
|
||||
|
|
|
@ -5,13 +5,13 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
func TestResponseToDSLMap(t *testing.T) {
|
||||
|
|
|
@ -14,10 +14,16 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/responsehighlighter"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/tostring"
|
||||
templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
|
||||
)
|
||||
|
||||
var _ protocols.Request = &Request{}
|
||||
|
||||
// Type returns the type of the protocol request
|
||||
func (request *Request) Type() templateTypes.ProtocolType {
|
||||
return templateTypes.FileProtocol
|
||||
}
|
||||
|
||||
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
|
||||
func (request *Request) ExecuteWithResults(input string, metadata /*TODO review unused parameter*/, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
|
||||
wg := sizedwaitgroup.New(request.options.Options.BulkSize)
|
||||
|
@ -67,7 +73,7 @@ func (request *Request) ExecuteWithResults(input string, metadata /*TODO review
|
|||
})
|
||||
wg.Wait()
|
||||
if err != nil {
|
||||
request.options.Output.Request(request.options.TemplatePath, input, "file", err)
|
||||
request.options.Output.Request(request.options.TemplatePath, input, request.Type().String(), err)
|
||||
request.options.Progress.IncrementFailedRequestsBy(1)
|
||||
return errors.Wrap(err, "could not send file request")
|
||||
}
|
||||
|
|
|
@ -8,13 +8,13 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
func TestFileExecuteWithResults(t *testing.T) {
|
||||
|
|
|
@ -12,15 +12,21 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/responsehighlighter"
|
||||
templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
|
||||
)
|
||||
|
||||
var _ protocols.Request = &Request{}
|
||||
|
||||
// Type returns the type of the protocol request
|
||||
func (request *Request) Type() templateTypes.ProtocolType {
|
||||
return templateTypes.HeadlessProtocol
|
||||
}
|
||||
|
||||
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
|
||||
func (request *Request) ExecuteWithResults(inputURL string, metadata, previous output.InternalEvent /*TODO review unused parameter*/, callback protocols.OutputEventCallback) error {
|
||||
instance, err := request.options.Browser.NewInstance()
|
||||
if err != nil {
|
||||
request.options.Output.Request(request.options.TemplatePath, inputURL, "headless", err)
|
||||
request.options.Output.Request(request.options.TemplatePath, inputURL, request.Type().String(), err)
|
||||
request.options.Progress.IncrementFailedRequestsBy(1)
|
||||
return errors.Wrap(err, "could get html element")
|
||||
}
|
||||
|
@ -28,19 +34,19 @@ func (request *Request) ExecuteWithResults(inputURL string, metadata, previous o
|
|||
|
||||
parsedURL, err := url.Parse(inputURL)
|
||||
if err != nil {
|
||||
request.options.Output.Request(request.options.TemplatePath, inputURL, "headless", err)
|
||||
request.options.Output.Request(request.options.TemplatePath, inputURL, request.Type().String(), err)
|
||||
request.options.Progress.IncrementFailedRequestsBy(1)
|
||||
return errors.Wrap(err, "could get html element")
|
||||
}
|
||||
out, page, err := instance.Run(parsedURL, request.Steps, time.Duration(request.options.Options.PageTimeout)*time.Second)
|
||||
if err != nil {
|
||||
request.options.Output.Request(request.options.TemplatePath, inputURL, "headless", err)
|
||||
request.options.Output.Request(request.options.TemplatePath, inputURL, request.Type().String(), err)
|
||||
request.options.Progress.IncrementFailedRequestsBy(1)
|
||||
return errors.Wrap(err, "could get html element")
|
||||
}
|
||||
defer page.Close()
|
||||
|
||||
request.options.Output.Request(request.options.TemplatePath, inputURL, "headless", nil)
|
||||
request.options.Output.Request(request.options.TemplatePath, inputURL, request.Type().String(), nil)
|
||||
request.options.Progress.IncrementRequests()
|
||||
gologger.Verbose().Msgf("Sent Headless request to %s", inputURL)
|
||||
|
||||
|
|
|
@ -5,10 +5,11 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -134,7 +135,7 @@ func TestMakeRequestFromRawWithPayloads(t *testing.T) {
|
|||
"username": []string{"admin"},
|
||||
"password": []string{"admin", "guest", "password", "test", "12345", "123456"},
|
||||
},
|
||||
AttackType: "clusterbomb",
|
||||
AttackType: generators.AttackTypeHolder{Value: generators.ClusterbombAttack},
|
||||
Raw: []string{`GET /manager/html HTTP/1.1
|
||||
Host: {{Hostname}}
|
||||
User-Agent: Nuclei - Open-source project (github.com/projectdiscovery/nuclei)
|
||||
|
@ -173,7 +174,7 @@ func TestMakeRequestFromRawPayloadExpressions(t *testing.T) {
|
|||
"username": []string{"admin"},
|
||||
"password": []string{"admin", "guest", "password", "test", "12345", "123456"},
|
||||
},
|
||||
AttackType: "clusterbomb",
|
||||
AttackType: generators.AttackTypeHolder{Value: generators.ClusterbombAttack},
|
||||
Raw: []string{`GET /manager/html HTTP/1.1
|
||||
Host: {{Hostname}}
|
||||
User-Agent: Nuclei - Open-source project (github.com/projectdiscovery/nuclei)
|
||||
|
|
|
@ -52,7 +52,7 @@ type Request struct {
|
|||
// - "batteringram"
|
||||
// - "pitchfork"
|
||||
// - "clusterbomb"
|
||||
AttackType string `yaml:"attack,omitempty" jsonschema:"title=attack is the payload combination,description=Attack is the type of payload combinations to perform,enum=batteringram,enum=pitchfork,enum=clusterbomb"`
|
||||
AttackType generators.AttackTypeHolder `yaml:"attack,omitempty" jsonschema:"title=attack is the payload combination,description=Attack is the type of payload combinations to perform,enum=batteringram,enum=pitchfork,enum=clusterbomb"`
|
||||
// description: |
|
||||
// Method is the HTTP Request Method.
|
||||
// values:
|
||||
|
@ -129,10 +129,9 @@ type Request struct {
|
|||
CompiledOperators *operators.Operators `yaml:"-"`
|
||||
|
||||
options *protocols.ExecuterOptions
|
||||
attackType generators.Type
|
||||
totalRequests int
|
||||
customHeaders map[string]string
|
||||
generator *generators.Generator // optional, only enabled when using payloads
|
||||
generator *generators.PayloadGenerator // optional, only enabled when using payloads
|
||||
httpClient *retryablehttp.Client
|
||||
rawhttpClient *rawhttp.Client
|
||||
dynamicValues map[string]interface{}
|
||||
|
@ -267,28 +266,7 @@ func (request *Request) Compile(options *protocols.ExecuterOptions) error {
|
|||
}
|
||||
|
||||
if len(request.Payloads) > 0 {
|
||||
attackType := request.AttackType
|
||||
if attackType == "" {
|
||||
attackType = "batteringram"
|
||||
}
|
||||
var ok bool
|
||||
request.attackType, ok = generators.StringToType[attackType]
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid attack type provided: %s", attackType)
|
||||
}
|
||||
|
||||
// Resolve payload paths if they are files.
|
||||
for name, payload := range request.Payloads {
|
||||
payloadStr, ok := payload.(string)
|
||||
if ok {
|
||||
final, resolveErr := options.Catalog.ResolvePath(payloadStr, options.TemplatePath)
|
||||
if resolveErr != nil {
|
||||
return errors.Wrap(resolveErr, "could not read payload file")
|
||||
}
|
||||
request.Payloads[name] = final
|
||||
}
|
||||
}
|
||||
request.generator, err = generators.New(request.Payloads, request.attackType, request.options.TemplatePath)
|
||||
request.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, request.options.Catalog)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not parse payloads")
|
||||
}
|
||||
|
|
|
@ -5,9 +5,10 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
func TestHTTPCompile(t *testing.T) {
|
||||
|
@ -22,7 +23,7 @@ func TestHTTPCompile(t *testing.T) {
|
|||
"username": []string{"admin"},
|
||||
"password": []string{"admin", "guest", "password", "test", "12345", "123456"},
|
||||
},
|
||||
AttackType: "clusterbomb",
|
||||
AttackType: generators.AttackTypeHolder{Value: generators.ClusterbombAttack},
|
||||
Raw: []string{`GET /manager/html HTTP/1.1
|
||||
Host: {{Hostname}}
|
||||
User-Agent: Nuclei - Open-source project (github.com/projectdiscovery/nuclei)
|
||||
|
|
|
@ -76,6 +76,9 @@ func (request *Request) Extract(data map[string]interface{}, extractor *extracto
|
|||
|
||||
// getMatchPart returns the match part honoring "all" matchers + others.
|
||||
func (request *Request) getMatchPart(part string, data output.InternalEvent) (string, bool) {
|
||||
if part == "" {
|
||||
part = "body"
|
||||
}
|
||||
if part == "header" {
|
||||
part = "all_headers"
|
||||
}
|
||||
|
|
|
@ -7,13 +7,13 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
func TestResponseToDSLMap(t *testing.T) {
|
||||
|
|
|
@ -28,12 +28,18 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/tostring"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/httpclientpool"
|
||||
templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
|
||||
"github.com/projectdiscovery/rawhttp"
|
||||
"github.com/projectdiscovery/stringsutil"
|
||||
)
|
||||
|
||||
const defaultMaxWorkers = 150
|
||||
|
||||
// Type returns the type of the protocol request
|
||||
func (request *Request) Type() templateTypes.ProtocolType {
|
||||
return templateTypes.HTTPProtocol
|
||||
}
|
||||
|
||||
// executeRaceRequest executes race condition request for a URL
|
||||
func (request *Request) executeRaceRequest(reqURL string, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
|
||||
var generatedRequests []*generatedRequest
|
||||
|
@ -351,7 +357,7 @@ func (request *Request) executeRequest(reqURL string, generatedRequest *generate
|
|||
_, _ = io.CopyN(ioutil.Discard, resp.Body, drainReqSize)
|
||||
resp.Body.Close()
|
||||
}
|
||||
request.options.Output.Request(request.options.TemplatePath, formedURL, "http", err)
|
||||
request.options.Output.Request(request.options.TemplatePath, formedURL, request.Type().String(), err)
|
||||
request.options.Progress.IncrementErrorsBy(1)
|
||||
|
||||
// If we have interactsh markers and request times out, still send
|
||||
|
@ -389,7 +395,7 @@ func (request *Request) executeRequest(reqURL string, generatedRequest *generate
|
|||
}
|
||||
|
||||
gologger.Verbose().Msgf("[%s] Sent HTTP request to %s", request.options.TemplateID, formedURL)
|
||||
request.options.Output.Request(request.options.TemplatePath, formedURL, "http", err)
|
||||
request.options.Output.Request(request.options.TemplatePath, formedURL, request.Type().String(), err)
|
||||
|
||||
duration := time.Since(timeStart)
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package http
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/catalog"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -28,10 +29,11 @@ func TestRequestGeneratorClusterBombSingle(t *testing.T) {
|
|||
|
||||
req := &Request{
|
||||
Payloads: map[string]interface{}{"username": []string{"admin", "tomcat", "manager"}, "password": []string{"password", "test", "secret"}},
|
||||
attackType: generators.ClusterBomb,
|
||||
AttackType: generators.AttackTypeHolder{Value: generators.ClusterbombAttack},
|
||||
Raw: []string{`GET /{{username}}:{{password}} HTTP/1.1`},
|
||||
}
|
||||
req.generator, err = generators.New(req.Payloads, req.attackType, "")
|
||||
catalogInstance := catalog.New("")
|
||||
req.generator, err = generators.New(req.Payloads, req.AttackType.Value, "", catalogInstance)
|
||||
require.Nil(t, err, "could not create generator")
|
||||
|
||||
generator := req.newGenerator()
|
||||
|
@ -51,10 +53,11 @@ func TestRequestGeneratorClusterBombMultipleRaw(t *testing.T) {
|
|||
|
||||
req := &Request{
|
||||
Payloads: map[string]interface{}{"username": []string{"admin", "tomcat", "manager"}, "password": []string{"password", "test", "secret"}},
|
||||
attackType: generators.ClusterBomb,
|
||||
AttackType: generators.AttackTypeHolder{Value: generators.ClusterbombAttack},
|
||||
Raw: []string{`GET /{{username}}:{{password}} HTTP/1.1`, `GET /{{username}}@{{password}} HTTP/1.1`},
|
||||
}
|
||||
req.generator, err = generators.New(req.Payloads, req.attackType, "")
|
||||
catalogInstance := catalog.New("")
|
||||
req.generator, err = generators.New(req.Payloads, req.AttackType.Value, "", catalogInstance)
|
||||
require.Nil(t, err, "could not create generator")
|
||||
|
||||
generator := req.newGenerator()
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
|
@ -41,7 +40,7 @@ type Request struct {
|
|||
// - "batteringram"
|
||||
// - "pitchfork"
|
||||
// - "clusterbomb"
|
||||
AttackType string `yaml:"attack,omitempty" jsonschema:"title=attack is the payload combination,description=Attack is the type of payload combinations to perform,enum=batteringram,enum=pitchfork,enum=clusterbomb"`
|
||||
AttackType generators.AttackTypeHolder `yaml:"attack,omitempty" jsonschema:"title=attack is the payload combination,description=Attack is the type of payload combinations to perform,enum=batteringram,enum=pitchfork,enum=clusterbomb"`
|
||||
// description: |
|
||||
// Payloads contains any payloads for the current request.
|
||||
//
|
||||
|
@ -76,8 +75,7 @@ type Request struct {
|
|||
operators.Operators `yaml:",inline,omitempty"`
|
||||
CompiledOperators *operators.Operators `yaml:"-"`
|
||||
|
||||
generator *generators.Generator
|
||||
attackType generators.Type
|
||||
generator *generators.PayloadGenerator
|
||||
// cache any variables that may be needed for operation.
|
||||
dialer *fastdialer.Dialer
|
||||
options *protocols.ExecuterOptions
|
||||
|
@ -186,28 +184,7 @@ func (request *Request) Compile(options *protocols.ExecuterOptions) error {
|
|||
}
|
||||
|
||||
if len(request.Payloads) > 0 {
|
||||
attackType := request.AttackType
|
||||
if attackType == "" {
|
||||
attackType = "batteringram"
|
||||
}
|
||||
var ok bool
|
||||
request.attackType, ok = generators.StringToType[attackType]
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid attack type provided: %s", attackType)
|
||||
}
|
||||
|
||||
// Resolve payload paths if they are files.
|
||||
for name, payload := range request.Payloads {
|
||||
payloadStr, ok := payload.(string)
|
||||
if ok {
|
||||
final, resolveErr := options.Catalog.ResolvePath(payloadStr, options.TemplatePath)
|
||||
if resolveErr != nil {
|
||||
return errors.Wrap(resolveErr, "could not read payload file")
|
||||
}
|
||||
request.Payloads[name] = final
|
||||
}
|
||||
}
|
||||
request.generator, err = generators.New(request.Payloads, request.attackType, request.options.TemplatePath)
|
||||
request.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, request.options.Catalog)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not parse payloads")
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
func TestNetworkCompileMake(t *testing.T) {
|
||||
|
|
|
@ -5,13 +5,13 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
func TestResponseToDSLMap(t *testing.T) {
|
||||
|
|
|
@ -22,10 +22,16 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/responsehighlighter"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/replacer"
|
||||
templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
|
||||
)
|
||||
|
||||
var _ protocols.Request = &Request{}
|
||||
|
||||
// Type returns the type of the protocol request
|
||||
func (request *Request) Type() templateTypes.ProtocolType {
|
||||
return templateTypes.NetworkProtocol
|
||||
}
|
||||
|
||||
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
|
||||
func (request *Request) ExecuteWithResults(input string, metadata /*TODO review unused parameter*/, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
|
||||
var address string
|
||||
|
@ -37,7 +43,7 @@ func (request *Request) ExecuteWithResults(input string, metadata /*TODO review
|
|||
address, err = getAddress(input)
|
||||
}
|
||||
if err != nil {
|
||||
request.options.Output.Request(request.options.TemplatePath, input, "network", err)
|
||||
request.options.Output.Request(request.options.TemplatePath, input, request.Type().String(), err)
|
||||
request.options.Progress.IncrementFailedRequestsBy(1)
|
||||
return errors.Wrap(err, "could not get address from url")
|
||||
}
|
||||
|
@ -66,7 +72,7 @@ func (request *Request) ExecuteWithResults(input string, metadata /*TODO review
|
|||
func (request *Request) executeAddress(actualAddress, address, input string, shouldUseTLS bool, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
|
||||
if !strings.Contains(actualAddress, ":") {
|
||||
err := errors.New("no port provided in network protocol request")
|
||||
request.options.Output.Request(request.options.TemplatePath, address, "network", err)
|
||||
request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
|
||||
request.options.Progress.IncrementFailedRequestsBy(1)
|
||||
return err
|
||||
}
|
||||
|
@ -114,7 +120,7 @@ func (request *Request) executeRequestWithPayloads(actualAddress, address, input
|
|||
conn, err = request.dialer.Dial(context.Background(), "tcp", actualAddress)
|
||||
}
|
||||
if err != nil {
|
||||
request.options.Output.Request(request.options.TemplatePath, address, "network", err)
|
||||
request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
|
||||
request.options.Progress.IncrementFailedRequestsBy(1)
|
||||
return errors.Wrap(err, "could not connect to server request")
|
||||
}
|
||||
|
@ -138,7 +144,7 @@ func (request *Request) executeRequestWithPayloads(actualAddress, address, input
|
|||
data = []byte(input.Data)
|
||||
}
|
||||
if err != nil {
|
||||
request.options.Output.Request(request.options.TemplatePath, address, "network", err)
|
||||
request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
|
||||
request.options.Progress.IncrementFailedRequestsBy(1)
|
||||
return errors.Wrap(err, "could not write request to server")
|
||||
}
|
||||
|
@ -146,7 +152,7 @@ func (request *Request) executeRequestWithPayloads(actualAddress, address, input
|
|||
|
||||
finalData, dataErr := expressions.EvaluateByte(data, payloads)
|
||||
if dataErr != nil {
|
||||
request.options.Output.Request(request.options.TemplatePath, address, "network", dataErr)
|
||||
request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), dataErr)
|
||||
request.options.Progress.IncrementFailedRequestsBy(1)
|
||||
return errors.Wrap(dataErr, "could not evaluate template expressions")
|
||||
}
|
||||
|
@ -157,7 +163,7 @@ func (request *Request) executeRequestWithPayloads(actualAddress, address, input
|
|||
return nil
|
||||
}
|
||||
if _, err := conn.Write(finalData); err != nil {
|
||||
request.options.Output.Request(request.options.TemplatePath, address, "network", err)
|
||||
request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
|
||||
request.options.Progress.IncrementFailedRequestsBy(1)
|
||||
return errors.Wrap(err, "could not write request to server")
|
||||
}
|
||||
|
@ -191,7 +197,7 @@ func (request *Request) executeRequestWithPayloads(actualAddress, address, input
|
|||
}
|
||||
}
|
||||
|
||||
request.options.Output.Request(request.options.TemplatePath, actualAddress, "network", err)
|
||||
request.options.Output.Request(request.options.TemplatePath, actualAddress, request.Type().String(), err)
|
||||
gologger.Verbose().Msgf("Sent TCP request to %s", actualAddress)
|
||||
|
||||
bufferSize := 1024
|
||||
|
@ -222,7 +228,7 @@ func (request *Request) executeRequestWithPayloads(actualAddress, address, input
|
|||
buf := make([]byte, bufferSize)
|
||||
nBuf, err := conn.Read(buf)
|
||||
if err != nil && !os.IsTimeout(err) {
|
||||
request.options.Output.Request(request.options.TemplatePath, address, "network", err)
|
||||
request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
|
||||
closeTimer(readInterval)
|
||||
return errors.Wrap(err, "could not read from server")
|
||||
}
|
||||
|
@ -235,7 +241,7 @@ func (request *Request) executeRequestWithPayloads(actualAddress, address, input
|
|||
final = make([]byte, bufferSize)
|
||||
n, err = conn.Read(final)
|
||||
if err != nil && err != io.EOF {
|
||||
request.options.Output.Request(request.options.TemplatePath, address, "network", err)
|
||||
request.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)
|
||||
return errors.Wrap(err, "could not read from server")
|
||||
}
|
||||
responseBuilder.Write(final[:n])
|
||||
|
|
|
@ -10,13 +10,13 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
func TestNetworkExecuteWithResults(t *testing.T) {
|
||||
|
|
|
@ -8,10 +8,10 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
func TestFindResponses(t *testing.T) {
|
||||
|
|
|
@ -7,13 +7,13 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/internal/testutils"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
)
|
||||
|
||||
func TestResponseToDSLMap(t *testing.T) {
|
||||
|
|
|
@ -15,12 +15,18 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/tostring"
|
||||
templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
|
||||
)
|
||||
|
||||
var _ protocols.Request = &Request{}
|
||||
|
||||
const maxSize = 5 * 1024 * 1024
|
||||
|
||||
// Type returns the type of the protocol request
|
||||
func (request *Request) Type() templateTypes.ProtocolType {
|
||||
return templateTypes.HTTPProtocol
|
||||
}
|
||||
|
||||
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
|
||||
func (request *Request) ExecuteWithResults(input string, metadata /*TODO review unused parameter*/, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
|
||||
wg := sizedwaitgroup.New(request.options.Options.BulkSize)
|
||||
|
|
|
@ -3,6 +3,7 @@ package protocols
|
|||
import (
|
||||
"go.uber.org/ratelimit"
|
||||
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/catalog"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators"
|
||||
|
@ -15,6 +16,7 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/interactsh"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/headless/engine"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting"
|
||||
templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||
)
|
||||
|
||||
|
@ -61,9 +63,16 @@ type ExecuterOptions struct {
|
|||
|
||||
Operators []*operators.Operators // only used by offlinehttp module
|
||||
|
||||
Colorizer aurora.Aurora
|
||||
WorkflowLoader model.WorkflowLoader
|
||||
}
|
||||
|
||||
// Copy returns a copy of the executeroptions structure
|
||||
func (e ExecuterOptions) Copy() ExecuterOptions {
|
||||
copy := e
|
||||
return copy
|
||||
}
|
||||
|
||||
// Request is an interface implemented any protocol based request generator.
|
||||
type Request interface {
|
||||
// Compile compiles the request generators preparing any requests possible.
|
||||
|
@ -88,6 +97,8 @@ type Request interface {
|
|||
MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent
|
||||
// GetCompiledOperators returns a list of the compiled operators
|
||||
GetCompiledOperators() []*operators.Operators
|
||||
// Type returns the type of the protocol request
|
||||
Type() templateTypes.ProtocolType
|
||||
}
|
||||
|
||||
// OutputEventCallback is a callback event for any results found during scanning.
|
||||
|
@ -120,3 +131,58 @@ func MakeDefaultResultEvent(request Request, wrapped *output.InternalWrappedEven
|
|||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// MakeDefaultExtractFunc performs extracting operation for an extractor on model and returns true or false.
|
||||
func MakeDefaultExtractFunc(data map[string]interface{}, extractor *extractors.Extractor) map[string]struct{} {
|
||||
part := extractor.Part
|
||||
if part == "" {
|
||||
part = "response"
|
||||
}
|
||||
|
||||
item, ok := data[part]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
itemStr := types.ToString(item)
|
||||
|
||||
switch extractor.GetType() {
|
||||
case extractors.RegexExtractor:
|
||||
return extractor.ExtractRegex(itemStr)
|
||||
case extractors.KValExtractor:
|
||||
return extractor.ExtractKval(data)
|
||||
case extractors.JSONExtractor:
|
||||
return extractor.ExtractJSON(itemStr)
|
||||
case extractors.XPathExtractor:
|
||||
return extractor.ExtractHTML(itemStr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MakeDefaultMatchFunc performs matching operation for a matcher on model and returns true or false.
|
||||
func MakeDefaultMatchFunc(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {
|
||||
part := matcher.Part
|
||||
if part == "" {
|
||||
part = "response"
|
||||
}
|
||||
|
||||
partItem, ok := data[part]
|
||||
if !ok && len(matcher.DSL) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
item := types.ToString(partItem)
|
||||
|
||||
switch matcher.GetType() {
|
||||
case matchers.SizeMatcher:
|
||||
result := matcher.Result(matcher.MatchSize(len(item)))
|
||||
return result, nil
|
||||
case matchers.WordsMatcher:
|
||||
return matcher.ResultWithMatchedSnippet(matcher.MatchWords(item, nil))
|
||||
case matchers.RegexMatcher:
|
||||
return matcher.ResultWithMatchedSnippet(matcher.MatchRegex(item))
|
||||
case matchers.BinaryMatcher:
|
||||
return matcher.ResultWithMatchedSnippet(matcher.MatchBinary(item))
|
||||
case matchers.DSLMatcher:
|
||||
return matcher.Result(matcher.MatchDSL(data)), nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
package ssl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/projectdiscovery/cryptoutil"
|
||||
"github.com/projectdiscovery/fastdialer/fastdialer"
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/responsehighlighter"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/network/networkclientpool"
|
||||
templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||
)
|
||||
|
||||
// Request is a request for the SSL protocol
|
||||
type Request struct {
|
||||
// Operators for the current request go here.
|
||||
operators.Operators `yaml:",inline,omitempty"`
|
||||
CompiledOperators *operators.Operators `yaml:"-"`
|
||||
// description: |
|
||||
// Address contains address for the request
|
||||
Address string `yaml:"address,omitempty" jsonschema:"title=address for the ssl request,description=Address contains address for the request"`
|
||||
|
||||
// cache any variables that may be needed for operation.
|
||||
dialer *fastdialer.Dialer
|
||||
options *protocols.ExecuterOptions
|
||||
}
|
||||
|
||||
// Compile compiles the request generators preparing any requests possible.
|
||||
func (request *Request) Compile(options *protocols.ExecuterOptions) error {
|
||||
request.options = options
|
||||
|
||||
client, err := networkclientpool.Get(options.Options, &networkclientpool.Configuration{})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get network client")
|
||||
}
|
||||
request.dialer = client
|
||||
|
||||
if len(request.Matchers) > 0 || len(request.Extractors) > 0 {
|
||||
compiled := &request.Operators
|
||||
if err := compiled.Compile(); err != nil {
|
||||
return errors.Wrap(err, "could not compile operators")
|
||||
}
|
||||
request.CompiledOperators = compiled
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Requests returns the total number of requests the rule will perform
|
||||
func (request *Request) Requests() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// GetID returns the ID for the request if any.
|
||||
func (request *Request) GetID() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
|
||||
func (request *Request) ExecuteWithResults(input string, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
|
||||
address, err := getAddress(input)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
hostname, port, _ := net.SplitHostPort(address)
|
||||
|
||||
requestOptions := request.options
|
||||
payloadValues := make(map[string]interface{})
|
||||
for k, v := range dynamicValues {
|
||||
payloadValues[k] = v
|
||||
}
|
||||
payloadValues["Hostname"] = address
|
||||
payloadValues["Host"] = hostname
|
||||
payloadValues["Port"] = port
|
||||
|
||||
finalAddress, dataErr := expressions.EvaluateByte([]byte(request.Address), payloadValues)
|
||||
if dataErr != nil {
|
||||
requestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), dataErr)
|
||||
requestOptions.Progress.IncrementFailedRequestsBy(1)
|
||||
return errors.Wrap(dataErr, "could not evaluate template expressions")
|
||||
}
|
||||
|
||||
addressToDial := string(finalAddress)
|
||||
config := &tls.Config{InsecureSkipVerify: true, ServerName: hostname}
|
||||
|
||||
conn, err := request.dialer.DialTLSWithConfig(context.Background(), "tcp", addressToDial, config)
|
||||
if err != nil {
|
||||
requestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), err)
|
||||
requestOptions.Progress.IncrementFailedRequestsBy(1)
|
||||
return errors.Wrap(err, "could not connect to server")
|
||||
}
|
||||
defer conn.Close()
|
||||
_ = conn.SetReadDeadline(time.Now().Add(time.Duration(requestOptions.Options.Timeout) * time.Second))
|
||||
|
||||
connTLS, ok := conn.(*tls.Conn)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
requestOptions.Output.Request(requestOptions.TemplateID, address, request.Type().String(), err)
|
||||
gologger.Verbose().Msgf("Sent SSL request to %s", address)
|
||||
|
||||
if requestOptions.Options.Debug || requestOptions.Options.DebugRequests {
|
||||
gologger.Debug().Str("address", input).Msgf("[%s] Dumped SSL request for %s", requestOptions.TemplateID, input)
|
||||
}
|
||||
|
||||
state := connTLS.ConnectionState()
|
||||
if len(state.PeerCertificates) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
tlsData := cryptoutil.TLSGrab(&state)
|
||||
jsonData, _ := jsoniter.Marshal(tlsData)
|
||||
jsonDataString := string(jsonData)
|
||||
|
||||
data := make(map[string]interface{})
|
||||
cert := connTLS.ConnectionState().PeerCertificates[0]
|
||||
|
||||
data["response"] = jsonDataString
|
||||
data["host"] = input
|
||||
data["matched"] = addressToDial
|
||||
data["not_after"] = float64(cert.NotAfter.Unix())
|
||||
data["ip"] = request.dialer.GetDialedIP(hostname)
|
||||
|
||||
event := eventcreator.CreateEvent(request, data, requestOptions.Options.Debug || requestOptions.Options.DebugResponse)
|
||||
if requestOptions.Options.Debug || requestOptions.Options.DebugResponse {
|
||||
gologger.Debug().Msgf("[%s] Dumped SSL response for %s", requestOptions.TemplateID, input)
|
||||
gologger.Print().Msgf("%s", responsehighlighter.Highlight(event.OperatorsResult, jsonDataString, requestOptions.Options.NoColor, false))
|
||||
}
|
||||
callback(event)
|
||||
return nil
|
||||
}
|
||||
|
||||
// getAddress returns the address of the host to make request to
|
||||
func getAddress(toTest string) (string, error) {
|
||||
if strings.Contains(toTest, "://") {
|
||||
parsed, err := url.Parse(toTest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, port, _ := net.SplitHostPort(parsed.Host)
|
||||
|
||||
if strings.ToLower(parsed.Scheme) == "https" && port == "" {
|
||||
toTest = net.JoinHostPort(parsed.Host, "443")
|
||||
} else {
|
||||
toTest = parsed.Host
|
||||
}
|
||||
return toTest, nil
|
||||
}
|
||||
return toTest, nil
|
||||
}
|
||||
|
||||
// Match performs matching operation for a matcher on model and returns:
|
||||
// true and a list of matched snippets if the matcher type is supports it
|
||||
// otherwise false and an empty string slice
|
||||
func (request *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {
|
||||
return protocols.MakeDefaultMatchFunc(data, matcher)
|
||||
}
|
||||
|
||||
// Extract performs extracting operation for an extractor on model and returns true or false.
|
||||
func (request *Request) Extract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{} {
|
||||
return protocols.MakeDefaultExtractFunc(data, matcher)
|
||||
}
|
||||
|
||||
// MakeResultEvent creates a result event from internal wrapped event
|
||||
func (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent {
|
||||
return protocols.MakeDefaultResultEvent(request, wrapped)
|
||||
}
|
||||
|
||||
// GetCompiledOperators returns a list of the compiled operators
|
||||
func (request *Request) GetCompiledOperators() []*operators.Operators {
|
||||
return []*operators.Operators{request.CompiledOperators}
|
||||
}
|
||||
|
||||
// Type returns the type of the protocol request
|
||||
func (request *Request) Type() templateTypes.ProtocolType {
|
||||
return templateTypes.SSLProtocol
|
||||
}
|
||||
|
||||
func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent {
|
||||
data := &output.ResultEvent{
|
||||
TemplateID: types.ToString(request.options.TemplateID),
|
||||
TemplatePath: types.ToString(request.options.TemplatePath),
|
||||
Info: request.options.TemplateInfo,
|
||||
Type: request.Type().String(),
|
||||
Host: types.ToString(wrapped.InternalEvent["host"]),
|
||||
Matched: types.ToString(wrapped.InternalEvent["host"]),
|
||||
Metadata: wrapped.OperatorsResult.PayloadValues,
|
||||
ExtractedResults: wrapped.OperatorsResult.OutputExtracts,
|
||||
Timestamp: time.Now(),
|
||||
IP: types.ToString(wrapped.InternalEvent["ip"]),
|
||||
}
|
||||
return data
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package ssl
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/testutils"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSSLProtocol(t *testing.T) {
|
||||
options := testutils.DefaultOptions
|
||||
|
||||
testutils.Init(options)
|
||||
templateID := "testing-ssl"
|
||||
request := &Request{
|
||||
Address: "{{Hostname}}",
|
||||
}
|
||||
executerOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{
|
||||
ID: templateID,
|
||||
Info: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: "test"},
|
||||
})
|
||||
err := request.Compile(executerOpts)
|
||||
require.Nil(t, err, "could not compile ssl request")
|
||||
|
||||
var gotEvent output.InternalEvent
|
||||
err = request.ExecuteWithResults("google.com:443", nil, nil, func(event *output.InternalWrappedEvent) {
|
||||
gotEvent = event.InternalEvent
|
||||
})
|
||||
require.Nil(t, err, "could not run ssl request")
|
||||
require.NotEmpty(t, gotEvent, "could not get event items")
|
||||
}
|
||||
|
||||
func TestGetAddress(t *testing.T) {
|
||||
address, _ := getAddress("https://google.com")
|
||||
require.Equal(t, "google.com:443", address, "could not get correct address")
|
||||
}
|
|
@ -0,0 +1,383 @@
|
|||
package websocket
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gobwas/ws"
|
||||
"github.com/gobwas/ws/wsutil"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/projectdiscovery/fastdialer/fastdialer"
|
||||
"github.com/projectdiscovery/gologger"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators/extractors"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/operators/matchers"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/generators"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/eventcreator"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/helpers/responsehighlighter"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/network/networkclientpool"
|
||||
templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||
)
|
||||
|
||||
// Request is a request for the Websocket protocol
|
||||
type Request struct {
|
||||
// Operators for the current request go here.
|
||||
operators.Operators `yaml:",inline,omitempty"`
|
||||
CompiledOperators *operators.Operators `yaml:"-"`
|
||||
|
||||
// description: |
|
||||
// Address contains address for the request
|
||||
Address string `yaml:"address,omitempty" jsonschema:"title=address for the websocket request,description=Address contains address for the request"`
|
||||
// description: |
|
||||
// Inputs contains inputs for the websocket protocol
|
||||
Inputs []*Input `yaml:"inputs,omitempty" jsonschema:"title=inputs for the websocket request,description=Inputs contains any input/output for the current request"`
|
||||
// description: |
|
||||
// Headers contains headers for the request.
|
||||
Headers map[string]string `yaml:"headers,omitempty" jsonschema:"title=headers contains the request headers,description=Headers contains headers for the request"`
|
||||
|
||||
// description: |
|
||||
// Attack is the type of payload combinations to perform.
|
||||
//
|
||||
// Sniper is each payload once, pitchfork combines multiple payload sets and clusterbomb generates
|
||||
// permutations and combinations for all payloads.
|
||||
// values:
|
||||
// - "sniper"
|
||||
// - "pitchfork"
|
||||
// - "clusterbomb"
|
||||
AttackType generators.AttackTypeHolder `yaml:"attack,omitempty" jsonschema:"title=attack is the payload combination,description=Attack is the type of payload combinations to perform,enum=sniper,enum=pitchfork,enum=clusterbomb"`
|
||||
// description: |
|
||||
// Payloads contains any payloads for the current request.
|
||||
//
|
||||
// Payloads support both key-values combinations where a list
|
||||
// of payloads is provided, or optionally a single file can also
|
||||
// be provided as payload which will be read on run-time.
|
||||
Payloads map[string]interface{} `yaml:"payloads,omitempty" jsonschema:"title=payloads for the webosocket request,description=Payloads contains any payloads for the current request"`
|
||||
|
||||
generator *generators.PayloadGenerator
|
||||
|
||||
// cache any variables that may be needed for operation.
|
||||
dialer *fastdialer.Dialer
|
||||
options *protocols.ExecuterOptions
|
||||
}
|
||||
|
||||
// Input is an input for the websocket protocol
|
||||
type Input struct {
|
||||
// description: |
|
||||
// Data is the data to send as the input.
|
||||
//
|
||||
// It supports DSL Helper Functions as well as normal expressions.
|
||||
// examples:
|
||||
// - value: "\"TEST\""
|
||||
// - value: "\"hex_decode('50494e47')\""
|
||||
Data string `yaml:"data,omitempty" jsonschema:"title=data to send as input,description=Data is the data to send as the input"`
|
||||
// description: |
|
||||
// Name is the optional name of the data read to provide matching on.
|
||||
// examples:
|
||||
// - value: "\"prefix\""
|
||||
Name string `yaml:"name,omitempty" jsonschema:"title=optional name for data read,description=Optional name of the data read to provide matching on"`
|
||||
}
|
||||
|
||||
// Compile compiles the request generators preparing any requests possible.
|
||||
func (request *Request) Compile(options *protocols.ExecuterOptions) error {
|
||||
request.options = options
|
||||
|
||||
client, err := networkclientpool.Get(options.Options, &networkclientpool.Configuration{})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get network client")
|
||||
}
|
||||
request.dialer = client
|
||||
|
||||
if len(request.Payloads) > 0 {
|
||||
request.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, options.Catalog)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not parse payloads")
|
||||
}
|
||||
}
|
||||
|
||||
if len(request.Matchers) > 0 || len(request.Extractors) > 0 {
|
||||
compiled := &request.Operators
|
||||
if err := compiled.Compile(); err != nil {
|
||||
return errors.Wrap(err, "could not compile operators")
|
||||
}
|
||||
request.CompiledOperators = compiled
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Requests returns the total number of requests the rule will perform
|
||||
func (request *Request) Requests() int {
|
||||
if request.generator != nil {
|
||||
return request.generator.NewIterator().Total()
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
// GetID returns the ID for the request if any.
|
||||
func (request *Request) GetID() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
|
||||
func (request *Request) ExecuteWithResults(input string, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
|
||||
hostname, err := getAddress(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if request.generator != nil {
|
||||
iterator := request.generator.NewIterator()
|
||||
|
||||
for {
|
||||
value, ok := iterator.Value()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if err := request.executeRequestWithPayloads(input, hostname, value, previous, callback); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
value := make(map[string]interface{})
|
||||
if err := request.executeRequestWithPayloads(input, hostname, value, previous, callback); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExecuteWithResults executes the protocol requests and returns results instead of writing them.
|
||||
func (request *Request) executeRequestWithPayloads(input, hostname string, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {
|
||||
header := http.Header{}
|
||||
|
||||
payloadValues := make(map[string]interface{})
|
||||
for k, v := range dynamicValues {
|
||||
payloadValues[k] = v
|
||||
}
|
||||
parsed, err := url.Parse(input)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not parse input url")
|
||||
}
|
||||
payloadValues["Hostname"] = parsed.Host
|
||||
payloadValues["Host"] = parsed.Hostname()
|
||||
payloadValues["Scheme"] = parsed.Scheme
|
||||
requestPath := parsed.Path
|
||||
if values := parsed.Query(); len(values) > 0 {
|
||||
requestPath = requestPath + "?" + values.Encode()
|
||||
}
|
||||
payloadValues["Path"] = requestPath
|
||||
|
||||
requestOptions := request.options
|
||||
for key, value := range request.Headers {
|
||||
finalData, dataErr := expressions.EvaluateByte([]byte(value), payloadValues)
|
||||
if dataErr != nil {
|
||||
requestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), dataErr)
|
||||
requestOptions.Progress.IncrementFailedRequestsBy(1)
|
||||
return errors.Wrap(dataErr, "could not evaluate template expressions")
|
||||
}
|
||||
header.Set(key, string(finalData))
|
||||
}
|
||||
websocketDialer := ws.Dialer{
|
||||
Header: ws.HandshakeHeaderHTTP(header),
|
||||
Timeout: time.Duration(requestOptions.Options.Timeout) * time.Second,
|
||||
NetDial: request.dialer.Dial,
|
||||
TLSConfig: &tls.Config{InsecureSkipVerify: true, ServerName: hostname},
|
||||
}
|
||||
|
||||
finalAddress, dataErr := expressions.EvaluateByte([]byte(request.Address), payloadValues)
|
||||
if dataErr != nil {
|
||||
requestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), dataErr)
|
||||
requestOptions.Progress.IncrementFailedRequestsBy(1)
|
||||
return errors.Wrap(dataErr, "could not evaluate template expressions")
|
||||
}
|
||||
|
||||
addressToDial := string(finalAddress)
|
||||
parsedAddress, err := url.Parse(addressToDial)
|
||||
if err != nil {
|
||||
requestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), err)
|
||||
requestOptions.Progress.IncrementFailedRequestsBy(1)
|
||||
return errors.Wrap(err, "could not parse input url")
|
||||
}
|
||||
parsedAddress.Path = path.Join(parsedAddress.Path, parsed.Path)
|
||||
addressToDial = parsedAddress.String()
|
||||
|
||||
conn, readBuffer, _, err := websocketDialer.Dial(context.Background(), addressToDial)
|
||||
if err != nil {
|
||||
requestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), err)
|
||||
requestOptions.Progress.IncrementFailedRequestsBy(1)
|
||||
return errors.Wrap(err, "could not connect to server")
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
responseBuilder := &strings.Builder{}
|
||||
if readBuffer != nil {
|
||||
_, _ = io.Copy(responseBuilder, readBuffer) // Copy initial response
|
||||
}
|
||||
|
||||
events, requestOutput, err := request.readWriteInputWebsocket(conn, payloadValues, input, responseBuilder)
|
||||
if err != nil {
|
||||
requestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), err)
|
||||
requestOptions.Progress.IncrementFailedRequestsBy(1)
|
||||
return errors.Wrap(err, "could not read write response")
|
||||
}
|
||||
requestOptions.Progress.IncrementRequests()
|
||||
|
||||
if requestOptions.Options.Debug || requestOptions.Options.DebugRequests {
|
||||
gologger.Debug().Str("address", input).Msgf("[%s] Dumped Websocket request for %s", requestOptions.TemplateID, input)
|
||||
gologger.Print().Msgf("%s", requestOutput)
|
||||
}
|
||||
|
||||
requestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), err)
|
||||
gologger.Verbose().Msgf("Sent Websocket request to %s", input)
|
||||
|
||||
data := make(map[string]interface{})
|
||||
for k, v := range previous {
|
||||
data[k] = v
|
||||
}
|
||||
for k, v := range events {
|
||||
data[k] = v
|
||||
}
|
||||
data["success"] = "true"
|
||||
data["request"] = requestOutput
|
||||
data["response"] = responseBuilder.String()
|
||||
data["host"] = input
|
||||
data["matched"] = addressToDial
|
||||
data["ip"] = request.dialer.GetDialedIP(hostname)
|
||||
|
||||
event := eventcreator.CreateEventWithAdditionalOptions(request, data, requestOptions.Options.Debug || requestOptions.Options.DebugResponse, func(internalWrappedEvent *output.InternalWrappedEvent) {
|
||||
internalWrappedEvent.OperatorsResult.PayloadValues = payloadValues
|
||||
})
|
||||
if requestOptions.Options.Debug || requestOptions.Options.DebugResponse {
|
||||
responseOutput := responseBuilder.String()
|
||||
gologger.Debug().Msgf("[%s] Dumped Websocket response for %s", requestOptions.TemplateID, input)
|
||||
gologger.Print().Msgf("%s", responsehighlighter.Highlight(event.OperatorsResult, responseOutput, requestOptions.Options.NoColor, false))
|
||||
}
|
||||
|
||||
callback(event)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (request *Request) readWriteInputWebsocket(conn net.Conn, payloadValues map[string]interface{}, input string, respBuilder *strings.Builder) (events map[string]interface{}, req string, err error) {
|
||||
reqBuilder := &strings.Builder{}
|
||||
inputEvents := make(map[string]interface{})
|
||||
|
||||
requestOptions := request.options
|
||||
for _, req := range request.Inputs {
|
||||
reqBuilder.Grow(len(req.Data))
|
||||
|
||||
finalData, dataErr := expressions.EvaluateByte([]byte(req.Data), payloadValues)
|
||||
if dataErr != nil {
|
||||
requestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), dataErr)
|
||||
requestOptions.Progress.IncrementFailedRequestsBy(1)
|
||||
return nil, "", errors.Wrap(dataErr, "could not evaluate template expressions")
|
||||
}
|
||||
reqBuilder.WriteString(string(finalData))
|
||||
|
||||
err = wsutil.WriteClientMessage(conn, ws.OpText, finalData)
|
||||
if err != nil {
|
||||
requestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), err)
|
||||
requestOptions.Progress.IncrementFailedRequestsBy(1)
|
||||
return nil, "", errors.Wrap(err, "could not write request to server")
|
||||
}
|
||||
|
||||
msg, opCode, err := wsutil.ReadServerData(conn)
|
||||
if err != nil {
|
||||
requestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), err)
|
||||
requestOptions.Progress.IncrementFailedRequestsBy(1)
|
||||
return nil, "", errors.Wrap(err, "could not write request to server")
|
||||
}
|
||||
// Only perform matching and writes in case we recieve
|
||||
// text or binary opcode from the websocket server.
|
||||
if opCode != ws.OpText && opCode != ws.OpBinary {
|
||||
continue
|
||||
}
|
||||
|
||||
respBuilder.Write(msg)
|
||||
if req.Name != "" {
|
||||
bufferStr := string(msg)
|
||||
inputEvents[req.Name] = bufferStr
|
||||
|
||||
// Run any internal extractors for the request here and add found values to map.
|
||||
if request.CompiledOperators != nil {
|
||||
values := request.CompiledOperators.ExecuteInternalExtractors(map[string]interface{}{req.Name: bufferStr}, protocols.MakeDefaultExtractFunc)
|
||||
for k, v := range values {
|
||||
inputEvents[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return inputEvents, reqBuilder.String(), nil
|
||||
}
|
||||
|
||||
// getAddress returns the address of the host to make request to
|
||||
func getAddress(toTest string) (string, error) {
|
||||
parsed, err := url.Parse(toTest)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "could not parse input url")
|
||||
}
|
||||
scheme := strings.ToLower(parsed.Scheme)
|
||||
|
||||
if scheme != "ws" && scheme != "wss" {
|
||||
return "", fmt.Errorf("invalid url scheme provided: %s", scheme)
|
||||
}
|
||||
if parsed != nil && parsed.Host != "" {
|
||||
return parsed.Host, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Match performs matching operation for a matcher on model and returns:
|
||||
// true and a list of matched snippets if the matcher type is supports it
|
||||
// otherwise false and an empty string slice
|
||||
func (request *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {
|
||||
return protocols.MakeDefaultMatchFunc(data, matcher)
|
||||
}
|
||||
|
||||
// Extract performs extracting operation for an extractor on model and returns true or false.
|
||||
func (request *Request) Extract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{} {
|
||||
return protocols.MakeDefaultExtractFunc(data, matcher)
|
||||
}
|
||||
|
||||
// MakeResultEvent creates a result event from internal wrapped event
|
||||
func (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent {
|
||||
return protocols.MakeDefaultResultEvent(request, wrapped)
|
||||
}
|
||||
|
||||
// GetCompiledOperators returns a list of the compiled operators
|
||||
func (request *Request) GetCompiledOperators() []*operators.Operators {
|
||||
return []*operators.Operators{request.CompiledOperators}
|
||||
}
|
||||
|
||||
func (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent {
|
||||
data := &output.ResultEvent{
|
||||
TemplateID: types.ToString(request.options.TemplateID),
|
||||
TemplatePath: types.ToString(request.options.TemplatePath),
|
||||
Info: request.options.TemplateInfo,
|
||||
Type: request.Type().String(),
|
||||
Host: types.ToString(wrapped.InternalEvent["host"]),
|
||||
Matched: types.ToString(wrapped.InternalEvent["matched"]),
|
||||
Metadata: wrapped.OperatorsResult.PayloadValues,
|
||||
ExtractedResults: wrapped.OperatorsResult.OutputExtracts,
|
||||
Timestamp: time.Now(),
|
||||
IP: types.ToString(wrapped.InternalEvent["ip"]),
|
||||
Request: types.ToString(wrapped.InternalEvent["request"]),
|
||||
Response: types.ToString(wrapped.InternalEvent["response"]),
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// Type returns the type of the protocol request
|
||||
func (request *Request) Type() templateTypes.ProtocolType {
|
||||
return templateTypes.WebsocketProtocol
|
||||
}
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
@ -70,7 +71,7 @@ func Parse(filePath string, preprocessor Preprocessor, options protocols.Execute
|
|||
options.TemplatePath = filePath
|
||||
|
||||
// If no requests, and it is also not a workflow, return error.
|
||||
if len(template.RequestsDNS)+len(template.RequestsHTTP)+len(template.RequestsFile)+len(template.RequestsNetwork)+len(template.RequestsHeadless)+len(template.Workflows) == 0 {
|
||||
if template.Requests() == 0 {
|
||||
return nil, fmt.Errorf("no requests defined for %s", template.ID)
|
||||
}
|
||||
|
||||
|
@ -83,61 +84,15 @@ func Parse(filePath string, preprocessor Preprocessor, options protocols.Execute
|
|||
template.CompiledWorkflow.Options = &options
|
||||
}
|
||||
|
||||
// Compile the requests found
|
||||
requests := []protocols.Request{}
|
||||
if len(template.RequestsDNS) > 0 && !options.Options.OfflineHTTP {
|
||||
for _, req := range template.RequestsDNS {
|
||||
requests = append(requests, req)
|
||||
}
|
||||
template.Executer = executer.NewExecuter(requests, &options)
|
||||
if err := template.compileProtocolRequests(options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(template.RequestsHTTP) > 0 {
|
||||
if options.Options.OfflineHTTP {
|
||||
operatorsList := []*operators.Operators{}
|
||||
|
||||
mainLoop:
|
||||
for _, req := range template.RequestsHTTP {
|
||||
for _, path := range req.Path {
|
||||
if !(strings.EqualFold(path, "{{BaseURL}}") || strings.EqualFold(path, "{{BaseURL}}/")) {
|
||||
break mainLoop
|
||||
}
|
||||
}
|
||||
operatorsList = append(operatorsList, &req.Operators)
|
||||
}
|
||||
if len(operatorsList) > 0 {
|
||||
options.Operators = operatorsList
|
||||
template.Executer = executer.NewExecuter([]protocols.Request{&offlinehttp.Request{}}, &options)
|
||||
}
|
||||
} else {
|
||||
for _, req := range template.RequestsHTTP {
|
||||
requests = append(requests, req)
|
||||
}
|
||||
template.Executer = executer.NewExecuter(requests, &options)
|
||||
}
|
||||
}
|
||||
if len(template.RequestsFile) > 0 && !options.Options.OfflineHTTP {
|
||||
for _, req := range template.RequestsFile {
|
||||
requests = append(requests, req)
|
||||
}
|
||||
template.Executer = executer.NewExecuter(requests, &options)
|
||||
}
|
||||
if len(template.RequestsNetwork) > 0 && !options.Options.OfflineHTTP {
|
||||
for _, req := range template.RequestsNetwork {
|
||||
requests = append(requests, req)
|
||||
}
|
||||
template.Executer = executer.NewExecuter(requests, &options)
|
||||
}
|
||||
if len(template.RequestsHeadless) > 0 && !options.Options.OfflineHTTP && options.Options.Headless {
|
||||
for _, req := range template.RequestsHeadless {
|
||||
requests = append(requests, req)
|
||||
}
|
||||
template.Executer = executer.NewExecuter(requests, &options)
|
||||
}
|
||||
if template.Executer != nil {
|
||||
if err := template.Executer.Compile(); err != nil {
|
||||
return nil, errors.Wrap(err, "could not compile request")
|
||||
}
|
||||
template.TotalRequests += template.Executer.Requests()
|
||||
template.TotalRequests = template.Executer.Requests()
|
||||
}
|
||||
if template.Executer == nil && template.CompiledWorkflow == nil {
|
||||
return nil, ErrCreateTemplateExecutor
|
||||
|
@ -151,14 +106,106 @@ func Parse(filePath string, preprocessor Preprocessor, options protocols.Execute
|
|||
}
|
||||
|
||||
// parseSelfContainedRequests parses the self contained template requests.
|
||||
func (t *Template) parseSelfContainedRequests() {
|
||||
if !t.SelfContained {
|
||||
func (template *Template) parseSelfContainedRequests() {
|
||||
if !template.SelfContained {
|
||||
return
|
||||
}
|
||||
for _, request := range t.RequestsHTTP {
|
||||
for _, request := range template.RequestsHTTP {
|
||||
request.SelfContained = true
|
||||
}
|
||||
for _, request := range t.RequestsNetwork {
|
||||
for _, request := range template.RequestsNetwork {
|
||||
request.SelfContained = true
|
||||
}
|
||||
}
|
||||
|
||||
// Requests returns the total request count for the template
|
||||
func (template *Template) Requests() int {
|
||||
return len(template.RequestsDNS) +
|
||||
len(template.RequestsHTTP) +
|
||||
len(template.RequestsFile) +
|
||||
len(template.RequestsNetwork) +
|
||||
len(template.RequestsHeadless) +
|
||||
len(template.Workflows) +
|
||||
len(template.RequestsSSL) +
|
||||
len(template.RequestsWebsocket)
|
||||
}
|
||||
|
||||
// compileProtocolRequests compiles all the protocol requests for the template
|
||||
func (template *Template) compileProtocolRequests(options protocols.ExecuterOptions) error {
|
||||
templateRequests := template.Requests()
|
||||
|
||||
if templateRequests == 0 {
|
||||
return fmt.Errorf("no requests defined for %s", template.ID)
|
||||
}
|
||||
|
||||
if options.Options.OfflineHTTP {
|
||||
template.compileOfflineHTTPRequest(options)
|
||||
return nil
|
||||
}
|
||||
|
||||
var requests []protocols.Request
|
||||
switch {
|
||||
case len(template.RequestsDNS) > 0:
|
||||
requests = template.convertRequestToProtocolsRequest(template.RequestsDNS)
|
||||
|
||||
case len(template.RequestsFile) > 0:
|
||||
requests = template.convertRequestToProtocolsRequest(template.RequestsFile)
|
||||
|
||||
case len(template.RequestsNetwork) > 0:
|
||||
requests = template.convertRequestToProtocolsRequest(template.RequestsNetwork)
|
||||
|
||||
case len(template.RequestsHTTP) > 0:
|
||||
requests = template.convertRequestToProtocolsRequest(template.RequestsHTTP)
|
||||
|
||||
case len(template.RequestsHeadless) > 0 && options.Options.Headless:
|
||||
requests = template.convertRequestToProtocolsRequest(template.RequestsHeadless)
|
||||
|
||||
case len(template.RequestsSSL) > 0:
|
||||
requests = template.convertRequestToProtocolsRequest(template.RequestsSSL)
|
||||
|
||||
case len(template.RequestsWebsocket) > 0:
|
||||
requests = template.convertRequestToProtocolsRequest(template.RequestsWebsocket)
|
||||
}
|
||||
template.Executer = executer.NewExecuter(requests, &options)
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertRequestToProtocolsRequest is a convenience wrapper to convert
|
||||
// arbitrary interfaces which are slices of requests from the template to a
|
||||
// slice of protocols.Request interface items.
|
||||
func (template *Template) convertRequestToProtocolsRequest(requests interface{}) []protocols.Request {
|
||||
switch reflect.TypeOf(requests).Kind() {
|
||||
case reflect.Slice:
|
||||
s := reflect.ValueOf(requests)
|
||||
|
||||
requestSlice := make([]protocols.Request, s.Len())
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
value := s.Index(i)
|
||||
valueInterface := value.Interface()
|
||||
requestSlice[i] = valueInterface.(protocols.Request)
|
||||
}
|
||||
return requestSlice
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// compileOfflineHTTPRequest iterates all requests if offline http mode is
|
||||
// specified and collects all matchers for all the base request templates
|
||||
// (those with URL {{BaseURL}} and it's slash variation.)
|
||||
func (template *Template) compileOfflineHTTPRequest(options protocols.ExecuterOptions) {
|
||||
operatorsList := []*operators.Operators{}
|
||||
|
||||
mainLoop:
|
||||
for _, req := range template.RequestsHTTP {
|
||||
for _, path := range req.Path {
|
||||
if !(strings.EqualFold(path, "{{BaseURL}}") || strings.EqualFold(path, "{{BaseURL}}/")) {
|
||||
break mainLoop
|
||||
}
|
||||
}
|
||||
operatorsList = append(operatorsList, &req.Operators)
|
||||
}
|
||||
if len(operatorsList) > 0 {
|
||||
options.Operators = operatorsList
|
||||
template.Executer = executer.NewExecuter([]protocols.Request{&offlinehttp.Request{}}, &options)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ import (
|
|||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/headless"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/http"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/network"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/ssl"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/websocket"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/templates/types"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/workflows"
|
||||
)
|
||||
|
@ -57,6 +59,12 @@ type Template struct {
|
|||
// description: |
|
||||
// Headless contains the headless request to make in the template.
|
||||
RequestsHeadless []*headless.Request `yaml:"headless,omitempty" json:"headless,omitempty" jsonschema:"title=headless requests to make,description=Headless requests to make for the template"`
|
||||
// description: |
|
||||
// SSL contains the SSL request to make in the template.
|
||||
RequestsSSL []*ssl.Request `yaml:"ssl,omitempty" json:"ssl,omitempty" jsonschema:"title=ssl requests to make,description=SSL requests to make for the template"`
|
||||
// description: |
|
||||
// Websocket contains the Websocket request to make in the template.
|
||||
RequestsWebsocket []*websocket.Request `yaml:"websocket,omitempty" json:"websocket,omitempty" jsonschema:"title=websocket requests to make,description=Websocket requests to make for the template"`
|
||||
|
||||
// description: |
|
||||
// Workflows is a yaml based workflow declaration code.
|
||||
|
@ -75,6 +83,18 @@ type Template struct {
|
|||
Path string `yaml:"-" json:"-"`
|
||||
}
|
||||
|
||||
// TemplateProtocols is a list of accepted template protocols
|
||||
var TemplateProtocols = []string{
|
||||
"dns",
|
||||
"file",
|
||||
"http",
|
||||
"headless",
|
||||
"network",
|
||||
"workflow",
|
||||
"ssl",
|
||||
"websocket",
|
||||
}
|
||||
|
||||
// Type returns the type of the template
|
||||
func (t *Template) Type() types.ProtocolType {
|
||||
switch {
|
||||
|
@ -90,6 +110,10 @@ func (t *Template) Type() types.ProtocolType {
|
|||
return types.NetworkProtocol
|
||||
case t.CompiledWorkflow != nil:
|
||||
return types.WorkflowProtocol
|
||||
case len(t.RequestsSSL) > 0:
|
||||
return types.SSLProtocol
|
||||
case len(t.RequestsWebsocket) > 0:
|
||||
return types.WebsocketProtocol
|
||||
default:
|
||||
return types.InvalidProtocol
|
||||
}
|
||||
|
|
|
@ -37,11 +37,11 @@ var (
|
|||
_ = exampleNormalHTTPRequest
|
||||
|
||||
exampleNormalDNSRequest = &dns.Request{
|
||||
Name: "{{FQDN}}",
|
||||
Type: "CNAME",
|
||||
Class: "inet",
|
||||
Retries: 2,
|
||||
Recursion: true,
|
||||
Name: "{{FQDN}}",
|
||||
RequestType: "CNAME",
|
||||
Class: "inet",
|
||||
Retries: 2,
|
||||
Recursion: true,
|
||||
Operators: operators.Operators{
|
||||
Extractors: []*extractors.Extractor{
|
||||
{Type: "regex", Regex: []string{"ec2-[-\\d]+\\.compute[-\\d]*\\.amazonaws\\.com", "ec2-[-\\d]+\\.[\\w\\d\\-]+\\.compute[-\\d]*\\.amazonaws\\.com"}},
|
||||
|
|
|
@ -22,19 +22,23 @@ const (
|
|||
HeadlessProtocol
|
||||
NetworkProtocol
|
||||
WorkflowProtocol
|
||||
SSLProtocol
|
||||
WebsocketProtocol
|
||||
limit
|
||||
InvalidProtocol
|
||||
)
|
||||
|
||||
// ExtractorTypes is a table for conversion of extractor type from string.
|
||||
var protocolMappings = map[ProtocolType]string{
|
||||
InvalidProtocol: "invalid",
|
||||
DNSProtocol: "dns",
|
||||
FileProtocol: "file",
|
||||
HTTPProtocol: "http",
|
||||
HeadlessProtocol: "headless",
|
||||
NetworkProtocol: "network",
|
||||
WorkflowProtocol: "workflow",
|
||||
InvalidProtocol: "invalid",
|
||||
DNSProtocol: "dns",
|
||||
FileProtocol: "file",
|
||||
HTTPProtocol: "http",
|
||||
HeadlessProtocol: "headless",
|
||||
NetworkProtocol: "network",
|
||||
WorkflowProtocol: "workflow",
|
||||
SSLProtocol: "ssl",
|
||||
WebsocketProtocol: "websocket",
|
||||
}
|
||||
|
||||
func GetSupportedProtocolTypes() ProtocolTypes {
|
||||
|
|
|
@ -62,19 +62,7 @@ func parseWorkflowTemplate(workflow *workflows.WorkflowTemplate, preprocessor Pr
|
|||
return nil
|
||||
}
|
||||
for _, path := range paths {
|
||||
opts := protocols.ExecuterOptions{
|
||||
Output: options.Output,
|
||||
Options: options.Options,
|
||||
Progress: options.Progress,
|
||||
Catalog: options.Catalog,
|
||||
Browser: options.Browser,
|
||||
RateLimiter: options.RateLimiter,
|
||||
IssuesClient: options.IssuesClient,
|
||||
Interactsh: options.Interactsh,
|
||||
ProjectFile: options.ProjectFile,
|
||||
HostErrorsCache: options.HostErrorsCache,
|
||||
}
|
||||
template, err := Parse(path, preprocessor, opts)
|
||||
template, err := Parse(path, preprocessor, options.Copy())
|
||||
if err != nil {
|
||||
gologger.Warning().Msgf("Could not parse workflow template %s: %v\n", path, err)
|
||||
continue
|
||||
|
|
|
@ -4,10 +4,15 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/gobwas/ws"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
// RunNucleiTemplateAndGetResults returns a list of results for a template
|
||||
|
@ -124,3 +129,29 @@ func NewTCPServer(handler func(conn net.Conn), port ...int) *TCPServer {
|
|||
func (s *TCPServer) Close() {
|
||||
s.listener.Close()
|
||||
}
|
||||
|
||||
// NewWebsocketServer creates a new websocket server from a handler
|
||||
func NewWebsocketServer(path string, handler func(conn net.Conn), originValidate func(origin string) bool, port ...int) *httptest.Server {
|
||||
handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if value := r.Header.Get("Origin"); value != "" && !originValidate(value) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
conn, _, _, err := ws.UpgradeHTTP(r, w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
defer conn.Close()
|
||||
|
||||
handler(conn)
|
||||
}()
|
||||
})
|
||||
|
||||
if path != "" {
|
||||
router := httprouter.New()
|
||||
router.HandlerFunc("*", "/test", handlerFunc)
|
||||
return httptest.NewServer(router)
|
||||
}
|
||||
return httptest.NewServer(handlerFunc)
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
package testutils
|
||||
|
||||
import (
|
||||
"github.com/logrusorgru/aurora"
|
||||
"go.uber.org/ratelimit"
|
||||
|
||||
"github.com/logrusorgru/aurora"
|
||||
"github.com/projectdiscovery/gologger/levels"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/catalog"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/model"
|
||||
|
@ -64,6 +64,38 @@ var DefaultOptions = &types.Options{
|
|||
InteractionsPollDuration: 5,
|
||||
}
|
||||
|
||||
// TemplateInfo contains info for a mock executed template.
|
||||
type TemplateInfo struct {
|
||||
ID string
|
||||
Info model.Info
|
||||
Path string
|
||||
}
|
||||
|
||||
// NewMockExecuterOptions creates a new mock executeroptions struct
|
||||
func NewMockExecuterOptions(options *types.Options, info *TemplateInfo) *protocols.ExecuterOptions {
|
||||
progressImpl, _ := progress.NewStatsTicker(0, false, false, false, 0)
|
||||
executerOpts := &protocols.ExecuterOptions{
|
||||
TemplateID: info.ID,
|
||||
TemplateInfo: info.Info,
|
||||
TemplatePath: info.Path,
|
||||
Output: NewMockOutputWriter(),
|
||||
Options: options,
|
||||
Progress: progressImpl,
|
||||
ProjectFile: nil,
|
||||
IssuesClient: nil,
|
||||
Browser: nil,
|
||||
Catalog: catalog.New(options.TemplatesDirectory),
|
||||
RateLimiter: ratelimit.New(options.RateLimit),
|
||||
}
|
||||
return executerOpts
|
||||
}
|
||||
|
||||
// NoopWriter is a NooP gologger writer.
|
||||
type NoopWriter struct{}
|
||||
|
||||
// Write writes the data to an output writer.
|
||||
func (n *NoopWriter) Write(data []byte, level levels.Level) {}
|
||||
|
||||
// MockOutputWriter is a mocked output writer.
|
||||
type MockOutputWriter struct {
|
||||
aurora aurora.Aurora
|
||||
|
@ -99,34 +131,26 @@ func (m *MockOutputWriter) Request(templateID, url, requestType string, err erro
|
|||
}
|
||||
}
|
||||
|
||||
// TemplateInfo contains info for a mock executed template.
|
||||
type TemplateInfo struct {
|
||||
ID string
|
||||
Info model.Info
|
||||
Path string
|
||||
}
|
||||
type MockProgressClient struct{}
|
||||
|
||||
// NewMockExecuterOptions creates a new mock executeroptions struct
|
||||
func NewMockExecuterOptions(options *types.Options, info *TemplateInfo) *protocols.ExecuterOptions {
|
||||
progressImpl, _ := progress.NewStatsTicker(0, false, false, false, 0)
|
||||
executerOpts := &protocols.ExecuterOptions{
|
||||
TemplateID: info.ID,
|
||||
TemplateInfo: info.Info,
|
||||
TemplatePath: info.Path,
|
||||
Output: NewMockOutputWriter(),
|
||||
Options: options,
|
||||
Progress: progressImpl,
|
||||
ProjectFile: nil,
|
||||
IssuesClient: nil,
|
||||
Browser: nil,
|
||||
Catalog: catalog.New(options.TemplatesDirectory),
|
||||
RateLimiter: ratelimit.New(options.RateLimit),
|
||||
}
|
||||
return executerOpts
|
||||
}
|
||||
// Stop stops the progress recorder.
|
||||
func (m *MockProgressClient) Stop() {}
|
||||
|
||||
// NoopWriter is a NooP gologger writer.
|
||||
type NoopWriter struct{}
|
||||
// Init inits the progress bar with initial details for scan
|
||||
func (m *MockProgressClient) Init(hostCount int64, rulesCount int, requestCount int64) {}
|
||||
|
||||
// Write writes the data to an output writer.
|
||||
func (n *NoopWriter) Write(data []byte, level levels.Level) {}
|
||||
// AddToTotal adds a value to the total request count
|
||||
func (m *MockProgressClient) AddToTotal(delta int64) {}
|
||||
|
||||
// IncrementRequests increments the requests counter by 1.
|
||||
func (m *MockProgressClient) IncrementRequests() {}
|
||||
|
||||
// IncrementMatched increments the matched counter by 1.
|
||||
func (m *MockProgressClient) IncrementMatched() {}
|
||||
|
||||
// IncrementErrorsBy increments the error counter by count.
|
||||
func (m *MockProgressClient) IncrementErrorsBy(count int64) {}
|
||||
|
||||
// IncrementFailedRequestsBy increments the number of requests counter by count
|
||||
// along with errors.
|
||||
func (m *MockProgressClient) IncrementFailedRequestsBy(count int64) {}
|
|
@ -34,12 +34,12 @@ type Options struct {
|
|||
Severities severity.Severities
|
||||
// ExcludeSeverities specifies severities to exclude
|
||||
ExcludeSeverities severity.Severities
|
||||
// Authors filters templates based on their author and only run the matching ones.
|
||||
Authors goflags.NormalizedStringSlice
|
||||
// Protocols contains the protocols to be allowed executed
|
||||
Protocols types.ProtocolTypes
|
||||
// ExcludeProtocols contains protocols to not be executed
|
||||
ExcludeProtocols types.ProtocolTypes
|
||||
// Author filters templates based on their author and only run the matching ones.
|
||||
Author goflags.NormalizedStringSlice
|
||||
// IncludeTags includes specified tags to be run even while being in denylist
|
||||
IncludeTags goflags.NormalizedStringSlice
|
||||
// IncludeTemplates includes specified templates to be run even while being in denylist
|
||||
|
@ -86,6 +86,10 @@ type Options struct {
|
|||
BulkSize int
|
||||
// TemplateThreads is the number of templates executed in parallel
|
||||
TemplateThreads int
|
||||
// HeadlessBulkSize is the of targets analyzed in parallel for each headless template
|
||||
HeadlessBulkSize int
|
||||
// HeadlessTemplateThreads is the number of headless templates executed in parallel
|
||||
HeadlessTemplateThreads int
|
||||
// Timeout is the seconds to wait for a response from the server.
|
||||
Timeout int
|
||||
// Retries is the number of times to retry the request
|
||||
|
@ -192,3 +196,17 @@ func (options *Options) AddVarPayload(key string, value interface{}) {
|
|||
func (options *Options) VarsPayload() map[string]interface{} {
|
||||
return options.varsPayload
|
||||
}
|
||||
|
||||
// DefaultOptions returns default options for nuclei
|
||||
func DefaultOptions() *Options {
|
||||
return &Options{
|
||||
RateLimit: 150,
|
||||
BulkSize: 25,
|
||||
TemplateThreads: 25,
|
||||
HeadlessBulkSize: 10,
|
||||
HeadlessTemplateThreads: 10,
|
||||
Timeout: 5,
|
||||
Retries: 1,
|
||||
MaxHostError: 30,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue