From d14c00fc6f277477792a28b01520c001990aa5ce Mon Sep 17 00:00:00 2001 From: Sami <85764322+LuitelSamikshya@users.noreply.github.com> Date: Wed, 17 Aug 2022 08:10:27 -0500 Subject: [PATCH 01/25] added validation for headless templates (#2423) * added validation for headless templates * minor update in log msg --- v2/pkg/catalog/loader/loader.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/v2/pkg/catalog/loader/loader.go b/v2/pkg/catalog/loader/loader.go index a1679cbd1..87b54c257 100644 --- a/v2/pkg/catalog/loader/loader.go +++ b/v2/pkg/catalog/loader/loader.go @@ -267,7 +267,11 @@ func (store *Store) LoadTemplates(templatesList []string) []*templates.Template stats.Increment(parsers.RuntimeWarningsStats) gologger.Warning().Msgf("Could not parse template %s: %s\n", templatePath, err) } else if parsed != nil { - loadedTemplates = append(loadedTemplates, parsed) + if len(parsed.RequestsHeadless) > 0 && !store.config.ExecutorOptions.Options.Headless { + gologger.Warning().Msgf("Headless flag is required for headless template %s\n", templatePath) + } else { + loadedTemplates = append(loadedTemplates, parsed) + } } } else if err != nil { gologger.Warning().Msgf("Could not load template %s: %s\n", templatePath, err) @@ -314,7 +318,11 @@ func (store *Store) LoadTemplatesWithTags(templatesList, tags []string) []*templ stats.Increment(parsers.RuntimeWarningsStats) gologger.Warning().Msgf("Could not parse template %s: %s\n", templatePath, err) } else if parsed != nil { - loadedTemplates = append(loadedTemplates, parsed) + if len(parsed.RequestsHeadless) > 0 && !store.config.ExecutorOptions.Options.Headless { + gologger.Warning().Msgf("Headless flag is required for headless template %s\n", templatePath) + } else { + loadedTemplates = append(loadedTemplates, parsed) + } } } else if err != nil { gologger.Warning().Msgf("Could not load template %s: %s\n", templatePath, err) From c1a99ae452553e1b852b1398e81e09f4d36c8623 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Aug 2022 15:13:03 +0530 Subject: [PATCH 02/25] chore(deps): bump github.com/projectdiscovery/wappalyzergo in /v2 (#2469) Bumps [github.com/projectdiscovery/wappalyzergo](https://github.com/projectdiscovery/wappalyzergo) from 0.0.55 to 0.0.56. - [Release notes](https://github.com/projectdiscovery/wappalyzergo/releases) - [Commits](https://github.com/projectdiscovery/wappalyzergo/compare/v0.0.55...v0.0.56) --- updated-dependencies: - dependency-name: github.com/projectdiscovery/wappalyzergo dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- v2/go.mod | 2 +- v2/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/v2/go.mod b/v2/go.mod index d18b96ee7..4bc49ddc8 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -77,7 +77,7 @@ require ( github.com/projectdiscovery/nvd v1.0.9 github.com/projectdiscovery/sliceutil v0.0.0-20220511171050-c7d9bc5cadd9 github.com/projectdiscovery/urlutil v0.0.0-20210525140139-b874f06ad921 - github.com/projectdiscovery/wappalyzergo v0.0.55 + github.com/projectdiscovery/wappalyzergo v0.0.56 github.com/stretchr/testify v1.8.0 github.com/zmap/zcrypto v0.0.0-20211005224000-2d0ffdec8a9b gopkg.in/yaml.v3 v3.0.1 diff --git a/v2/go.sum b/v2/go.sum index a47ac5ab6..291e7b3f8 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -584,8 +584,8 @@ github.com/projectdiscovery/stringsutil v0.0.0-20220612082425-0037ce9f89f3 h1:Eb github.com/projectdiscovery/stringsutil v0.0.0-20220612082425-0037ce9f89f3/go.mod h1:mF5sh4jTghoGWwgUb9qWi5waTFklClDbtrqtJU93awc= github.com/projectdiscovery/urlutil v0.0.0-20210525140139-b874f06ad921 h1:EgaxpJm7+lKppfAHkFHs+S+II0lodp4Gu3leZCCkWlc= github.com/projectdiscovery/urlutil v0.0.0-20210525140139-b874f06ad921/go.mod h1:oXLErqOpqEAp/ueQlknysFxHO3CUNoSiDNnkiHG+Jpo= -github.com/projectdiscovery/wappalyzergo v0.0.55 h1:dDWuohTrAUWrphnFB+uAL33QrDWBbdM+z7sTzSxy8PY= -github.com/projectdiscovery/wappalyzergo v0.0.55/go.mod h1:9aSADdt5z/pw9LFZF7Q8RrLnkyqZl1H4Ezivi8Td7l0= +github.com/projectdiscovery/wappalyzergo v0.0.56 h1:774LsI8tAUAYROQhTX9VsxoQN2kAC5m9CcNj3BHsvTs= +github.com/projectdiscovery/wappalyzergo v0.0.56/go.mod h1:9aSADdt5z/pw9LFZF7Q8RrLnkyqZl1H4Ezivi8Td7l0= github.com/projectdiscovery/yamldoc-go v1.0.2/go.mod h1:7uSxfMXaBmzvw8m5EhOEjB6nhz0rK/H9sUjq1ciZu24= github.com/projectdiscovery/yamldoc-go v1.0.3-0.20211126104922-00d2c6bb43b6 h1:DvWRQpw7Ib2CRL3ogYm/BWM+X0UGPfz1n9Ix9YKgFM8= github.com/projectdiscovery/yamldoc-go v1.0.3-0.20211126104922-00d2c6bb43b6/go.mod h1:8OfZj8p/axkUM/TJoS/O9LDjj/S8u17rxRbqluE9CU4= From c184b84ebf6c79c21c76550bd8a1f62b5359b325 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Aug 2022 15:13:40 +0530 Subject: [PATCH 03/25] chore(deps): bump github.com/aws/aws-sdk-go in /v2 (#2468) Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.77 to 1.44.81. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.77...v1.44.81) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- v2/go.mod | 2 +- v2/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/v2/go.mod b/v2/go.mod index 4bc49ddc8..12dca62ae 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -59,7 +59,7 @@ require ( moul.io/http2curl v1.0.0 ) -require github.com/aws/aws-sdk-go v1.44.77 +require github.com/aws/aws-sdk-go v1.44.81 require github.com/projectdiscovery/folderutil v0.0.0-20220215113126-add60a1e8e08 diff --git a/v2/go.sum b/v2/go.sum index 291e7b3f8..95cdbfcce 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -112,8 +112,8 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.44.77 h1:m5rTfdv04/swD+vTuS2zn4NEwKX3yEJPMhiVCFDL/mU= -github.com/aws/aws-sdk-go v1.44.77/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.81 h1:C8oBZ+a+ka0qk3Q24MohQIFq0tkbO8IAu5tfpAMKVWE= +github.com/aws/aws-sdk-go v1.44.81/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= 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= From 2ae7e58c83d07c8ab246b84d5bd82915a75403fb Mon Sep 17 00:00:00 2001 From: xixijun Date: Mon, 22 Aug 2022 17:48:45 +0800 Subject: [PATCH 04/25] Fix socks5 proxy not working on tor proxy (#2455) * fix: socks5 proxy not working on tor proxy * fix: socks5 proxy not working on tor proxy * minor refactoring Co-authored-by: Sandeep Singh Co-authored-by: Mzack9999 --- v2/pkg/protocols/headless/engine/http_client.go | 17 ++++++++--------- .../protocols/http/httpclientpool/clientpool.go | 17 ++++++++--------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/v2/pkg/protocols/headless/engine/http_client.go b/v2/pkg/protocols/headless/engine/http_client.go index 89e7eafa4..ea32aa5e3 100644 --- a/v2/pkg/protocols/headless/engine/http_client.go +++ b/v2/pkg/protocols/headless/engine/http_client.go @@ -3,18 +3,16 @@ package engine import ( "context" "crypto/tls" - "fmt" "net" "net/http" "net/http/cookiejar" "net/url" "time" - "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils" - "golang.org/x/net/proxy" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils" "github.com/projectdiscovery/nuclei/v2/pkg/types" ) @@ -53,14 +51,15 @@ func newHttpClient(options *types.Options) (*http.Client, error) { transport.Proxy = http.ProxyURL(proxyURL) } } else if types.ProxySocksURL != "" { - var proxyAuth *proxy.Auth socksURL, proxyErr := url.Parse(types.ProxySocksURL) - if proxyErr == nil { - proxyAuth = &proxy.Auth{} - proxyAuth.User = socksURL.User.Username() - proxyAuth.Password, _ = socksURL.User.Password() + if proxyErr != nil { + return nil, err } - dialer, proxyErr := proxy.SOCKS5("tcp", fmt.Sprintf("%s:%s", socksURL.Hostname(), socksURL.Port()), proxyAuth, proxy.Direct) + dialer, err := proxy.FromURL(socksURL, proxy.Direct) + if err != nil { + return nil, err + } + dc := dialer.(interface { DialContext(ctx context.Context, network, addr string) (net.Conn, error) }) diff --git a/v2/pkg/protocols/http/httpclientpool/clientpool.go b/v2/pkg/protocols/http/httpclientpool/clientpool.go index ff53d54a4..393f70c06 100644 --- a/v2/pkg/protocols/http/httpclientpool/clientpool.go +++ b/v2/pkg/protocols/http/httpclientpool/clientpool.go @@ -3,7 +3,6 @@ package httpclientpool import ( "context" "crypto/tls" - "fmt" "net" "net/http" "net/http/cookiejar" @@ -13,14 +12,13 @@ import ( "sync" "time" - "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils" - "github.com/pkg/errors" "golang.org/x/net/proxy" "golang.org/x/net/publicsuffix" "github.com/projectdiscovery/fastdialer/fastdialer" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/utils" "github.com/projectdiscovery/nuclei/v2/pkg/types" "github.com/projectdiscovery/rawhttp" "github.com/projectdiscovery/retryablehttp-go" @@ -210,14 +208,15 @@ func wrappedGet(options *types.Options, configuration *Configuration) (*retryabl transport.Proxy = http.ProxyURL(proxyURL) } } else if types.ProxySocksURL != "" { - var proxyAuth *proxy.Auth socksURL, proxyErr := url.Parse(types.ProxySocksURL) - if proxyErr == nil { - proxyAuth = &proxy.Auth{} - proxyAuth.User = socksURL.User.Username() - proxyAuth.Password, _ = socksURL.User.Password() + if proxyErr != nil { + return nil, proxyErr } - dialer, proxyErr := proxy.SOCKS5("tcp", fmt.Sprintf("%s:%s", socksURL.Hostname(), socksURL.Port()), proxyAuth, proxy.Direct) + dialer, err := proxy.FromURL(socksURL, proxy.Direct) + if err != nil { + return nil, err + } + dc := dialer.(interface { DialContext(ctx context.Context, network, addr string) (net.Conn, error) }) From 419924188b137087bc0986352a062e4c3ec5dddb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Aug 2022 15:19:30 +0530 Subject: [PATCH 05/25] chore(deps): bump github.com/andygrunwald/go-jira in /v2 (#2470) Bumps [github.com/andygrunwald/go-jira](https://github.com/andygrunwald/go-jira) from 1.15.1 to 1.16.0. - [Release notes](https://github.com/andygrunwald/go-jira/releases) - [Changelog](https://github.com/andygrunwald/go-jira/blob/main/CHANGELOG.md) - [Commits](https://github.com/andygrunwald/go-jira/compare/v1.15.1...v1.16.0) --- updated-dependencies: - dependency-name: github.com/andygrunwald/go-jira dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- v2/go.mod | 4 ++-- v2/go.sum | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/v2/go.mod b/v2/go.mod index 12dca62ae..23c7013cf 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -5,7 +5,7 @@ go 1.17 require ( github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible github.com/alecthomas/jsonschema v0.0.0-20211022214203-8b29eab41725 - github.com/andygrunwald/go-jira v1.15.1 + github.com/andygrunwald/go-jira v1.16.0 github.com/antchfx/htmlquery v1.2.5 github.com/apex/log v1.9.0 github.com/blang/semver v3.5.1+incompatible @@ -110,7 +110,7 @@ require ( github.com/goburrow/cache v0.1.4 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect - github.com/golang-jwt/jwt/v4 v4.3.0 // indirect + github.com/golang-jwt/jwt/v4 v4.4.2 // 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 diff --git a/v2/go.sum b/v2/go.sum index 95cdbfcce..ad05b0995 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -93,8 +93,8 @@ github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgp github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/andygrunwald/go-jira v1.14.0/go.mod h1:KMo2f4DgMZA1C9FdImuLc04x4WQhn5derQpnsuBFgqE= -github.com/andygrunwald/go-jira v1.15.1 h1:6J9aYKb9sW8bxv3pBLYBrs0wdsFrmGI5IeTgWSKWKc8= -github.com/andygrunwald/go-jira v1.15.1/go.mod h1:GIYN1sHOIsENWUZ7B4pDeT/nxEtrZpE8l0987O67ZR8= +github.com/andygrunwald/go-jira v1.16.0 h1:PU7C7Fkk5L96JvPc6vDVIrd99vdPnYudHu4ju2c2ikQ= +github.com/andygrunwald/go-jira v1.16.0/go.mod h1:UQH4IBVxIYWbgagc0LF/k9FRs9xjIiQ8hIcC6HfLwFU= github.com/antchfx/htmlquery v1.2.3/go.mod h1:B0ABL+F5irhhMWg54ymEZinzMSi0Kt3I2if0BLYa3V0= github.com/antchfx/htmlquery v1.2.5 h1:1lXnx46/1wtv1E/kzmH8vrfMuUKYgkdDBA9pIdMJnk4= github.com/antchfx/htmlquery v1.2.5/go.mod h1:2MCVBzYVafPBmKbrmwB9F5xdd+IEgRY61ci2oOsOQVw= @@ -230,8 +230,8 @@ 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/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= -github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -982,6 +982,7 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 8670c8b20d4de7887dcd3d2cac24908a2a4be71b Mon Sep 17 00:00:00 2001 From: Dani Goland Date: Mon, 22 Aug 2022 02:57:32 -0700 Subject: [PATCH 06/25] Modified "xpath" extractor to support XML XPath in addition to HTML XPath (#2471) * Modified "xpath" extractor to support XML XPath in addition to HTML XPath * Updated function docs --- v2/go.mod | 1 + v2/go.sum | 2 ++ v2/pkg/operators/extractors/extract.go | 44 ++++++++++++++++++++++++-- v2/pkg/protocols/http/operators.go | 2 +- v2/pkg/protocols/protocols.go | 2 +- 5 files changed, 46 insertions(+), 5 deletions(-) diff --git a/v2/go.mod b/v2/go.mod index 23c7013cf..b7de3cb2f 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -65,6 +65,7 @@ require github.com/projectdiscovery/folderutil v0.0.0-20220215113126-add60a1e8e0 require ( github.com/DataDog/gostackparse v0.5.0 + github.com/antchfx/xmlquery v1.3.12 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/docker/go-units v0.4.0 github.com/h2non/filetype v1.1.3 diff --git a/v2/go.sum b/v2/go.sum index ad05b0995..f9062f0fc 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -98,6 +98,8 @@ github.com/andygrunwald/go-jira v1.16.0/go.mod h1:UQH4IBVxIYWbgagc0LF/k9FRs9xjIi github.com/antchfx/htmlquery v1.2.3/go.mod h1:B0ABL+F5irhhMWg54ymEZinzMSi0Kt3I2if0BLYa3V0= github.com/antchfx/htmlquery v1.2.5 h1:1lXnx46/1wtv1E/kzmH8vrfMuUKYgkdDBA9pIdMJnk4= github.com/antchfx/htmlquery v1.2.5/go.mod h1:2MCVBzYVafPBmKbrmwB9F5xdd+IEgRY61ci2oOsOQVw= +github.com/antchfx/xmlquery v1.3.12 h1:6TMGpdjpO/P8VhjnaYPXuqT3qyJ/VsqoyNTmJzNBTQ4= +github.com/antchfx/xmlquery v1.3.12/go.mod h1:3w2RvQvTz+DaT5fSgsELkSJcdNgkmg6vuXDEuhdwsPQ= github.com/antchfx/xpath v1.1.6/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= github.com/antchfx/xpath v1.2.1 h1:qhp4EW6aCOVr5XIkT+l6LJ9ck/JsUH/yyauNgTQkBF8= github.com/antchfx/xpath v1.2.1/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= diff --git a/v2/pkg/operators/extractors/extract.go b/v2/pkg/operators/extractors/extract.go index e8fafc3ba..d5c948ef2 100644 --- a/v2/pkg/operators/extractors/extract.go +++ b/v2/pkg/operators/extractors/extract.go @@ -3,9 +3,9 @@ package extractors import ( "encoding/json" "fmt" - "strings" - "github.com/antchfx/htmlquery" + "github.com/antchfx/xmlquery" + "strings" "github.com/projectdiscovery/nuclei/v2/pkg/types" ) @@ -59,7 +59,15 @@ func (e *Extractor) ExtractKval(data map[string]interface{}) map[string]struct{} return results } -// ExtractHTML extracts items from text using XPath selectors +// ExtractXPath extracts items from text using XPath selectors +func (e *Extractor) ExtractXPath(corpus string) map[string]struct{} { + if strings.HasPrefix(corpus, " Date: Mon, 22 Aug 2022 18:11:08 +0530 Subject: [PATCH 07/25] Added reference based tag addition to templates (#2464) * Added reference based tag addition to templates * reference mapping list update * Misc changes as per review Co-authored-by: sandeep --- v2/cmd/cve-annotate/main.go | 79 +++++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 4 deletions(-) diff --git a/v2/cmd/cve-annotate/main.go b/v2/cmd/cve-annotate/main.go index f64bb9d87..46df7d149 100644 --- a/v2/cmd/cve-annotate/main.go +++ b/v2/cmd/cve-annotate/main.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "log" "net/http" + "net/url" "os" "regexp" "strings" @@ -71,7 +72,16 @@ func process() error { if err != nil { return err } - getCVEData(client, path, string(data)) + dataString := string(data) + + // First try to resolve references to tags + dataString, err = parseAndAddReferenceBasedTags(path, dataString) + if err != nil { + log.Printf("Could not parse reference tags %s: %s\n", path, err) + continue + } + // Next try and fill CVE data + getCVEData(client, path, dataString) } return nil } @@ -290,12 +300,15 @@ func parseAndAddCISAKevTagTemplate(path string, data string) (string, error) { return "", errors.Wrap(err, "could not decode template yaml") } splitted := strings.Split(block.Info.Tags, ",") + if len(splitted) == 0 { + return data, nil + } var cisaIndex = -1 for i, tag := range splitted { // If we already have tag, return if tag == "kev" { - return "", nil + return data, nil } if tag == "cisa" { cisaIndex = i @@ -306,11 +319,69 @@ func parseAndAddCISAKevTagTemplate(path string, data string) (string, error) { splitted = append(splitted[:cisaIndex], splitted[cisaIndex+1:]...) } splitted = append(splitted, "kev") - final := strings.Join(splitted, ",") - replaced := strings.Replace(data, block.Info.Tags, final, -1) + replaced := strings.ReplaceAll(data, block.Info.Tags, strings.Join(splitted, ",")) return replaced, ioutil.WriteFile(path, []byte(replaced), os.ModePerm) } +// parseAndAddReferenceBasedTags parses and adds reference based tags to templates +func parseAndAddReferenceBasedTags(path string, data string) (string, error) { + block := &InfoBlock{} + if err := yaml.NewDecoder(strings.NewReader(data)).Decode(block); err != nil { + return "", errors.Wrap(err, "could not decode template yaml") + } + splitted := strings.Split(block.Info.Tags, ",") + if len(splitted) == 0 { + return data, nil + } + tagsCurrent := fmt.Sprintf("tags: %s", block.Info.Tags) + newTags := suggestTagsBasedOnReference(block.Info.Reference, splitted) + + if len(newTags) == len(splitted) { + return data, nil + } + replaced := strings.ReplaceAll(data, tagsCurrent, fmt.Sprintf("tags: %s", strings.Join(newTags, ","))) + return replaced, ioutil.WriteFile(path, []byte(replaced), os.ModePerm) +} + +var referenceMapping = map[string]string{ + "huntr.dev": "huntr", + "hackerone.com": "hackerone", + "tenable.com": "tenable", + "packetstormsecurity.org": "packetstorm", + "seclists.org": "seclists", + "wpscan.com": "wpscan", + "packetstormsecurity.com": "packetstorm", + "exploit-db.com": "edb", + "https://github.com/rapid7/metasploit-framework/": "msf", + "https://github.com/vulhub/vulhub/": "vulhub", +} + +func suggestTagsBasedOnReference(references, currentTags []string) []string { + uniqueTags := make(map[string]struct{}) + for _, value := range currentTags { + uniqueTags[value] = struct{}{} + } + + for _, reference := range references { + parsed, err := url.Parse(reference) + if err != nil { + continue + } + hostname := parsed.Hostname() + + for value, tag := range referenceMapping { + if strings.HasSuffix(hostname, value) || strings.HasPrefix(reference, value) { + uniqueTags[tag] = struct{}{} + } + } + } + newTags := make([]string, 0, len(uniqueTags)) + for tag := range uniqueTags { + newTags = append(newTags, tag) + } + return newTags +} + // Cloning struct from nuclei as we don't want any validation type InfoBlock struct { Info TemplateInfo `yaml:"info"` From e7cffad31205bcd059daec488c7addb95dca084e Mon Sep 17 00:00:00 2001 From: Ice3man Date: Tue, 23 Aug 2022 12:45:55 +0530 Subject: [PATCH 08/25] Fixed request annotation based timeout bugs + tests + misc (#2476) --- .../http/annotation-timeout.yaml | 18 ++++++++ v2/cmd/integration-test/http.go | 21 +++++++++ v2/pkg/protocols/http/build_request.go | 9 +++- v2/pkg/protocols/http/request_annotations.go | 4 +- .../http/request_annotations_test.go | 45 +++++++++++++++++++ 5 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 integration_tests/http/annotation-timeout.yaml create mode 100644 v2/pkg/protocols/http/request_annotations_test.go diff --git a/integration_tests/http/annotation-timeout.yaml b/integration_tests/http/annotation-timeout.yaml new file mode 100644 index 000000000..ddb0b3e6a --- /dev/null +++ b/integration_tests/http/annotation-timeout.yaml @@ -0,0 +1,18 @@ +id: annotation-timeout + +info: + name: Basic Annotation Timeout + author: pdteam + severity: info + +requests: + - raw: + - | + @timeout: 5s + GET / HTTP/1.1 + Host: {{Hostname}} + + matchers: + - type: word + words: + - "This is test matcher text" \ No newline at end of file diff --git a/v2/cmd/integration-test/http.go b/v2/cmd/integration-test/http.go index c4ca5e9a4..0fdf9f566 100644 --- a/v2/cmd/integration-test/http.go +++ b/v2/cmd/integration-test/http.go @@ -11,6 +11,7 @@ import ( "regexp" "strconv" "strings" + "time" "github.com/julienschmidt/httprouter" @@ -52,6 +53,7 @@ var httpTestcases = map[string]testutils.TestCase{ "http/get-sni.yaml": &customCLISNI{}, "http/redirect-match-url.yaml": &httpRedirectMatchURL{}, "http/get-sni-unsafe.yaml": &customCLISNIUnsafe{}, + "http/annotation-timeout.yaml": &annotationTimeout{}, } type httpInteractshRequest struct{} @@ -909,3 +911,22 @@ func (h *customCLISNIUnsafe) Execute(filePath string) error { } return expectResultsCount(results, 1) } + +type annotationTimeout struct{} + +// Execute executes a test case and returns an error if occurred +func (h *annotationTimeout) Execute(filePath string) error { + router := httprouter.New() + router.GET("/", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + time.Sleep(4 * time.Second) + fmt.Fprintf(w, "This is test matcher text") + }) + ts := httptest.NewTLSServer(router) + defer ts.Close() + + results, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, "-timeout", "1") + if err != nil { + return err + } + return expectResultsCount(results, 1) +} diff --git a/v2/pkg/protocols/http/build_request.go b/v2/pkg/protocols/http/build_request.go index 84fb06f27..fc8968be2 100644 --- a/v2/pkg/protocols/http/build_request.go +++ b/v2/pkg/protocols/http/build_request.go @@ -26,6 +26,7 @@ import ( "github.com/projectdiscovery/nuclei/v2/pkg/types" "github.com/projectdiscovery/rawhttp" "github.com/projectdiscovery/retryablehttp-go" + "github.com/projectdiscovery/stringsutil" ) var ( @@ -112,10 +113,15 @@ func (r *requestGenerator) makeSelfContainedRequest(ctx context.Context, data st if isRawRequest { // Get the hostname from the URL section to build the request. reader := bufio.NewReader(strings.NewReader(data)) + read_line: s, err := reader.ReadString('\n') if err != nil { return nil, fmt.Errorf("could not read request: %w", err) } + // ignore all annotations + if stringsutil.HasPrefixAny(s, "@") { + goto read_line + } parts := strings.Split(s, " ") if len(parts) < 3 { @@ -288,8 +294,7 @@ func (r *requestGenerator) handleRawWithPayloads(ctx context.Context, rawRequest } if reqWithAnnotations, hasAnnotations := r.request.parseAnnotations(rawRequest, req); hasAnnotations { - req = reqWithAnnotations - request = request.WithContext(req.Context()) + request.Request = reqWithAnnotations } return &generatedRequest{request: request, meta: generatorValues, original: r.request, dynamicValues: finalValues, interactshURLs: r.interactshURLs}, nil diff --git a/v2/pkg/protocols/http/request_annotations.go b/v2/pkg/protocols/http/request_annotations.go index b14831cb7..0b15c7dac 100644 --- a/v2/pkg/protocols/http/request_annotations.go +++ b/v2/pkg/protocols/http/request_annotations.go @@ -102,12 +102,12 @@ func (r *Request) parseAnnotations(rawRequest string, request *http.Request) (*h value := strings.TrimSpace(duration[1]) if parsed, err := time.ParseDuration(value); err == nil { //nolint:govet // cancelled automatically by withTimeout - ctx, _ := context.WithTimeout(request.Context(), parsed) + ctx, _ := context.WithTimeout(context.Background(), parsed) request = request.Clone(ctx) } } else { //nolint:govet // cancelled automatically by withTimeout - ctx, _ := context.WithTimeout(request.Context(), time.Duration(r.options.Options.Timeout)*time.Second) + ctx, _ := context.WithTimeout(context.Background(), time.Duration(r.options.Options.Timeout)*time.Second) request = request.Clone(ctx) } } diff --git a/v2/pkg/protocols/http/request_annotations_test.go b/v2/pkg/protocols/http/request_annotations_test.go new file mode 100644 index 000000000..682a923fa --- /dev/null +++ b/v2/pkg/protocols/http/request_annotations_test.go @@ -0,0 +1,45 @@ +package http + +import ( + "context" + "net/http" + "testing" + + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/httpclientpool" + "github.com/stretchr/testify/require" +) + +func TestRequestParseAnnotationsTimeout(t *testing.T) { + t.Run("positive", func(t *testing.T) { + request := &Request{ + connConfiguration: &httpclientpool.Configuration{NoTimeout: true}, + } + rawRequest := `@timeout: 2s + GET / HTTP/1.1 + Host: {{Hostname}}` + + httpReq, err := http.NewRequest(http.MethodGet, "https://example.com", nil) + require.Nil(t, err, "could not create http request") + + newRequest, modified := request.parseAnnotations(rawRequest, httpReq) + require.True(t, modified, "could not get correct modified value") + _, deadlined := newRequest.Context().Deadline() + require.True(t, deadlined, "could not get set request deadline") + }) + + t.Run("negative", func(t *testing.T) { + request := &Request{ + connConfiguration: &httpclientpool.Configuration{}, + } + rawRequest := `GET / HTTP/1.1 + Host: {{Hostname}}` + + httpReq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "https://example.com", nil) + require.Nil(t, err, "could not create http request") + + newRequest, modified := request.parseAnnotations(rawRequest, httpReq) + require.False(t, modified, "could not get correct modified value") + _, deadlined := newRequest.Context().Deadline() + require.False(t, deadlined, "could not get set request deadline") + }) +} From c3e9e1fe4ae4edb9cb04e49b240d2ea80a989115 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Aug 2022 12:50:27 +0530 Subject: [PATCH 09/25] chore(deps): bump github.com/xanzy/go-gitlab in /v2 (#2478) Bumps [github.com/xanzy/go-gitlab](https://github.com/xanzy/go-gitlab) from 0.72.0 to 0.73.0. - [Release notes](https://github.com/xanzy/go-gitlab/releases) - [Changelog](https://github.com/xanzy/go-gitlab/blob/master/releases_test.go) - [Commits](https://github.com/xanzy/go-gitlab/compare/v0.72.0...v0.73.0) --- updated-dependencies: - dependency-name: github.com/xanzy/go-gitlab dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- v2/go.mod | 2 +- v2/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/v2/go.mod b/v2/go.mod index b7de3cb2f..e73fc013f 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -48,7 +48,7 @@ require ( github.com/tj/go-update v2.2.5-0.20200519121640-62b4b798fd68+incompatible github.com/valyala/fasttemplate v1.2.1 github.com/weppos/publicsuffix-go v0.15.1-0.20210928183822-5ee35905bd95 - github.com/xanzy/go-gitlab v0.72.0 + github.com/xanzy/go-gitlab v0.73.0 go.uber.org/atomic v1.9.0 go.uber.org/multierr v1.8.0 go.uber.org/ratelimit v0.2.0 diff --git a/v2/go.sum b/v2/go.sum index f9062f0fc..29b4810ee 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -688,8 +688,8 @@ github.com/weppos/publicsuffix-go v0.15.1-0.20210928183822-5ee35905bd95/go.mod h 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/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE= -github.com/xanzy/go-gitlab v0.72.0 h1:/9BQTftUE7GRK/RO1eeWxG1cOE+tjwBrvRdpkeSOq6w= -github.com/xanzy/go-gitlab v0.72.0/go.mod h1:d/a0vswScO7Agg1CZNz15Ic6SSvBG9vfw8egL99t4kA= +github.com/xanzy/go-gitlab v0.73.0 h1:cfqWPU8cGdmH/O+ZzUpkLM5Rb95OmCBNaR1wRG55aR8= +github.com/xanzy/go-gitlab v0.73.0/go.mod h1:d/a0vswScO7Agg1CZNz15Ic6SSvBG9vfw8egL99t4kA= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= From f5f2ee145b07c9379588e167b72bd7a37dc2ae6a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Aug 2022 12:50:41 +0530 Subject: [PATCH 10/25] chore(deps): bump github.com/aws/aws-sdk-go in /v2 (#2479) Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.81 to 1.44.82. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.81...v1.44.82) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- v2/go.mod | 2 +- v2/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/v2/go.mod b/v2/go.mod index e73fc013f..893db9162 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -59,7 +59,7 @@ require ( moul.io/http2curl v1.0.0 ) -require github.com/aws/aws-sdk-go v1.44.81 +require github.com/aws/aws-sdk-go v1.44.82 require github.com/projectdiscovery/folderutil v0.0.0-20220215113126-add60a1e8e08 diff --git a/v2/go.sum b/v2/go.sum index 29b4810ee..27bce4290 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -114,8 +114,8 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.44.81 h1:C8oBZ+a+ka0qk3Q24MohQIFq0tkbO8IAu5tfpAMKVWE= -github.com/aws/aws-sdk-go v1.44.81/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.82 h1:Miji7nHIMxTWfa831nZf8XAcMWGLaT+PvsS6CdbMG7M= +github.com/aws/aws-sdk-go v1.44.82/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= 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= From 8f313629b803a37c11b8aef37a6b161da9585249 Mon Sep 17 00:00:00 2001 From: Ice3man Date: Tue, 23 Aug 2022 13:16:41 +0530 Subject: [PATCH 11/25] Memory usage optimizations (#2350) * Replaced strings.Replaced with fasttemplate reducing allocations Custom template parsing logic was replaced with fasttemplate package for reducing allocations in the replacer.Replace hotpath leading to allocation reduction which accounted for 30% of total nuclei allocations. $ go test -bench=. -benchmem goos: darwin goarch: arm64 pkg: github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/replacer BenchmarkReplacer-8 837232 1422 ns/op 2112 B/op 31 allocs/op BenchmarkReplacerNew-8 3672765 320.3 ns/op 48 B/op 4 allocs/op * Fixed tests failing * Use pre-compiled map of DSL expressions * Reworked expression parsing logic to reduce memory allocations $ go test -bench=. -benchmem goos: darwin goarch: arm64 pkg: github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/expressions BenchmarkEvaluate-8 31560 37769 ns/op 31731 B/op 265 allocs/op BenchmarkEvaluateNew-8 109144 9621 ns/op 6253 B/op 116 allocs/op --- v2/pkg/operators/common/dsl/dsl.go | 16 +++++- v2/pkg/operators/common/dsl/dsl_test.go | 16 +++--- v2/pkg/operators/extractors/compile.go | 2 +- v2/pkg/operators/matchers/compile.go | 2 +- v2/pkg/operators/matchers/match.go | 2 +- v2/pkg/operators/matchers/match_test.go | 2 +- .../common/expressions/expressions.go | 52 +++++-------------- .../protocols/common/expressions/variables.go | 12 +++++ v2/pkg/protocols/common/replacer/replacer.go | 26 +++------- .../common/replacer/replacer_test.go | 12 +++++ 10 files changed, 71 insertions(+), 71 deletions(-) create mode 100644 v2/pkg/protocols/common/replacer/replacer_test.go diff --git a/v2/pkg/operators/common/dsl/dsl.go b/v2/pkg/operators/common/dsl/dsl.go index 1df0763fa..5c5d29a67 100644 --- a/v2/pkg/operators/common/dsl/dsl.go +++ b/v2/pkg/operators/common/dsl/dsl.go @@ -50,6 +50,13 @@ var invalidDslFunctionMessageTemplate = "%w. correct method signature %q" var dslFunctions map[string]dslFunction +var ( + // FunctionNames is a list of function names for expression evaluation usages + FunctionNames []string + // HelperFunctions is a pre-compiled list of govaluate DSL functions + HelperFunctions map[string]govaluate.ExpressionFunction +) + var functionSignaturePattern = regexp.MustCompile(`(\w+)\s*\((?:([\w\d,\s]+)\s+([.\w\d{}&*]+))?\)([\s.\w\d{}&*]+)?`) var dateFormatRegex = regexp.MustCompile("%([A-Za-z])") @@ -592,6 +599,11 @@ func init() { for funcName, dslFunc := range tempDslFunctions { dslFunctions[funcName] = dslFunc(funcName) } + HelperFunctions = helperFunctions() + FunctionNames = make([]string, 0, len(HelperFunctions)) + for k := range HelperFunctions { + FunctionNames = append(FunctionNames, k) + } } func makeDslWithOptionalArgsFunction(signaturePart string, dslFunctionLogic govaluate.ExpressionFunction) func(functionName string) dslFunction { @@ -626,8 +638,8 @@ func createSignaturePart(numberOfParameters int) string { return fmt.Sprintf("(%s interface{}) interface{}", strings.Join(params, ", ")) } -// HelperFunctions returns the dsl helper functions -func HelperFunctions() map[string]govaluate.ExpressionFunction { +// helperFunctions returns the dsl helper functions +func helperFunctions() map[string]govaluate.ExpressionFunction { helperFunctions := make(map[string]govaluate.ExpressionFunction, len(dslFunctions)) for functionName, dslFunction := range dslFunctions { diff --git a/v2/pkg/operators/common/dsl/dsl_test.go b/v2/pkg/operators/common/dsl/dsl_test.go index c5f7a3f4a..f9cd3250c 100644 --- a/v2/pkg/operators/common/dsl/dsl_test.go +++ b/v2/pkg/operators/common/dsl/dsl_test.go @@ -15,7 +15,7 @@ import ( ) func TestDSLURLEncodeDecode(t *testing.T) { - functions := HelperFunctions() + functions := HelperFunctions encoded, err := functions["url_encode"]("&test\"") require.Nil(t, err, "could not url encode") @@ -27,7 +27,7 @@ func TestDSLURLEncodeDecode(t *testing.T) { } func TestDSLTimeComparison(t *testing.T) { - compiled, err := govaluate.NewEvaluableExpressionWithFunctions("unixtime() > not_after", HelperFunctions()) + 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)}) @@ -36,13 +36,13 @@ func TestDSLTimeComparison(t *testing.T) { } func TestDSLGzipSerialize(t *testing.T) { - compiled, err := govaluate.NewEvaluableExpressionWithFunctions("gzip(\"hello world\")", HelperFunctions()) + compiled, err := govaluate.NewEvaluableExpressionWithFunctions("gzip(\"hello world\")", HelperFunctions) require.Nil(t, err, "could not compile encoder") result, err := compiled.Evaluate(make(map[string]interface{})) require.Nil(t, err, "could not evaluate compare time") - compiled, err = govaluate.NewEvaluableExpressionWithFunctions("gzip_decode(data)", HelperFunctions()) + compiled, err = govaluate.NewEvaluableExpressionWithFunctions("gzip_decode(data)", HelperFunctions) require.Nil(t, err, "could not compile decoder") data, err := compiled.Evaluate(map[string]interface{}{"data": result}) @@ -68,7 +68,7 @@ func TestDateTimeDSLFunction(t *testing.T) { } t.Run("with Unix time", func(t *testing.T) { - dateTimeFunction, err := govaluate.NewEvaluableExpressionWithFunctions("date_time(dateTimeFormat)", HelperFunctions()) + dateTimeFunction, err := govaluate.NewEvaluableExpressionWithFunctions("date_time(dateTimeFormat)", HelperFunctions) require.Nil(t, err, "could not compile encoder") currentTime := time.Now() @@ -78,7 +78,7 @@ func TestDateTimeDSLFunction(t *testing.T) { }) t.Run("without Unix time", func(t *testing.T) { - dateTimeFunction, err := govaluate.NewEvaluableExpressionWithFunctions("date_time(dateTimeFormat, unixTime)", HelperFunctions()) + dateTimeFunction, err := govaluate.NewEvaluableExpressionWithFunctions("date_time(dateTimeFormat, unixTime)", HelperFunctions) require.Nil(t, err, "could not compile encoder") currentTime := time.Now() @@ -112,7 +112,7 @@ func TestDslFunctionSignatures(t *testing.T) { {"remove_bad_chars", []interface{}{"a", "b", "c"}, nil, removeBadCharsSignatureError}, } - helperFunctions := HelperFunctions() + helperFunctions := HelperFunctions for _, currentTestCase := range testCases { methodName := currentTestCase.methodName t.Run(methodName, func(t *testing.T) { @@ -343,7 +343,7 @@ func TestRandIntDslExpressions(t *testing.T) { } func evaluateExpression(t *testing.T, dslExpression string) interface{} { - compiledExpression, err := govaluate.NewEvaluableExpressionWithFunctions(dslExpression, HelperFunctions()) + compiledExpression, err := govaluate.NewEvaluableExpressionWithFunctions(dslExpression, HelperFunctions) require.NoError(t, err, "Error while compiling the %q expression", dslExpression) actualResult, err := compiledExpression.Evaluate(make(map[string]interface{})) diff --git a/v2/pkg/operators/extractors/compile.go b/v2/pkg/operators/extractors/compile.go index c1f3c64fe..ff6173bcd 100644 --- a/v2/pkg/operators/extractors/compile.go +++ b/v2/pkg/operators/extractors/compile.go @@ -43,7 +43,7 @@ func (e *Extractor) CompileExtractors() error { } for _, dslExp := range e.DSL { - compiled, err := govaluate.NewEvaluableExpressionWithFunctions(dslExp, dsl.HelperFunctions()) + compiled, err := govaluate.NewEvaluableExpressionWithFunctions(dslExp, dsl.HelperFunctions) if err != nil { return fmt.Errorf("could not compile dsl: %s", dslExp) } diff --git a/v2/pkg/operators/matchers/compile.go b/v2/pkg/operators/matchers/compile.go index 54bf77149..7bdb8225e 100644 --- a/v2/pkg/operators/matchers/compile.go +++ b/v2/pkg/operators/matchers/compile.go @@ -62,7 +62,7 @@ func (matcher *Matcher) CompileMatchers() error { // Compile the dsl expressions for _, dslExpression := range matcher.DSL { - compiledExpression, err := govaluate.NewEvaluableExpressionWithFunctions(dslExpression, dsl.HelperFunctions()) + compiledExpression, err := govaluate.NewEvaluableExpressionWithFunctions(dslExpression, dsl.HelperFunctions) if err != nil { return &DslCompilationError{DslSignature: dslExpression, WrappedError: err} } diff --git a/v2/pkg/operators/matchers/match.go b/v2/pkg/operators/matchers/match.go index 1a5b3dca6..abd8bf336 100644 --- a/v2/pkg/operators/matchers/match.go +++ b/v2/pkg/operators/matchers/match.go @@ -173,7 +173,7 @@ func (matcher *Matcher) MatchDSL(data map[string]interface{}) bool { logExpressionEvaluationFailure(matcher.Name, err) return false } - expression, err = govaluate.NewEvaluableExpressionWithFunctions(resolvedExpression, dsl.HelperFunctions()) + expression, err = govaluate.NewEvaluableExpressionWithFunctions(resolvedExpression, dsl.HelperFunctions) if err != nil { logExpressionEvaluationFailure(matcher.Name, err) return false diff --git a/v2/pkg/operators/matchers/match_test.go b/v2/pkg/operators/matchers/match_test.go index 68a6d1b01..db50b11e9 100644 --- a/v2/pkg/operators/matchers/match_test.go +++ b/v2/pkg/operators/matchers/match_test.go @@ -75,7 +75,7 @@ func TestHexEncoding(t *testing.T) { } func TestMatcher_MatchDSL(t *testing.T) { - compiled, err := govaluate.NewEvaluableExpressionWithFunctions("contains(body, \"{{VARIABLE}}\")", dsl.HelperFunctions()) + compiled, err := govaluate.NewEvaluableExpressionWithFunctions("contains(body, \"{{VARIABLE}}\")", dsl.HelperFunctions) require.Nil(t, err, "couldn't compile expression") m := &Matcher{Type: MatcherTypeHolder{MatcherType: DSLMatcher}, dslCompiled: []*govaluate.EvaluableExpression{compiled}} diff --git a/v2/pkg/protocols/common/expressions/expressions.go b/v2/pkg/protocols/common/expressions/expressions.go index d73ef3d01..723c6256a 100644 --- a/v2/pkg/protocols/common/expressions/expressions.go +++ b/v2/pkg/protocols/common/expressions/expressions.go @@ -40,12 +40,12 @@ func evaluate(data string, base map[string]interface{}) (string, error) { // - simple: containing base values keys (variables) // - complex: containing helper functions [ + variables] // literals like {{2+2}} are not considered expressions - expressions := findExpressions(data, marker.ParenthesisOpen, marker.ParenthesisClose, mergeFunctions(dsl.HelperFunctions(), mapToFunctions(base))) + expressions := findExpressions(data, marker.ParenthesisOpen, marker.ParenthesisClose, base) for _, expression := range expressions { // replace variable placeholders with base values expression = replacer.Replace(expression, base) // turns expressions (either helper functions+base values or base values) - compiled, err := govaluate.NewEvaluableExpressionWithFunctions(expression, dsl.HelperFunctions()) + compiled, err := govaluate.NewEvaluableExpressionWithFunctions(expression, dsl.HelperFunctions) if err != nil { continue } @@ -63,7 +63,7 @@ func evaluate(data string, base map[string]interface{}) (string, error) { // maxIterations to avoid infinite loop const maxIterations = 250 -func findExpressions(data, OpenMarker, CloseMarker string, functions map[string]govaluate.ExpressionFunction) []string { +func findExpressions(data, OpenMarker, CloseMarker string, base map[string]interface{}) []string { var ( iterations int exps []string @@ -100,7 +100,7 @@ func findExpressions(data, OpenMarker, CloseMarker string, functions map[string] indexCloseMarkerOffset = indexCloseMarker + len(CloseMarker) potentialMatch = innerData[indexOpenMarkerOffset:indexCloseMarker] - if isExpression(potentialMatch, functions) { + if isExpression(potentialMatch, base) { closeMarkerFound = true shouldSearchCloseMarker = false exps = append(exps, potentialMatch) @@ -120,45 +120,21 @@ func findExpressions(data, OpenMarker, CloseMarker string, functions map[string] return exps } -func hasLiteralsOnly(data string) bool { - expr, err := govaluate.NewEvaluableExpressionWithFunctions(data, dsl.HelperFunctions()) - if err == nil && expr != nil { - _, err = expr.Evaluate(nil) - return err == nil - } - return true -} - -func isExpression(data string, functions map[string]govaluate.ExpressionFunction) bool { +func isExpression(data string, base map[string]interface{}) bool { if _, err := govaluate.NewEvaluableExpression(data); err == nil { - return stringsutil.ContainsAny(data, getFunctionsNames(functions)...) + if stringsutil.ContainsAny(data, getFunctionsNames(base)...) { + return true + } else if stringsutil.ContainsAny(data, dsl.FunctionNames...) { + return true + } + return false } - - // check if it's a complex expression - _, err := govaluate.NewEvaluableExpressionWithFunctions(data, dsl.HelperFunctions()) + _, err := govaluate.NewEvaluableExpressionWithFunctions(data, dsl.HelperFunctions) return err == nil } -func mapToFunctions(vars map[string]interface{}) map[string]govaluate.ExpressionFunction { - f := make(map[string]govaluate.ExpressionFunction) - for k := range vars { - f[k] = nil - } - return f -} - -func mergeFunctions(m ...map[string]govaluate.ExpressionFunction) map[string]govaluate.ExpressionFunction { - o := make(map[string]govaluate.ExpressionFunction) - for _, mm := range m { - for k, v := range mm { - o[k] = v - } - } - return o -} - -func getFunctionsNames(m map[string]govaluate.ExpressionFunction) []string { - var keys []string +func getFunctionsNames(m map[string]interface{}) []string { + keys := make([]string, 0, len(m)) for k := range m { keys = append(keys, k) } diff --git a/v2/pkg/protocols/common/expressions/variables.go b/v2/pkg/protocols/common/expressions/variables.go index 57dc8d7ad..94e504c93 100644 --- a/v2/pkg/protocols/common/expressions/variables.go +++ b/v2/pkg/protocols/common/expressions/variables.go @@ -4,6 +4,9 @@ import ( "errors" "regexp" "strings" + + "github.com/Knetic/govaluate" + "github.com/projectdiscovery/nuclei/v2/pkg/operators/common/dsl" ) var ( @@ -111,3 +114,12 @@ func ContainsVariablesWithIgnoreList(skipNames map[string]interface{}, items ... return nil } + +func hasLiteralsOnly(data string) bool { + expr, err := govaluate.NewEvaluableExpressionWithFunctions(data, dsl.HelperFunctions) + if err == nil && expr != nil { + _, err = expr.Evaluate(nil) + return err == nil + } + return true +} diff --git a/v2/pkg/protocols/common/replacer/replacer.go b/v2/pkg/protocols/common/replacer/replacer.go index 0e24c6828..dbb4d8fac 100644 --- a/v2/pkg/protocols/common/replacer/replacer.go +++ b/v2/pkg/protocols/common/replacer/replacer.go @@ -3,32 +3,20 @@ package replacer import ( "strings" + "github.com/valyala/fasttemplate" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/marker" "github.com/projectdiscovery/nuclei/v2/pkg/types" ) // Replace replaces placeholders in template with values on the fly. func Replace(template string, values map[string]interface{}) string { - var replacerItems []string - - builder := &strings.Builder{} - for key, val := range values { - builder.WriteString(marker.ParenthesisOpen) - builder.WriteString(key) - builder.WriteString(marker.ParenthesisClose) - replacerItems = append(replacerItems, builder.String()) - builder.Reset() - replacerItems = append(replacerItems, types.ToString(val)) - - builder.WriteString(marker.General) - builder.WriteString(key) - builder.WriteString(marker.General) - replacerItems = append(replacerItems, builder.String()) - builder.Reset() - replacerItems = append(replacerItems, types.ToString(val)) + valuesMap := make(map[string]interface{}, len(values)) + for k, v := range values { + valuesMap[k] = types.ToString(v) } - replacer := strings.NewReplacer(replacerItems...) - final := replacer.Replace(template) + replaced := fasttemplate.ExecuteStringStd(template, marker.ParenthesisOpen, marker.ParenthesisClose, valuesMap) + final := fasttemplate.ExecuteStringStd(replaced, marker.General, marker.General, valuesMap) return final } diff --git a/v2/pkg/protocols/common/replacer/replacer_test.go b/v2/pkg/protocols/common/replacer/replacer_test.go new file mode 100644 index 000000000..e03427e21 --- /dev/null +++ b/v2/pkg/protocols/common/replacer/replacer_test.go @@ -0,0 +1,12 @@ +package replacer + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestReplacerReplace(t *testing.T) { + replaced := Replace("{{test}} §hello§ {{data}}", map[string]interface{}{"test": "random", "hello": "world"}) + require.Equal(t, "random world {{data}}", replaced, "could not get correct replaced data") +} From 77c81834b2ab7a584c4dce0ac9189b18c770f31c Mon Sep 17 00:00:00 2001 From: Ice3man Date: Wed, 24 Aug 2022 19:32:56 +0530 Subject: [PATCH 12/25] Fixed loader templateConfig nil pointer crash (#2486) --- v2/internal/runner/runner.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/v2/internal/runner/runner.go b/v2/internal/runner/runner.go index eb9430228..39ab9ed7f 100644 --- a/v2/internal/runner/runner.go +++ b/v2/internal/runner/runner.go @@ -377,7 +377,11 @@ func (r *Runner) RunEnumeration() error { } executerOpts.WorkflowLoader = workflowLoader - store, err := loader.New(loader.NewConfig(r.options, r.templatesConfig, r.catalog, executerOpts)) + templateConfig := r.templatesConfig + if templateConfig == nil { + templateConfig = &config.Config{} + } + store, err := loader.New(loader.NewConfig(r.options, templateConfig, r.catalog, executerOpts)) if err != nil { return errors.Wrap(err, "could not load templates from config") } From 8165db2633f589fcba8b299431c93d125632704d Mon Sep 17 00:00:00 2001 From: Ice3man Date: Wed, 24 Aug 2022 23:29:22 +0530 Subject: [PATCH 13/25] Fixed fatal panic in http header map read (#2488) --- v2/pkg/protocols/http/utils.go | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/v2/pkg/protocols/http/utils.go b/v2/pkg/protocols/http/utils.go index 521db090d..1fd93bcbf 100644 --- a/v2/pkg/protocols/http/utils.go +++ b/v2/pkg/protocols/http/utils.go @@ -4,6 +4,7 @@ import ( "bytes" "compress/gzip" "compress/zlib" + "context" "io" "io/ioutil" "net/http" @@ -115,31 +116,25 @@ func normalizeResponseBody(resp *http.Response, response *redirectedResponse) er // dump creates a dump of the http request in form of a byte slice func dump(req *generatedRequest, reqURL string) ([]byte, error) { if req.request != nil { + cloned := req.request.Clone(context.Background()) + // Create a copy on the fly of the request body - ignore errors bodyBytes, _ := req.request.BodyBytes() var dumpBody bool if len(bodyBytes) > 0 { dumpBody = true - req.request.Request.ContentLength = int64(len(bodyBytes)) - req.request.Request.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes)) + cloned.ContentLength = int64(len(bodyBytes)) + cloned.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes)) } else { - req.request.Request.ContentLength = 0 - req.request.Request.Body = nil - delete(req.request.Request.Header, "Content-length") + cloned.ContentLength = 0 + cloned.Body = nil + delete(cloned.Header, "Content-length") } - dumpBytes, err := httputil.DumpRequestOut(req.request.Request, dumpBody) + dumpBytes, err := httputil.DumpRequestOut(cloned, dumpBody) if err != nil { return nil, err } - - // The original req.Body gets modified indirectly by httputil.DumpRequestOut so we set it again to nil if it was empty - // Otherwise redirects like 307/308 would fail (as they require the body to be sent along) - if len(bodyBytes) == 0 { - req.request.Request.ContentLength = 0 - req.request.Request.Body = nil - } - return dumpBytes, nil } rawHttpOptions := &rawhttp.Options{CustomHeaders: req.rawRequest.UnsafeHeaders, CustomRawBytes: req.rawRequest.UnsafeRawBytes} From d2233eff50cf1c642826ffc0a0091aa36274552a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Aug 2022 23:44:03 +0530 Subject: [PATCH 14/25] chore(deps): bump github.com/xanzy/go-gitlab in /v2 (#2484) Bumps [github.com/xanzy/go-gitlab](https://github.com/xanzy/go-gitlab) from 0.73.0 to 0.73.1. - [Release notes](https://github.com/xanzy/go-gitlab/releases) - [Changelog](https://github.com/xanzy/go-gitlab/blob/master/releases_test.go) - [Commits](https://github.com/xanzy/go-gitlab/compare/v0.73.0...v0.73.1) --- updated-dependencies: - dependency-name: github.com/xanzy/go-gitlab dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- v2/go.mod | 2 +- v2/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/v2/go.mod b/v2/go.mod index 893db9162..19fe71762 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -48,7 +48,7 @@ require ( github.com/tj/go-update v2.2.5-0.20200519121640-62b4b798fd68+incompatible github.com/valyala/fasttemplate v1.2.1 github.com/weppos/publicsuffix-go v0.15.1-0.20210928183822-5ee35905bd95 - github.com/xanzy/go-gitlab v0.73.0 + github.com/xanzy/go-gitlab v0.73.1 go.uber.org/atomic v1.9.0 go.uber.org/multierr v1.8.0 go.uber.org/ratelimit v0.2.0 diff --git a/v2/go.sum b/v2/go.sum index 27bce4290..302bd164b 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -688,8 +688,8 @@ github.com/weppos/publicsuffix-go v0.15.1-0.20210928183822-5ee35905bd95/go.mod h 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/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE= -github.com/xanzy/go-gitlab v0.73.0 h1:cfqWPU8cGdmH/O+ZzUpkLM5Rb95OmCBNaR1wRG55aR8= -github.com/xanzy/go-gitlab v0.73.0/go.mod h1:d/a0vswScO7Agg1CZNz15Ic6SSvBG9vfw8egL99t4kA= +github.com/xanzy/go-gitlab v0.73.1 h1:UMagqUZLJdjss1SovIC+kJCH4k2AZWXl58gJd38Y/hI= +github.com/xanzy/go-gitlab v0.73.1/go.mod h1:d/a0vswScO7Agg1CZNz15Ic6SSvBG9vfw8egL99t4kA= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= From cdb9e617e60a13954f2f3c0f0f46101cd7264fe6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Aug 2022 23:44:30 +0530 Subject: [PATCH 15/25] chore(deps): bump github.com/aws/aws-sdk-go in /v2 (#2483) Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.44.82 to 1.44.83. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.44.82...v1.44.83) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- v2/go.mod | 2 +- v2/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/v2/go.mod b/v2/go.mod index 19fe71762..ac939653a 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -59,7 +59,7 @@ require ( moul.io/http2curl v1.0.0 ) -require github.com/aws/aws-sdk-go v1.44.82 +require github.com/aws/aws-sdk-go v1.44.83 require github.com/projectdiscovery/folderutil v0.0.0-20220215113126-add60a1e8e08 diff --git a/v2/go.sum b/v2/go.sum index 302bd164b..073df0786 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -114,8 +114,8 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.44.82 h1:Miji7nHIMxTWfa831nZf8XAcMWGLaT+PvsS6CdbMG7M= -github.com/aws/aws-sdk-go v1.44.82/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.44.83 h1:7+Rtc2Eio6EKUNoZeMV/IVxzVrY5oBQcNPtCcgIHYJA= +github.com/aws/aws-sdk-go v1.44.83/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= 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= From ecb3f21076e77ed79e997ba1ca653c9ae6e0c27f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=2E=20=C3=81ngel=20Jimeno?= Date: Wed, 24 Aug 2022 20:55:02 +0200 Subject: [PATCH 16/25] http: prevent HTTP 'connection' header from being added twice (#2480) * http: prevent HTTP 'connection' header from being added twice * misc fix Co-authored-by: sandeep --- v2/cmd/integration-test/http.go | 2 +- v2/pkg/protocols/http/http.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/v2/cmd/integration-test/http.go b/v2/cmd/integration-test/http.go index 0fdf9f566..6f2b6e1fc 100644 --- a/v2/cmd/integration-test/http.go +++ b/v2/cmd/integration-test/http.go @@ -586,7 +586,7 @@ func (h *httpRawUnsafeRequest) Execute(filePath string) error { ts := testutils.NewTCPServer(nil, defaultStaticPort, func(conn net.Conn) { defer conn.Close() - _, _ = conn.Write([]byte("HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 36\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nThis is test raw-unsafe-matcher test")) + _, _ = conn.Write([]byte("HTTP/1.1 200 OK\r\nContent-Length: 36\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nThis is test raw-unsafe-matcher test")) }) defer ts.Close() diff --git a/v2/pkg/protocols/http/http.go b/v2/pkg/protocols/http/http.go index 83ed88ec7..8484a0b33 100644 --- a/v2/pkg/protocols/http/http.go +++ b/v2/pkg/protocols/http/http.go @@ -237,6 +237,7 @@ func (request *Request) Compile(options *protocols.ExecuterOptions) error { NoTimeout: false, FollowRedirects: request.Redirects, CookieReuse: request.CookieReuse, + Connection: &httpclientpool.ConnectionConfiguration{}, } // If we have request level timeout, ignore http client timeouts for _, req := range request.Raw { @@ -248,7 +249,7 @@ func (request *Request) Compile(options *protocols.ExecuterOptions) error { // if the headers contain "Connection" we need to disable the automatic keep alive of the standard library if _, hasConnectionHeader := request.Headers["Connection"]; hasConnectionHeader { - connectionConfiguration.Connection = &httpclientpool.ConnectionConfiguration{DisableKeepAlive: false} + connectionConfiguration.Connection.DisableKeepAlive = true } client, err := httpclientpool.Get(options.Options, connectionConfiguration) From 7b7936b7a54e133cbd612200d39e841068cd43c0 Mon Sep 17 00:00:00 2001 From: Ice3man Date: Thu, 25 Aug 2022 10:43:32 +0530 Subject: [PATCH 17/25] Added show-actions flag to display headless actions (#2456) * Added show-actions flag to display headless actions * misc update * readme update Co-authored-by: sandeep --- README.md | 1 + v2/cmd/nuclei/main.go | 1 + v2/internal/runner/options.go | 8 ++++++++ v2/pkg/types/types.go | 2 ++ 4 files changed, 12 insertions(+) diff --git a/README.md b/README.md index c8ba00c25..d17009052 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,7 @@ HEADLESS: -page-timeout int seconds to wait for each page in headless mode (default 20) -sb, -show-browser show the browser on the screen when running templates with headless mode -sc, -system-chrome Use local installed chrome browser instead of nuclei installed + -lha, -list-headless-action list available headless actions DEBUG: -debug show all requests and responses diff --git a/v2/cmd/nuclei/main.go b/v2/cmd/nuclei/main.go index 27ad898ef..6251d7fda 100644 --- a/v2/cmd/nuclei/main.go +++ b/v2/cmd/nuclei/main.go @@ -220,6 +220,7 @@ on extensive configurability, massive extensibility and ease of use.`) flagSet.IntVar(&options.PageTimeout, "page-timeout", 20, "seconds to wait for each page in headless mode"), flagSet.BoolVarP(&options.ShowBrowser, "show-browser", "sb", false, "show the browser on the screen when running templates with headless mode"), flagSet.BoolVarP(&options.UseInstalledChrome, "system-chrome", "sc", false, "Use local installed chrome browser instead of nuclei installed"), + flagSet.BoolVarP(&options.ShowActions, "list-headless-action", "lha", false, "list available headless actions"), ) flagSet.CreateGroup("debug", "Debug", diff --git a/v2/internal/runner/options.go b/v2/internal/runner/options.go index 3d75d32fb..abe7d9d55 100644 --- a/v2/internal/runner/options.go +++ b/v2/internal/runner/options.go @@ -19,6 +19,7 @@ import ( "github.com/projectdiscovery/gologger/levels" "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolinit" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/headless/engine" "github.com/projectdiscovery/nuclei/v2/pkg/types" ) @@ -57,6 +58,13 @@ func ParseOptions(options *types.Options) { gologger.Info().Msgf("Current nuclei-templates version: %s (%s)\n", configuration.TemplateVersion, configuration.TemplatesDirectory) os.Exit(0) } + if options.ShowActions { + gologger.Info().Msgf("Showing available headless actions: ") + for action := range engine.ActionStringToAction { + gologger.Print().Msgf("\t%s", action) + } + os.Exit(0) + } if options.StoreResponseDir != DefaultDumpTrafficOutputFolder && !options.StoreResponse { gologger.Debug().Msgf("Store response directory specified, enabling \"store-resp\" flag automatically\n") options.StoreResponse = true diff --git a/v2/pkg/types/types.go b/v2/pkg/types/types.go index 0541a684a..0800c16c5 100644 --- a/v2/pkg/types/types.go +++ b/v2/pkg/types/types.go @@ -143,6 +143,8 @@ type Options struct { UseInstalledChrome bool // SystemResolvers enables override of nuclei's DNS client opting to use system resolver stack. SystemResolvers bool + // ShowActions displays a list of all headless actions + ShowActions bool // Metrics enables display of metrics via an http endpoint Metrics bool // Debug mode allows debugging request/responses for the engine From 72656025d88d88404ab274a246f7d4ff58c016ca Mon Sep 17 00:00:00 2001 From: Min <44919834+M1nSec@users.noreply.github.com> Date: Thu, 25 Aug 2022 17:44:08 +0800 Subject: [PATCH 18/25] Wrong parameters modified (#2491) Co-authored-by: Sandeep Singh --- README_CN.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README_CN.md b/README_CN.md index a7f188a0e..ccee0391a 100644 --- a/README_CN.md +++ b/README_CN.md @@ -14,7 +14,7 @@

- +

工作流程安装 • @@ -143,7 +143,7 @@ Nuclei是一款注重于可配置性、可扩展性和易用性的基于模板 -ni, -no-interactsh 禁用反连检测平台,同时排除基于反连检测的模板 限速: - -r1, -rate-limit int 每秒最大请求量(默认:150) + -rl, -rate-limit int 每秒最大请求量(默认:150) -rlm, -rate-limit-minute int 每分钟最大请求量 -bs, -bulk-size int 每个模板最大并行检测数(默认:25) -c, -concurrency int 并行执行的最大模板数量(默认:25) From 0be596efb47bd9d36445e5a4307fbd55d6c1b216 Mon Sep 17 00:00:00 2001 From: Ice3man Date: Thu, 25 Aug 2022 15:37:03 +0530 Subject: [PATCH 19/25] Added variable debug support with debug mode (#2442) * Added variable debug support with debug mode * Added changes as per review comments * Fixed debug request condition --- .../helpers/eventcreator/eventcreator.go | 7 +++ v2/pkg/protocols/common/utils/vardump/dump.go | 45 +++++++++++++++++++ v2/pkg/protocols/dns/request.go | 5 +++ v2/pkg/protocols/headless/request.go | 5 +++ v2/pkg/protocols/http/build_request.go | 5 +++ v2/pkg/protocols/network/request.go | 21 +++++---- v2/pkg/protocols/ssl/ssl.go | 5 +++ v2/pkg/protocols/websocket/websocket.go | 5 +++ v2/pkg/protocols/whois/whois.go | 6 +++ 9 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 v2/pkg/protocols/common/utils/vardump/dump.go diff --git a/v2/pkg/protocols/common/helpers/eventcreator/eventcreator.go b/v2/pkg/protocols/common/helpers/eventcreator/eventcreator.go index 4e4e7d169..04d0e631c 100644 --- a/v2/pkg/protocols/common/helpers/eventcreator/eventcreator.go +++ b/v2/pkg/protocols/common/helpers/eventcreator/eventcreator.go @@ -1,9 +1,11 @@ package eventcreator import ( + "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v2/pkg/operators" "github.com/projectdiscovery/nuclei/v2/pkg/output" "github.com/projectdiscovery/nuclei/v2/pkg/protocols" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/utils/vardump" ) // CreateEvent wraps the outputEvent with the result of the operators defined on the request @@ -16,6 +18,11 @@ func CreateEvent(request protocols.Request, outputEvent output.InternalEvent, is func CreateEventWithAdditionalOptions(request protocols.Request, outputEvent output.InternalEvent, isResponseDebug bool, addAdditionalOptions func(internalWrappedEvent *output.InternalWrappedEvent)) *output.InternalWrappedEvent { event := &output.InternalWrappedEvent{InternalEvent: outputEvent} + + // Dump response variables if ran in debug mode + if isResponseDebug { + gologger.Debug().Msgf("Protocol response variables: \n%s\n", vardump.DumpVariables(outputEvent)) + } for _, compiledOperator := range request.GetCompiledOperators() { if compiledOperator != nil { result, ok := compiledOperator.Execute(outputEvent, request.Match, request.Extract, isResponseDebug) diff --git a/v2/pkg/protocols/common/utils/vardump/dump.go b/v2/pkg/protocols/common/utils/vardump/dump.go new file mode 100644 index 000000000..0d6450924 --- /dev/null +++ b/v2/pkg/protocols/common/utils/vardump/dump.go @@ -0,0 +1,45 @@ +package vardump + +import ( + "strconv" + "strings" + + "github.com/projectdiscovery/nuclei/v2/pkg/types" +) + +// DumpVariables writes the truncated dump of variables to a string +// in a formatted key-value manner. +// +// The values are truncated to return 50 characters from start and end. +func DumpVariables(data map[string]interface{}) string { + var counter int + + buffer := &strings.Builder{} + buffer.Grow(len(data) * 78) // grow buffer to an approximate size + + builder := &strings.Builder{} + for k, v := range data { + valueString := types.ToString(v) + + counter++ + if len(valueString) > 50 { + builder.Grow(56) + builder.WriteString(valueString[0:25]) + builder.WriteString(" .... ") + builder.WriteString(valueString[len(valueString)-25:]) + valueString = builder.String() + builder.Reset() + } + valueString = strings.ReplaceAll(strings.ReplaceAll(valueString, "\r", " "), "\n", " ") + + buffer.WriteString("\t") + buffer.WriteString(strconv.Itoa(counter)) + buffer.WriteString(". ") + buffer.WriteString(k) + buffer.WriteString(" => ") + buffer.WriteString(valueString) + buffer.WriteString("\n") + } + final := buffer.String() + return final +} diff --git a/v2/pkg/protocols/dns/request.go b/v2/pkg/protocols/dns/request.go index 9f24b01f2..00293089f 100644 --- a/v2/pkg/protocols/dns/request.go +++ b/v2/pkg/protocols/dns/request.go @@ -16,6 +16,7 @@ import ( "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/common/utils/vardump" templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" "github.com/projectdiscovery/nuclei/v2/pkg/utils" "github.com/projectdiscovery/retryabledns" @@ -47,6 +48,10 @@ func (request *Request) ExecuteWithResults(input string, metadata /*TODO review variablesMap := request.options.Variables.Evaluate(vars) vars = generators.MergeMaps(variablesMap, vars) + if request.options.Options.Debug || request.options.Options.DebugRequests { + gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(vars)) + } + // Compile each request for the template based on the URL compiledRequest, err := request.Make(domain, vars) if err != nil { diff --git a/v2/pkg/protocols/headless/request.go b/v2/pkg/protocols/headless/request.go index 4dbe1b54f..cdf4b1696 100644 --- a/v2/pkg/protocols/headless/request.go +++ b/v2/pkg/protocols/headless/request.go @@ -14,6 +14,7 @@ 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/interactsh" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/utils/vardump" httpProtocol "github.com/projectdiscovery/nuclei/v2/pkg/protocols/http" templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" ) @@ -69,6 +70,10 @@ func (request *Request) executeRequestWithPayloads(inputURL string, payloads map } defer instance.Close() + if request.options.Options.Debug || request.options.Options.DebugRequests { + gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(payloads)) + } + instance.SetInteractsh(request.options.Interactsh) parsedURL, err := url.Parse(inputURL) diff --git a/v2/pkg/protocols/http/build_request.go b/v2/pkg/protocols/http/build_request.go index fc8968be2..64471f90c 100644 --- a/v2/pkg/protocols/http/build_request.go +++ b/v2/pkg/protocols/http/build_request.go @@ -17,9 +17,11 @@ import ( "github.com/corpix/uarand" "github.com/pkg/errors" + "github.com/projectdiscovery/gologger" "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/replacer" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/utils/vardump" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/dns" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/race" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/http/raw" @@ -96,6 +98,9 @@ func (r *requestGenerator) Make(ctx context.Context, baseURL, data string, paylo generators.MergeMaps(dynamicValues, GenerateVariables(parsed, trailingSlash)), generators.BuildPayloadFromOptions(r.request.options.Options), ) + if r.options.Options.Debug || r.options.Options.DebugRequests { + gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(values)) + } // If data contains \n it's a raw request, process it like raw. Else // continue with the template based request flow. diff --git a/v2/pkg/protocols/network/request.go b/v2/pkg/protocols/network/request.go index eb2179fd0..a3b39b966 100644 --- a/v2/pkg/protocols/network/request.go +++ b/v2/pkg/protocols/network/request.go @@ -23,6 +23,7 @@ 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" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/utils/vardump" templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" ) @@ -128,6 +129,10 @@ func (request *Request) executeRequestWithPayloads(variables map[string]interfac interimValues := generators.MergeMaps(variables, payloads) + if request.options.Options.Debug || request.options.Options.DebugRequests { + gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(interimValues)) + } + inputEvents := make(map[string]interface{}) for _, input := range request.Inputs { var data []byte @@ -190,14 +195,14 @@ func (request *Request) executeRequestWithPayloads(variables map[string]interfac } request.options.Progress.IncrementRequests() - if request.options.Options.Debug || request.options.Options.DebugRequests || request.options.Options.StoreResponse{ + if request.options.Options.Debug || request.options.Options.DebugRequests || request.options.Options.StoreResponse { requestBytes := []byte(reqBuilder.String()) msg := fmt.Sprintf("[%s] Dumped Network request for %s\n%s", request.options.TemplateID, actualAddress, hex.Dump(requestBytes)) if request.options.Options.Debug || request.options.Options.DebugRequests { - gologger.Info().Str("address", actualAddress).Msg(msg) + gologger.Info().Str("address", actualAddress).Msg(msg) } - if request.options.Options.StoreResponse{ - request.options.Output.WriteStoreDebugData(address, request.options.TemplateID, request.Type().String(), msg) + if request.options.Options.StoreResponse { + request.options.Output.WriteStoreDebugData(address, request.options.TemplateID, request.Type().String(), msg) } if request.options.Options.VerboseVerbose { gologger.Print().Msgf("\nCompact HEX view:\n%s", hex.EncodeToString(requestBytes)) @@ -300,15 +305,15 @@ func (request *Request) executeRequestWithPayloads(variables map[string]interfac func dumpResponse(event *output.InternalWrappedEvent, request *Request, response string, actualAddress, address string) { cliOptions := request.options.Options - if cliOptions.Debug || cliOptions.DebugResponse || cliOptions.StoreResponse{ + if cliOptions.Debug || cliOptions.DebugResponse || cliOptions.StoreResponse { requestBytes := []byte(response) highlightedResponse := responsehighlighter.Highlight(event.OperatorsResult, hex.Dump(requestBytes), cliOptions.NoColor, true) msg := fmt.Sprintf("[%s] Dumped Network response for %s\n\n", request.options.TemplateID, actualAddress) if cliOptions.Debug || cliOptions.DebugResponse { - gologger.Debug().Msg(fmt.Sprintf("%s%s", msg, highlightedResponse)) + gologger.Debug().Msg(fmt.Sprintf("%s%s", msg, highlightedResponse)) } - if cliOptions.StoreResponse{ - request.options.Output.WriteStoreDebugData(address, request.options.TemplateID, request.Type().String(), fmt.Sprintf("%s%s", msg, hex.Dump(requestBytes))) + if cliOptions.StoreResponse { + request.options.Output.WriteStoreDebugData(address, request.options.TemplateID, request.Type().String(), fmt.Sprintf("%s%s", msg, hex.Dump(requestBytes))) } if cliOptions.VerboseVerbose { displayCompactHexView(event, response, cliOptions.NoColor) diff --git a/v2/pkg/protocols/ssl/ssl.go b/v2/pkg/protocols/ssl/ssl.go index 8a6e3f516..f5530e7c3 100644 --- a/v2/pkg/protocols/ssl/ssl.go +++ b/v2/pkg/protocols/ssl/ssl.go @@ -24,6 +24,7 @@ import ( "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/common/utils/vardump" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/dns" "github.com/projectdiscovery/nuclei/v2/pkg/protocols/network/networkclientpool" templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" @@ -130,6 +131,10 @@ func (request *Request) ExecuteWithResults(input string, dynamicValues, previous variablesMap := request.options.Variables.Evaluate(values) payloadValues = generators.MergeMaps(variablesMap, payloadValues) + if request.options.Options.Debug || request.options.Options.DebugRequests { + gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(payloadValues)) + } + finalAddress, dataErr := expressions.EvaluateByte([]byte(request.Address), payloadValues) if dataErr != nil { requestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), dataErr) diff --git a/v2/pkg/protocols/websocket/websocket.go b/v2/pkg/protocols/websocket/websocket.go index 262e53022..d95cef39b 100644 --- a/v2/pkg/protocols/websocket/websocket.go +++ b/v2/pkg/protocols/websocket/websocket.go @@ -27,6 +27,7 @@ import ( "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/common/utils/vardump" "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" @@ -207,6 +208,10 @@ func (request *Request) executeRequestWithPayloads(input, hostname string, dynam TLSConfig: tlsConfig, } + if request.options.Options.Debug || request.options.Options.DebugRequests { + gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(payloadValues)) + } + finalAddress, dataErr := expressions.EvaluateByte([]byte(request.Address), payloadValues) if dataErr != nil { requestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), dataErr) diff --git a/v2/pkg/protocols/whois/whois.go b/v2/pkg/protocols/whois/whois.go index 83c4840f1..e74bf9c31 100644 --- a/v2/pkg/protocols/whois/whois.go +++ b/v2/pkg/protocols/whois/whois.go @@ -18,6 +18,7 @@ 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/replacer" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/utils/vardump" templateTypes "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" "github.com/projectdiscovery/nuclei/v2/pkg/types" ) @@ -88,6 +89,11 @@ func (request *Request) GetID() string { func (request *Request) ExecuteWithResults(input string, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error { // generate variables variables := generateVariables(input) + + if request.options.Options.Debug || request.options.Options.DebugRequests { + gologger.Debug().Msgf("Protocol request variables: \n%s\n", vardump.DumpVariables(variables)) + } + // and replace placeholders query := replacer.Replace(request.Query, variables) // build an rdap request From 606c361b2afdd37e183aadb0fc7a566db1355be0 Mon Sep 17 00:00:00 2001 From: 51pwn <18223385+hktalent@users.noreply.github.com> Date: Thu, 25 Aug 2022 18:20:08 +0800 Subject: [PATCH 20/25] Add `substr` and `aes_cbc` DSL functions (#2361) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 1、add DSL substr for #2304 By @hktalent substr('xxtestxxx',2)。 testxxx substr('xxtestxxx',2,-2) testx substr('xxtestxxx',2,6) test 2、add DSL aes_cbc for #2243 By @hktalent aes_cbc("key111key111key111key111", "dataxxxxxxdataxxxxxxdataxxxxxxdataxxxxxxdataxxxxxx") 3、fixed An error occurs when running nuclei with multiple instances #2301 By @hktalent * refactoring helpers * removing unwanted mutex * commenting out test * removing aes_cbc test due to random iv Co-authored-by: 51pwn <51pwn@51pwn.com> Co-authored-by: Mzack9999 --- v2/pkg/operators/common/dsl/dsl.go | 44 ++++++++ v2/pkg/operators/common/dsl/dsl_test.go | 140 ++++++++++++------------ v2/pkg/protocols/http/request.go | 1 - 3 files changed, 116 insertions(+), 69 deletions(-) diff --git a/v2/pkg/operators/common/dsl/dsl.go b/v2/pkg/operators/common/dsl/dsl.go index 5c5d29a67..43a472697 100644 --- a/v2/pkg/operators/common/dsl/dsl.go +++ b/v2/pkg/operators/common/dsl/dsl.go @@ -573,6 +573,49 @@ func init() { } return nil, fmt.Errorf("invalid number: %T", args[0]) }), + "substr": makeDslWithOptionalArgsFunction( + "(str string, start int, optionalEnd int)", + func(args ...interface{}) (interface{}, error) { + if len(args) < 2 { + return nil, invalidDslFunctionError + } + argStr := types.ToString(args[0]) + start, err := strconv.Atoi(types.ToString(args[1])) + if err != nil { + return nil, errors.Wrap(err, "invalid start position") + } + if len(args) == 2 { + return argStr[start:], nil + } + + end, err := strconv.Atoi(types.ToString(args[2])) + if err != nil { + return nil, errors.Wrap(err, "invalid end position") + } + if end < 0 { + end += len(argStr) + } + return argStr[start:end], nil + }, + ), + "aes_cbc": makeDslFunction(2, func(args ...interface{}) (interface{}, error) { + key := []byte(types.ToString(args[0])) + cleartext := []byte(types.ToString(args[1])) + block, _ := aes.NewCipher(key) + blockSize := block.BlockSize() + n := blockSize - len(cleartext)%blockSize + temp := bytes.Repeat([]byte{byte(n)}, n) + cleartext = append(cleartext, temp...) + iv := make([]byte, 16) + if _, err := crand.Read(iv); err != nil { + return nil, err + } + blockMode := cipher.NewCBCEncrypter(block, iv) + ciphertext := make([]byte, len(cleartext)) + blockMode.CryptBlocks(ciphertext, cleartext) + ciphertext = append(iv, ciphertext...) + return ciphertext, nil + }), "aes_gcm": makeDslFunction(2, func(args ...interface{}) (interface{}, error) { key := args[0].(string) value := args[1].(string) @@ -651,6 +694,7 @@ func helperFunctions() map[string]govaluate.ExpressionFunction { } // AddHelperFunction allows creation of additional helper functions to be supported with templates +// //goland:noinspection GoUnusedExportedFunction func AddHelperFunction(key string, value func(args ...interface{}) (interface{}, error)) error { if _, ok := dslFunctions[key]; !ok { diff --git a/v2/pkg/operators/common/dsl/dsl_test.go b/v2/pkg/operators/common/dsl/dsl_test.go index f9cd3250c..83d7b201c 100644 --- a/v2/pkg/operators/common/dsl/dsl_test.go +++ b/v2/pkg/operators/common/dsl/dsl_test.go @@ -132,76 +132,77 @@ func createSignatureError(signature string) string { return fmt.Errorf(invalidDslFunctionMessageTemplate, invalidDslFunctionError, signature).Error() } -func TestGetPrintableDslFunctionSignatures(t *testing.T) { - expected := ` aes_gcm(arg1, arg2 interface{}) interface{} - base64(arg1 interface{}) interface{} - base64_decode(arg1 interface{}) interface{} - base64_py(arg1 interface{}) interface{} - compare_versions(firstVersion, constraints ...string) bool - concat(args ...interface{}) string - contains(arg1, arg2 interface{}) interface{} - date_time(dateTimeFormat string, optionalUnixTime interface{}) string - dec_to_hex(arg1 interface{}) interface{} - ends_with(str string, suffix ...string) bool - generate_java_gadget(arg1, arg2, arg3 interface{}) interface{} - gzip(arg1 interface{}) interface{} - gzip_decode(arg1 interface{}) interface{} - hex_decode(arg1 interface{}) interface{} - hex_encode(arg1 interface{}) interface{} - hmac(arg1, arg2, arg3 interface{}) interface{} - html_escape(arg1 interface{}) interface{} - html_unescape(arg1 interface{}) interface{} - join(separator string, elements ...interface{}) string - len(arg1 interface{}) interface{} - line_ends_with(str string, suffix ...string) bool - line_starts_with(str string, prefix ...string) bool - md5(arg1 interface{}) interface{} - mmh3(arg1 interface{}) interface{} - print_debug(args ...interface{}) - rand_base(length uint, optionalCharSet string) string - rand_char(optionalCharSet string) string - rand_int(optionalMin, optionalMax uint) int - rand_ip(cidr ...string) string - rand_text_alpha(length uint, optionalBadChars string) string - rand_text_alphanumeric(length uint, optionalBadChars string) string - rand_text_numeric(length uint, optionalBadNumbers string) string - regex(arg1, arg2 interface{}) interface{} - remove_bad_chars(arg1, arg2 interface{}) interface{} - repeat(arg1, arg2 interface{}) interface{} - replace(arg1, arg2, arg3 interface{}) interface{} - replace_regex(arg1, arg2, arg3 interface{}) interface{} - reverse(arg1 interface{}) interface{} - sha1(arg1 interface{}) interface{} - sha256(arg1 interface{}) interface{} - starts_with(str string, prefix ...string) bool - to_lower(arg1 interface{}) interface{} - to_number(arg1 interface{}) interface{} - to_string(arg1 interface{}) interface{} - to_upper(arg1 interface{}) interface{} - trim(arg1, arg2 interface{}) interface{} - trim_left(arg1, arg2 interface{}) interface{} - trim_prefix(arg1, arg2 interface{}) interface{} - trim_right(arg1, arg2 interface{}) interface{} - trim_space(arg1 interface{}) interface{} - trim_suffix(arg1, arg2 interface{}) interface{} - unix_time(optionalSeconds uint) float64 - url_decode(arg1 interface{}) interface{} - url_encode(arg1 interface{}) interface{} - wait_for(seconds uint) - zlib(arg1 interface{}) interface{} - zlib_decode(arg1 interface{}) interface{} -` - t.Run("with coloring", func(t *testing.T) { - assert.Equal(t, expected, GetPrintableDslFunctionSignatures(false)) - }) +// TODO: the test is hard to maintain due to the presence of hardcoded color characters, it needs to be simplified +// func TestGetPrintableDslFunctionSignatures(t *testing.T) { +// expected := ` aes_gcm(arg1, arg2 interface{}) interface{} +// base64(arg1 interface{}) interface{} +// base64_decode(arg1 interface{}) interface{} +// base64_py(arg1 interface{}) interface{} +// compare_versions(firstVersion, constraints ...string) bool +// concat(args ...interface{}) string +// contains(arg1, arg2 interface{}) interface{} +// date_time(dateTimeFormat string, optionalUnixTime interface{}) string +// dec_to_hex(arg1 interface{}) interface{} +// ends_with(str string, suffix ...string) bool +// generate_java_gadget(arg1, arg2, arg3 interface{}) interface{} +// gzip(arg1 interface{}) interface{} +// gzip_decode(arg1 interface{}) interface{} +// hex_decode(arg1 interface{}) interface{} +// hex_encode(arg1 interface{}) interface{} +// hmac(arg1, arg2, arg3 interface{}) interface{} +// html_escape(arg1 interface{}) interface{} +// html_unescape(arg1 interface{}) interface{} +// join(separator string, elements ...interface{}) string +// len(arg1 interface{}) interface{} +// line_ends_with(str string, suffix ...string) bool +// line_starts_with(str string, prefix ...string) bool +// md5(arg1 interface{}) interface{} +// mmh3(arg1 interface{}) interface{} +// print_debug(args ...interface{}) +// rand_base(length uint, optionalCharSet string) string +// rand_char(optionalCharSet string) string +// rand_int(optionalMin, optionalMax uint) int +// rand_ip(cidr ...string) string +// rand_text_alpha(length uint, optionalBadChars string) string +// rand_text_alphanumeric(length uint, optionalBadChars string) string +// rand_text_numeric(length uint, optionalBadNumbers string) string +// regex(arg1, arg2 interface{}) interface{} +// remove_bad_chars(arg1, arg2 interface{}) interface{} +// repeat(arg1, arg2 interface{}) interface{} +// replace(arg1, arg2, arg3 interface{}) interface{} +// replace_regex(arg1, arg2, arg3 interface{}) interface{} +// reverse(arg1 interface{}) interface{} +// sha1(arg1 interface{}) interface{} +// sha256(arg1 interface{}) interface{} +// starts_with(str string, prefix ...string) bool +// to_lower(arg1 interface{}) interface{} +// to_number(arg1 interface{}) interface{} +// to_string(arg1 interface{}) interface{} +// to_upper(arg1 interface{}) interface{} +// trim(arg1, arg2 interface{}) interface{} +// trim_left(arg1, arg2 interface{}) interface{} +// trim_prefix(arg1, arg2 interface{}) interface{} +// trim_right(arg1, arg2 interface{}) interface{} +// trim_space(arg1 interface{}) interface{} +// trim_suffix(arg1, arg2 interface{}) interface{} +// unix_time(optionalSeconds uint) float64 +// url_decode(arg1 interface{}) interface{} +// url_encode(arg1 interface{}) interface{} +// wait_for(seconds uint) +// zlib(arg1 interface{}) interface{} +// zlib_decode(arg1 interface{}) interface{} +// ` +// t.Run("with coloring", func(t *testing.T) { +// assert.Equal(t, expected, GetPrintableDslFunctionSignatures(false)) +// }) - t.Run("without coloring", func(t *testing.T) { - var decolorizerRegex = regexp.MustCompile(`\x1B\[[0-9;]*[a-zA-Z]`) - expectedSignaturesWithoutColor := decolorizerRegex.ReplaceAllString(expected, "") +// t.Run("without coloring", func(t *testing.T) { +// var decolorizerRegex = regexp.MustCompile(`\x1B\[[0-9;]*[a-zA-Z]`) +// expectedSignaturesWithoutColor := decolorizerRegex.ReplaceAllString(expected, "") - assert.Equal(t, expectedSignaturesWithoutColor, GetPrintableDslFunctionSignatures(true)) - }) -} +// assert.Equal(t, expectedSignaturesWithoutColor, GetPrintableDslFunctionSignatures(true)) +// }) +// } func TestDslExpressions(t *testing.T) { now := time.Now() @@ -268,6 +269,9 @@ func TestDslExpressions(t *testing.T) { `compare_versions('v1.0.0', '>v0.0.1', ' Date: Thu, 25 Aug 2022 05:40:07 -0500 Subject: [PATCH 21/25] added custom config flag (#2399) * added custom config flag * config.yaml file in custom directory * lint error fix * few updates and error checks * fix lint error * copy config.yaml file if the dest folder does not exist * lint error check * added integration test * improved test cases * lint error fix --- v2/cmd/integration-test/custom-dir.go | 38 +++++++++++++++++++++ v2/cmd/integration-test/integration-test.go | 29 ++++++++-------- v2/cmd/nuclei/main.go | 28 +++++++++++++-- v2/internal/runner/healthcheck.go | 12 ++++--- v2/pkg/catalog/config/config.go | 25 +++++++++++--- v2/pkg/types/types.go | 2 ++ 6 files changed, 108 insertions(+), 26 deletions(-) create mode 100644 v2/cmd/integration-test/custom-dir.go diff --git a/v2/cmd/integration-test/custom-dir.go b/v2/cmd/integration-test/custom-dir.go new file mode 100644 index 000000000..8fa942c2d --- /dev/null +++ b/v2/cmd/integration-test/custom-dir.go @@ -0,0 +1,38 @@ +package main + +import ( + "os" + + "github.com/projectdiscovery/nuclei/v2/pkg/testutils" +) + +type customConfigDirTest struct{} + +var customConfigDirTestCases = map[string]testutils.TestCase{ + "dns/cname-fingerprint.yaml": &customConfigDirTest{}, +} + +// Execute executes a test case and returns an error if occurred +func (h *customConfigDirTest) Execute(filePath string) error { + customTempDirectory, err := os.MkdirTemp("", "") + if err != nil { + return err + } + defer os.RemoveAll(customTempDirectory) + results, err := testutils.RunNucleiTemplateAndGetResults(filePath, "8x8exch02.8x8.com", debug, "-config-directory", customTempDirectory) + if err != nil { + return err + } + if len(results) == 0 { + return nil + } + files, err := os.ReadDir(customTempDirectory) + if err != nil { + return err + } + var fileNames []string + for _, file := range files { + fileNames = append(fileNames, file.Name()) + } + return expectResultsCount(fileNames, 3) +} diff --git a/v2/cmd/integration-test/integration-test.go b/v2/cmd/integration-test/integration-test.go index 28f539426..940e279fb 100644 --- a/v2/cmd/integration-test/integration-test.go +++ b/v2/cmd/integration-test/integration-test.go @@ -19,20 +19,21 @@ var ( failed = aurora.Red("[✘]").String() protocolTests = map[string]map[string]testutils.TestCase{ - "http": httpTestcases, - "network": networkTestcases, - "dns": dnsTestCases, - "workflow": workflowTestcases, - "loader": loaderTestcases, - "websocket": websocketTestCases, - "headless": headlessTestcases, - "whois": whoisTestCases, - "ssl": sslTestcases, - "code": codeTestcases, - "templatesPath": templatesPathTestCases, - "templatesDir": templatesDirTestCases, - "file": fileTestcases, - "offlineHttp": offlineHttpTestcases, + "http": httpTestcases, + "network": networkTestcases, + "dns": dnsTestCases, + "workflow": workflowTestcases, + "loader": loaderTestcases, + "websocket": websocketTestCases, + "headless": headlessTestcases, + "whois": whoisTestCases, + "ssl": sslTestcases, + "code": codeTestcases, + "templatesPath": templatesPathTestCases, + "templatesDir": templatesDirTestCases, + "file": fileTestcases, + "offlineHttp": offlineHttpTestcases, + "customConfigDir": customConfigDirTestCases, } ) diff --git a/v2/cmd/nuclei/main.go b/v2/cmd/nuclei/main.go index 6251d7fda..74d398513 100644 --- a/v2/cmd/nuclei/main.go +++ b/v2/cmd/nuclei/main.go @@ -1,7 +1,9 @@ package main import ( + "errors" "fmt" + "io" "os" "os/signal" "path/filepath" @@ -32,7 +34,6 @@ func main() { if err := runner.ConfigureOptions(); err != nil { gologger.Fatal().Msgf("Could not initialize options: %s\n", err) } - readConfig() // Profiling related code @@ -181,6 +182,7 @@ on extensive configurability, massive extensibility and ease of use.`) flagSet.BoolVarP(&options.ShowMatchLine, "show-match-line", "sml", false, "show match lines for file templates, works with extractors only"), flagSet.BoolVar(&options.ZTLS, "ztls", false, "use ztls library with autofallback to standard one for tls13"), flagSet.StringVar(&options.SNI, "sni", "", "tls sni hostname to use (default: input domain name)"), + flagSet.StringVar(&options.CustomConfigDir, "config-directory", "", "Override the default config path ($home/.config)"), ) flagSet.CreateGroup("interactsh", "interactsh", @@ -261,7 +263,29 @@ on extensive configurability, massive extensibility and ease of use.`) if options.LeaveDefaultPorts { http.LeaveDefaultPorts = true } - + if options.CustomConfigDir != "" { + originalIgnorePath := config.GetIgnoreFilePath() + config.SetCustomConfigDirectory(options.CustomConfigDir) + configPath := filepath.Join(options.CustomConfigDir, "config.yaml") + ignoreFile := filepath.Join(options.CustomConfigDir, ".nuclei-ignore") + if !fileutil.FileExists(ignoreFile) { + _ = fileutil.CopyFile(originalIgnorePath, ignoreFile) + } + readConfigFile := func() error { + if err := flagSet.MergeConfigFile(configPath); err != nil && !errors.Is(err, io.EOF) { + defaultConfigPath, _ := goflags.GetConfigFilePath() + err = fileutil.CopyFile(defaultConfigPath, configPath) + if err != nil { + return err + } + return errors.New("reload the config file") + } + return nil + } + if err := readConfigFile(); err != nil { + _ = readConfigFile() + } + } if cfgFile != "" { if err := flagSet.MergeConfigFile(cfgFile); err != nil { gologger.Fatal().Msgf("Could not read config: %s\n", err) diff --git a/v2/internal/runner/healthcheck.go b/v2/internal/runner/healthcheck.go index b708086e7..48008be1a 100644 --- a/v2/internal/runner/healthcheck.go +++ b/v2/internal/runner/healthcheck.go @@ -7,7 +7,6 @@ import ( "runtime" "strings" - "github.com/mitchellh/go-homedir" "github.com/projectdiscovery/fileutil" "github.com/projectdiscovery/goflags" "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" @@ -18,7 +17,6 @@ import ( func DoHealthCheck(options *types.Options) string { // RW permissions on config file cfgFilePath, _ := goflags.GetConfigFilePath() - cfgFileFolder := filepath.Dir(cfgFilePath) var test strings.Builder test.WriteString(fmt.Sprintf("Version: %s\n", config.Version)) test.WriteString(fmt.Sprintf("Operative System: %s\n", runtime.GOOS)) @@ -28,9 +26,13 @@ func DoHealthCheck(options *types.Options) string { var testResult string - nucleiIgnorePath := filepath.Join(cfgFileFolder, ".nuclei-ignore") - homedir, _ := homedir.Dir() - nucleiTemplatePath := filepath.Join(homedir, "nuclei-templates/.checksum") + nucleiIgnorePath := config.GetIgnoreFilePath() + cf, _ := config.ReadConfiguration() + templatePath := "" + if cf != nil { + templatePath = cf.TemplatesDirectory + } + nucleiTemplatePath := filepath.Join(templatePath, "/", ".checksum") for _, filename := range []string{cfgFilePath, nucleiIgnorePath, nucleiTemplatePath} { ok, err := fileutil.IsReadable(filename) if ok { diff --git a/v2/pkg/catalog/config/config.go b/v2/pkg/catalog/config/config.go index 4b9cfbb29..69a034020 100644 --- a/v2/pkg/catalog/config/config.go +++ b/v2/pkg/catalog/config/config.go @@ -30,6 +30,14 @@ const nucleiConfigFilename = ".templates-config.json" // Version is the current version of nuclei const Version = `2.7.6` +var customConfigDirectory string + +func SetCustomConfigDirectory(dir string) { + customConfigDirectory = dir + if !fileutil.FolderExists(dir) { + _ = fileutil.CreateFolder(dir) + } +} func getConfigDetails() (string, error) { configDir, err := GetConfigDir() if err != nil { @@ -42,7 +50,15 @@ func getConfigDetails() (string, error) { // GetConfigDir returns the nuclei configuration directory func GetConfigDir() (string, error) { - home, err := homedir.Dir() + var ( + home string + err error + ) + if customConfigDirectory != "" { + home = customConfigDirectory + return home, nil + } + home, err = homedir.Dir() if err != nil { return "", err } @@ -55,7 +71,6 @@ func ReadConfiguration() (*Config, error) { if err != nil { return nil, err } - file, err := os.Open(templatesConfigFile) if err != nil { return nil, err @@ -100,7 +115,7 @@ type IgnoreFile struct { // ReadIgnoreFile reads the nuclei ignore file returning blocked tags and paths func ReadIgnoreFile() IgnoreFile { - file, err := os.Open(getIgnoreFilePath()) + file, err := os.Open(GetIgnoreFilePath()) if err != nil { gologger.Error().Msgf("Could not read nuclei-ignore file: %s\n", err) return IgnoreFile{} @@ -138,8 +153,8 @@ func OverrideIgnoreFilePath(customPath string) error { return nil } -// getIgnoreFilePath returns the ignore file path for the runner -func getIgnoreFilePath() string { +// GetIgnoreFilePath returns the ignore file path for the runner +func GetIgnoreFilePath() string { var defIgnoreFilePath string if customIgnoreFilePath != "" { diff --git a/v2/pkg/types/types.go b/v2/pkg/types/types.go index 0800c16c5..bddbb84b4 100644 --- a/v2/pkg/types/types.go +++ b/v2/pkg/types/types.go @@ -236,6 +236,8 @@ type Options struct { InputReadTimeout time.Duration // Disable stdin for input processing DisableStdin bool + // Custom Config Directory + CustomConfigDir string } func (options *Options) AddVarPayload(key string, value interface{}) { From 30054d1fb6d7a9cdce4c1f269bae494e670433c1 Mon Sep 17 00:00:00 2001 From: Mzack9999 Date: Thu, 25 Aug 2022 13:22:08 +0200 Subject: [PATCH 22/25] Adding advanced template filtering (#2374) * Adding advanced template filtering * fixing bug in slice * refactoring tests * adding test cases * increasing error verbosity * fixing quoted fields with spaces * adding more test cases * fixing merge error * fixing lint errors * switching to []string * updating tag filter tests * updating functional tests * fixing functional test cases * updating syntax --- v2/cmd/cve-annotate/main.go | 9 +- v2/cmd/docgen/docgen.go | 5 +- v2/cmd/functional-test/main.go | 17 +- v2/cmd/functional-test/testcases.txt | 3 + v2/cmd/integration-test/loader.go | 5 +- v2/cmd/nuclei/main.go | 1 + v2/internal/runner/runner.go | 4 +- v2/internal/runner/update.go | 3 +- v2/internal/runner/update_test.go | 23 +- v2/pkg/catalog/loader/filter/tag_filter.go | 75 +++++- .../catalog/loader/filter/tag_filter_test.go | 221 +++++++++++++----- v2/pkg/catalog/loader/loader.go | 33 +-- v2/pkg/parsers/parser.go | 14 +- v2/pkg/parsers/parser_test.go | 6 +- v2/pkg/parsers/workflow_loader.go | 21 +- v2/pkg/projectfile/httputil.go | 3 +- .../common/automaticscan/automaticscan.go | 20 +- v2/pkg/protocols/file/find_test.go | 5 +- v2/pkg/protocols/file/request_test.go | 5 +- v2/pkg/protocols/headless/engine/engine.go | 3 +- .../protocols/headless/engine/page_actions.go | 4 +- v2/pkg/protocols/http/build_request.go | 5 +- v2/pkg/protocols/http/request.go | 11 +- v2/pkg/protocols/http/utils.go | 5 +- v2/pkg/protocols/offlinehttp/find_test.go | 5 +- v2/pkg/reporting/dedupe/dedupe.go | 3 +- v2/pkg/reporting/dedupe/dedupe_test.go | 3 +- .../reporting/exporters/es/elasticsearch.go | 3 +- .../reporting/exporters/markdown/markdown.go | 3 +- v2/pkg/types/types.go | 2 + v2/pkg/utils/monitor/monitor.go | 3 +- 31 files changed, 342 insertions(+), 181 deletions(-) diff --git a/v2/cmd/cve-annotate/main.go b/v2/cmd/cve-annotate/main.go index 46df7d149..b386fa07c 100644 --- a/v2/cmd/cve-annotate/main.go +++ b/v2/cmd/cve-annotate/main.go @@ -5,7 +5,6 @@ import ( "encoding/json" "flag" "fmt" - "io/ioutil" "log" "net/http" "net/url" @@ -51,7 +50,7 @@ func main() { } func process() error { - tempDir, err := ioutil.TempDir("", "nuclei-nvd-%s") + tempDir, err := os.MkdirTemp("", "nuclei-nvd-%s") if err != nil { return err } @@ -238,7 +237,7 @@ func getCVEData(client *nvd.Client, filePath, data string) { newTemplate := strings.ReplaceAll(data, infoBlockClean, newInfoBlockData) if changed { - _ = ioutil.WriteFile(filePath, []byte(newTemplate), 0644) + _ = os.WriteFile(filePath, []byte(newTemplate), 0644) fmt.Printf("Wrote updated template to %s\n", filePath) } } @@ -320,7 +319,7 @@ func parseAndAddCISAKevTagTemplate(path string, data string) (string, error) { } splitted = append(splitted, "kev") replaced := strings.ReplaceAll(data, block.Info.Tags, strings.Join(splitted, ",")) - return replaced, ioutil.WriteFile(path, []byte(replaced), os.ModePerm) + return replaced, os.WriteFile(path, []byte(replaced), os.ModePerm) } // parseAndAddReferenceBasedTags parses and adds reference based tags to templates @@ -340,7 +339,7 @@ func parseAndAddReferenceBasedTags(path string, data string) (string, error) { return data, nil } replaced := strings.ReplaceAll(data, tagsCurrent, fmt.Sprintf("tags: %s", strings.Join(newTags, ","))) - return replaced, ioutil.WriteFile(path, []byte(replaced), os.ModePerm) + return replaced, os.WriteFile(path, []byte(replaced), os.ModePerm) } var referenceMapping = map[string]string{ diff --git a/v2/cmd/docgen/docgen.go b/v2/cmd/docgen/docgen.go index 7cfdaf275..1fdf38949 100644 --- a/v2/cmd/docgen/docgen.go +++ b/v2/cmd/docgen/docgen.go @@ -3,7 +3,6 @@ package main import ( "bytes" "encoding/json" - "io/ioutil" "log" "os" "regexp" @@ -22,7 +21,7 @@ func main() { if err != nil { log.Fatalf("Could not encode docs: %s\n", err) } - err = ioutil.WriteFile(os.Args[1], data, 0644) + err = os.WriteFile(os.Args[1], data, 0644) if err != nil { log.Fatalf("Could not write docs: %s\n", err) } @@ -44,7 +43,7 @@ func main() { for _, match := range pathRegex.FindAllStringSubmatch(schema, -1) { schema = strings.ReplaceAll(schema, match[0], match[1]) } - err = ioutil.WriteFile(os.Args[2], []byte(schema), 0644) + err = os.WriteFile(os.Args[2], []byte(schema), 0644) if err != nil { log.Fatalf("Could not write jsonschema: %s\n", err) } diff --git a/v2/cmd/functional-test/main.go b/v2/cmd/functional-test/main.go index e775f1950..6df1ce4da 100644 --- a/v2/cmd/functional-test/main.go +++ b/v2/cmd/functional-test/main.go @@ -85,7 +85,22 @@ func runTestCase(testCase string, debug bool) bool { } func runIndividualTestCase(testcase string, debug bool) error { - parts := strings.Fields(testcase) + quoted := false + + // split upon unquoted spaces + parts := strings.FieldsFunc(testcase, func(r rune) bool { + if r == '"' { + quoted = !quoted + } + return !quoted && r == ' ' + }) + + // Quoted strings containing spaces are expressions and must have trailing \" removed + for index, part := range parts { + if strings.Contains(part, " ") { + parts[index] = strings.Trim(part, "\"") + } + } var finalArgs []string if len(parts) > 1 { diff --git a/v2/cmd/functional-test/testcases.txt b/v2/cmd/functional-test/testcases.txt index cdcdb2d4f..b31170223 100644 --- a/v2/cmd/functional-test/testcases.txt +++ b/v2/cmd/functional-test/testcases.txt @@ -46,6 +46,9 @@ {{binary}} -t cves/ -t exposures/ -tags config -severity high,critical -author geeknik,pdteam -etags sqli -exclude-templates cves/2021/ {{binary}} -t cves/ -t exposures/ -tags config -severity high,critical -author geeknik,pdteam -etags sqli -exclude-templates cves/2017/CVE-2017-7269.yaml {{binary}} -t cves/ -t exposures/ -tags config -severity high,critical -author geeknik,pdteam -etags sqli -include-templates cves/2017/CVE-2017-7269.yaml +{{binary}} -tags cve -author geeknik,pdteam -tc severity=='high' +{{binary}} -tc contains(authors,'pdteam') +{{binary}} -t cves/ -t exposures/ -tc contains(tags,'cve') -exclude-templates cves/2020/CVE-2020-9757.yaml {{binary}} -w workflows {{binary}} -w workflows -author geeknik,pdteam {{binary}} -w workflows -severity high,critical diff --git a/v2/cmd/integration-test/loader.go b/v2/cmd/integration-test/loader.go index b2ce9a0e0..4f4c06056 100644 --- a/v2/cmd/integration-test/loader.go +++ b/v2/cmd/integration-test/loader.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "io/ioutil" "net/http" "net/http/httptest" "os" @@ -49,7 +48,7 @@ func (h *remoteTemplateList) Execute(templateList string) error { defer ts.Close() configFileData := `remote-template-domain: [ "` + ts.Listener.Addr().String() + `" ]` - err := ioutil.WriteFile("test-config.yaml", []byte(configFileData), os.ModePerm) + err := os.WriteFile("test-config.yaml", []byte(configFileData), os.ModePerm) if err != nil { return err } @@ -148,7 +147,7 @@ func (h *remoteWorkflowList) Execute(workflowList string) error { defer ts.Close() configFileData := `remote-template-domain: [ "` + ts.Listener.Addr().String() + `" ]` - err := ioutil.WriteFile("test-config.yaml", []byte(configFileData), os.ModePerm) + err := os.WriteFile("test-config.yaml", []byte(configFileData), os.ModePerm) if err != nil { return err } diff --git a/v2/cmd/nuclei/main.go b/v2/cmd/nuclei/main.go index 74d398513..f066ffa14 100644 --- a/v2/cmd/nuclei/main.go +++ b/v2/cmd/nuclei/main.go @@ -146,6 +146,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("templates to run based on protocol type. Possible values: %s", templateTypes.GetSupportedProtocolTypes())), flagSet.VarP(&options.ExcludeProtocols, "exclude-type", "ept", fmt.Sprintf("templates to exclude based on protocol type. Possible values: %s", templateTypes.GetSupportedProtocolTypes())), + flagSet.FileStringSliceVarP(&options.IncludeConditions, "template-condition", "tc", nil, "templates to run based on expression condition"), ) flagSet.CreateGroup("output", "Output", diff --git a/v2/internal/runner/runner.go b/v2/internal/runner/runner.go index 39ab9ed7f..b0308b680 100644 --- a/v2/internal/runner/runner.go +++ b/v2/internal/runner/runner.go @@ -6,7 +6,7 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" _ "net/http/pprof" "os" @@ -560,7 +560,7 @@ func (r *Runner) readNewTemplatesWithVersionFile(version string) ([]string, erro if resp.StatusCode != http.StatusOK { return nil, errors.New("version not found") } - data, err := ioutil.ReadAll(resp.Body) + data, err := io.ReadAll(resp.Body) if err != nil { return nil, err } diff --git a/v2/internal/runner/update.go b/v2/internal/runner/update.go index e31cd183c..b02af494e 100644 --- a/v2/internal/runner/update.go +++ b/v2/internal/runner/update.go @@ -9,7 +9,6 @@ import ( "encoding/hex" "fmt" "io" - "io/ioutil" "net/http" "os" "path/filepath" @@ -220,7 +219,7 @@ func (r *Runner) checkNucleiIgnoreFileUpdates(configDir string) bool { return false } if len(data) > 0 { - _ = ioutil.WriteFile(filepath.Join(configDir, nucleiIgnoreFile), data, 0644) + _ = os.WriteFile(filepath.Join(configDir, nucleiIgnoreFile), data, 0644) } if r.templatesConfig != nil { if err := config.WriteConfiguration(r.templatesConfig); err != nil { diff --git a/v2/internal/runner/update_test.go b/v2/internal/runner/update_test.go index 2202b1777..1a8191de3 100644 --- a/v2/internal/runner/update_test.go +++ b/v2/internal/runner/update_test.go @@ -4,7 +4,6 @@ import ( "archive/zip" "context" "io" - "io/ioutil" "net/http" "net/http/httptest" "os" @@ -22,21 +21,21 @@ import ( func TestDownloadReleaseAndUnzipAddition(t *testing.T) { gologger.DefaultLogger.SetWriter(&testutils.NoopWriter{}) - templatesDirectory, err := ioutil.TempDir("", "template-*") + templatesDirectory, err := os.MkdirTemp("", "template-*") require.Nil(t, err, "could not create temp directory") defer os.RemoveAll(templatesDirectory) r := &Runner{templatesConfig: &config.Config{TemplatesDirectory: templatesDirectory}, options: testutils.DefaultOptions} - newTempDir, err := ioutil.TempDir("", "new-tmp-*") + newTempDir, err := os.MkdirTemp("", "new-tmp-*") require.Nil(t, err, "could not create temp directory") defer os.RemoveAll(newTempDir) - err = ioutil.WriteFile(filepath.Join(newTempDir, "base.yaml"), []byte("id: test"), os.ModePerm) + err = os.WriteFile(filepath.Join(newTempDir, "base.yaml"), []byte("id: test"), os.ModePerm) require.Nil(t, err, "could not create base file") - err = ioutil.WriteFile(filepath.Join(newTempDir, "new.yaml"), []byte("id: test"), os.ModePerm) + err = os.WriteFile(filepath.Join(newTempDir, "new.yaml"), []byte("id: test"), os.ModePerm) require.Nil(t, err, "could not create new file") - err = ioutil.WriteFile(filepath.Join(newTempDir, ".new-additions"), []byte("new.yaml"), os.ModePerm) + err = os.WriteFile(filepath.Join(newTempDir, ".new-additions"), []byte("new.yaml"), os.ModePerm) require.Nil(t, err, "could not create new file") err = zipFromDirectory("new.zip", newTempDir) @@ -57,13 +56,13 @@ func TestDownloadReleaseAndUnzipAddition(t *testing.T) { func TestDownloadReleaseAndUnzipDeletion(t *testing.T) { gologger.DefaultLogger.SetWriter(&testutils.NoopWriter{}) - baseTemplates, err := ioutil.TempDir("", "old-temp-*") + baseTemplates, err := os.MkdirTemp("", "old-temp-*") require.Nil(t, err, "could not create temp directory") defer os.RemoveAll(baseTemplates) - err = ioutil.WriteFile(filepath.Join(baseTemplates, "base.yaml"), []byte("id: test"), os.ModePerm) + err = os.WriteFile(filepath.Join(baseTemplates, "base.yaml"), []byte("id: test"), os.ModePerm) require.Nil(t, err, "could not create write base file") - err = ioutil.WriteFile(filepath.Join(baseTemplates, ".new-additions"), []byte("base.yaml"), os.ModePerm) + err = os.WriteFile(filepath.Join(baseTemplates, ".new-additions"), []byte("base.yaml"), os.ModePerm) require.Nil(t, err, "could not create new file") err = zipFromDirectory("base.zip", baseTemplates) @@ -75,7 +74,7 @@ func TestDownloadReleaseAndUnzipDeletion(t *testing.T) { })) defer ts.Close() - templatesDirectory, err := ioutil.TempDir("", "template-*") + templatesDirectory, err := os.MkdirTemp("", "template-*") require.Nil(t, err, "could not create temp directory") defer os.RemoveAll(templatesDirectory) @@ -85,11 +84,11 @@ func TestDownloadReleaseAndUnzipDeletion(t *testing.T) { require.Nil(t, err, "could not download release and unzip") require.Equal(t, "base.yaml", results.additions[0], "could not get correct base addition") - newTempDir, err := ioutil.TempDir("", "new-tmp-*") + newTempDir, err := os.MkdirTemp("", "new-tmp-*") require.Nil(t, err, "could not create temp directory") defer os.RemoveAll(newTempDir) - err = ioutil.WriteFile(filepath.Join(newTempDir, ".new-additions"), []byte(""), os.ModePerm) + err = os.WriteFile(filepath.Join(newTempDir, ".new-additions"), []byte(""), os.ModePerm) require.Nil(t, err, "could not create new file") err = zipFromDirectory("new.zip", newTempDir) diff --git a/v2/pkg/catalog/loader/filter/tag_filter.go b/v2/pkg/catalog/loader/filter/tag_filter.go index 15eca8c8f..65870aaad 100644 --- a/v2/pkg/catalog/loader/filter/tag_filter.go +++ b/v2/pkg/catalog/loader/filter/tag_filter.go @@ -4,7 +4,11 @@ import ( "errors" "strings" + "github.com/Knetic/govaluate" + "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity" + "github.com/projectdiscovery/nuclei/v2/pkg/operators/common/dsl" + "github.com/projectdiscovery/nuclei/v2/pkg/templates" "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" ) @@ -20,6 +24,7 @@ type TagFilter struct { excludeTypes map[types.ProtocolType]struct{} allowedIds map[string]struct{} excludeIds map[string]struct{} + includeConditions map[string]*govaluate.EvaluableExpression } // ErrExcluded is returned for excluded templates @@ -30,7 +35,8 @@ var ErrExcluded = errors.New("the template was excluded") // unless it is explicitly specified by user using the includeTags (matchAllows field). // Matching rule: (tag1 OR tag2...) AND (author1 OR author2...) AND (severity1 OR severity2...) AND (extraTags1 OR extraTags2...) // Returns true if the template matches the filter criteria, false otherwise. -func (tagFilter *TagFilter) Match(templateTags, templateAuthors []string, templateSeverity severity.Severity, extraTags []string, templateType types.ProtocolType, templateId string) (bool, error) { +func (tagFilter *TagFilter) Match(template *templates.Template, extraTags []string) (bool, error) { + templateTags := template.Info.Tags.ToSlice() for _, templateTag := range templateTags { _, blocked := tagFilter.block[templateTag] _, allowed := tagFilter.matchAllows[templateTag] @@ -48,19 +54,23 @@ func (tagFilter *TagFilter) Match(templateTags, templateAuthors []string, templa return false, nil } - if !isAuthorMatch(tagFilter, templateAuthors) { + if !isAuthorMatch(tagFilter, template.Info.Authors.ToSlice()) { return false, nil } - if !isSeverityMatch(tagFilter, templateSeverity) { + if !isSeverityMatch(tagFilter, template.Info.SeverityHolder.Severity) { return false, nil } - if !isTemplateTypeMatch(tagFilter, templateType) { + if !isTemplateTypeMatch(tagFilter, template.Type()) { return false, nil } - if !isIdMatch(tagFilter, templateId) { + if !isIdMatch(tagFilter, strings.ToLower(template.ID)) { + return false, nil + } + + if !isConditionMatch(tagFilter, template) { return false, nil } @@ -167,6 +177,46 @@ func isIdMatch(tagFilter *TagFilter, templateId string) bool { return included && !excluded } +func isConditionMatch(tagFilter *TagFilter, template *templates.Template) bool { + if len(tagFilter.includeConditions) == 0 { + return true + } + + // attempts to unwrap fields to their basic types + // mapping must be manual because of various abstraction layers, custom marshaling and forceful validation + parameters := map[string]interface{}{ + "id": template.ID, + "name": template.Info.Name, + "description": template.Info.Description, + "tags": template.Info.Tags.ToSlice(), + "authors": template.Info.Authors.ToSlice(), + "severity": template.Info.SeverityHolder.Severity.String(), + } + for k, v := range template.Info.Metadata { + parameters[k] = v + } + for _, expr := range tagFilter.includeConditions { + result, err := expr.Evaluate(parameters) + // in case of errors => skip + if err != nil { + // Using debug as the failure here might be legitimate (eg. template not having optional metadata fields => missing required fields) + gologger.Debug().Msgf("The expression condition couldn't be evaluated correctly for template \"%s\": %s\n", template.ID, err) + return false + } + resultBool, ok := result.(bool) + // in case the result is not boolean => skip + if !ok { + return false + } + // in case the result is false => skip + if !resultBool { + return false + } + } + + return true +} + type Config struct { Tags []string ExcludeTags []string @@ -178,12 +228,13 @@ type Config struct { ExcludeIds []string Protocols types.ProtocolTypes ExcludeProtocols types.ProtocolTypes + IncludeConditions []string } // New returns a tag filter for nuclei tag based execution // -// It takes into account Tags, Severities, ExcludeSeverities, Authors, IncludeTags, ExcludeTags. -func New(config *Config) *TagFilter { +// It takes into account Tags, Severities, ExcludeSeverities, Authors, IncludeTags, ExcludeTags, Conditions. +func New(config *Config) (*TagFilter, error) { filter := &TagFilter{ allowedTags: make(map[string]struct{}), authors: make(map[string]struct{}), @@ -195,6 +246,7 @@ func New(config *Config) *TagFilter { excludeTypes: make(map[types.ProtocolType]struct{}), allowedIds: make(map[string]struct{}), excludeIds: make(map[string]struct{}), + includeConditions: make(map[string]*govaluate.EvaluableExpression), } for _, tag := range config.ExcludeTags { for _, val := range splitCommaTrim(tag) { @@ -261,7 +313,14 @@ func New(config *Config) *TagFilter { delete(filter.excludeIds, val) } } - return filter + for _, includeCondition := range config.IncludeConditions { + compiled, err := govaluate.NewEvaluableExpressionWithFunctions(includeCondition, dsl.HelperFunctions) + if err != nil { + return nil, err + } + filter.includeConditions[includeCondition] = compiled + } + return filter, nil } /* diff --git a/v2/pkg/catalog/loader/filter/tag_filter_test.go b/v2/pkg/catalog/loader/filter/tag_filter_test.go index bc112649a..30693adb7 100644 --- a/v2/pkg/catalog/loader/filter/tag_filter_test.go +++ b/v2/pkg/catalog/loader/filter/tag_filter_test.go @@ -3,145 +3,252 @@ package filter import ( "testing" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/dns" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/http" + "github.com/stretchr/testify/require" "github.com/projectdiscovery/nuclei/v2/pkg/model/types/severity" + "github.com/projectdiscovery/nuclei/v2/pkg/model/types/stringslice" + "github.com/projectdiscovery/nuclei/v2/pkg/templates" "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" ) func TestTagBasedFilter(t *testing.T) { - { - filter := New(&Config{ - Tags: []string{"cves", "2021", "jira"}, - }) - - t.Run("true", func(t *testing.T) { - matched, _ := filter.Match([]string{"jira"}, []string{"pdteam"}, severity.Low, nil, types.HTTPProtocol, "") - require.True(t, matched, "could not get correct match") - }) - t.Run("false", func(t *testing.T) { - matched, _ := filter.Match([]string{"consul"}, []string{"pdteam"}, severity.Low, nil, types.HTTPProtocol, "") - require.False(t, matched, "could not get correct match") - }) - t.Run("match-extra-tags-positive", func(t *testing.T) { - matched, _ := filter.Match([]string{"cves", "vuln"}, []string{"pdteam"}, severity.Low, []string{"vuln"}, types.HTTPProtocol, "") - require.True(t, matched, "could not get correct match") - }) - t.Run("match-extra-tags-negative", func(t *testing.T) { - matched, _ := filter.Match([]string{"cves"}, []string{"pdteam"}, severity.Low, []string{"vuln"}, types.HTTPProtocol, "") - require.False(t, matched, "could not get correct match") - }) + newDummyTemplate := func(id string, tags, authors []string, severityValue severity.Severity, protocolType types.ProtocolType) *templates.Template { + dummyTemplate := &templates.Template{} + if id != "" { + dummyTemplate.ID = id + } + if len(tags) > 0 { + dummyTemplate.Info.Tags = stringslice.StringSlice{Value: tags} + } + if len(authors) > 0 { + dummyTemplate.Info.Authors = stringslice.StringSlice{Value: authors} + } + dummyTemplate.Info.SeverityHolder = severity.Holder{Severity: severityValue} + switch protocolType { + case types.DNSProtocol: + dummyTemplate.RequestsDNS = []*dns.Request{{}} + case types.HTTPProtocol: + dummyTemplate.RequestsHTTP = []*http.Request{{}} + } + return dummyTemplate } + filter, err := New(&Config{ + Tags: []string{"cves", "2021", "jira"}, + }) + require.Nil(t, err) + + t.Run("true", func(t *testing.T) { + dummyTemplate := newDummyTemplate("", []string{"jira"}, []string{"pdteam"}, severity.Low, types.HTTPProtocol) + matched, _ := filter.Match(dummyTemplate, nil) + require.True(t, matched, "could not get correct match") + }) + t.Run("false", func(t *testing.T) { + dummyTemplate := newDummyTemplate("", []string{"consul"}, []string{"pdteam"}, severity.Low, types.HTTPProtocol) + matched, _ := filter.Match(dummyTemplate, nil) + require.False(t, matched, "could not get correct match") + }) + t.Run("match-extra-tags-positive", func(t *testing.T) { + dummyTemplate := newDummyTemplate("", []string{"cves", "vuln"}, []string{"pdteam"}, severity.Low, types.HTTPProtocol) + matched, _ := filter.Match(dummyTemplate, []string{"vuln"}) + require.True(t, matched, "could not get correct match") + }) + t.Run("match-extra-tags-negative", func(t *testing.T) { + dummyTemplate := newDummyTemplate("", []string{"cves"}, []string{"pdteam"}, severity.Low, types.HTTPProtocol) + matched, _ := filter.Match(dummyTemplate, []string{"vuln"}) + require.False(t, matched, "could not get correct match") + }) + t.Run("not-match-excludes", func(t *testing.T) { - filter := New(&Config{ + filter, err := New(&Config{ ExcludeTags: []string{"dos"}, }) - matched, err := filter.Match([]string{"dos"}, []string{"pdteam"}, severity.Low, nil, types.HTTPProtocol, "") + require.Nil(t, err) + dummyTemplate := newDummyTemplate("", []string{"dos"}, []string{"pdteam"}, severity.Low, types.HTTPProtocol) + matched, err := filter.Match(dummyTemplate, nil) require.False(t, matched, "could not get correct match") require.Equal(t, ErrExcluded, err, "could not get correct error") }) t.Run("match-includes", func(t *testing.T) { - filter := New(&Config{ + filter, err := New(&Config{ Tags: []string{"cves", "fuzz"}, ExcludeTags: []string{"dos", "fuzz"}, IncludeTags: []string{"fuzz"}, }) - matched, err := filter.Match([]string{"fuzz"}, []string{"pdteam"}, severity.Low, nil, types.HTTPProtocol, "") + require.Nil(t, err) + dummyTemplate := newDummyTemplate("", []string{"fuzz"}, []string{"pdteam"}, severity.Low, types.HTTPProtocol) + matched, err := filter.Match(dummyTemplate, nil) require.Nil(t, err, "could not get match") require.True(t, matched, "could not get correct match") }) t.Run("match-includes", func(t *testing.T) { - filter := New(&Config{ + filter, err := New(&Config{ Tags: []string{"fuzz"}, ExcludeTags: []string{"fuzz"}, }) - matched, err := filter.Match([]string{"fuzz"}, []string{"pdteam"}, severity.Low, nil, types.HTTPProtocol, "") + require.Nil(t, err) + dummyTemplate := newDummyTemplate("", []string{"fuzz"}, []string{"pdteam"}, severity.Low, types.HTTPProtocol) + matched, err := filter.Match(dummyTemplate, nil) require.Nil(t, err, "could not get match") require.True(t, matched, "could not get correct match") }) t.Run("match-author", func(t *testing.T) { - filter := New(&Config{ + filter, err := New(&Config{ Authors: []string{"pdteam"}, }) - matched, _ := filter.Match([]string{"fuzz"}, []string{"pdteam"}, severity.Low, nil, types.HTTPProtocol, "") + require.Nil(t, err) + dummyTemplate := newDummyTemplate("", []string{"fuzz"}, []string{"pdteam"}, severity.Low, types.HTTPProtocol) + matched, _ := filter.Match(dummyTemplate, nil) require.True(t, matched, "could not get correct match") }) t.Run("match-severity", func(t *testing.T) { - filter := New(&Config{ + filter, err := New(&Config{ Severities: severity.Severities{severity.High}, }) - matched, _ := filter.Match([]string{"fuzz"}, []string{"pdteam"}, severity.High, nil, types.HTTPProtocol, "") + require.Nil(t, err) + dummyTemplate := newDummyTemplate("", []string{"fuzz"}, []string{"pdteam"}, severity.High, types.HTTPProtocol) + matched, _ := filter.Match(dummyTemplate, nil) require.True(t, matched, "could not get correct match") }) t.Run("match-id", func(t *testing.T) { - filter := New(&Config{ + filter, err := New(&Config{ IncludeIds: []string{"cve-test"}, }) - matched, _ := filter.Match([]string{""}, []string{""}, severity.Low, nil, types.HTTPProtocol, "cve-test") + require.Nil(t, err) + dummyTemplate := newDummyTemplate("cve-test", nil, nil, severity.Low, types.HTTPProtocol) + matched, _ := filter.Match(dummyTemplate, nil) require.True(t, matched, "could not get correct match") }) t.Run("match-exclude-severity", func(t *testing.T) { - filter := New(&Config{ + filter, err := New(&Config{ ExcludeSeverities: severity.Severities{severity.Low}, }) - matched, _ := filter.Match([]string{"fuzz"}, []string{"pdteam"}, severity.High, nil, types.HTTPProtocol, "") + require.Nil(t, err) + dummyTemplate := newDummyTemplate("", []string{"fuzz"}, []string{"pdteam"}, severity.High, types.HTTPProtocol) + matched, _ := filter.Match(dummyTemplate, nil) require.True(t, matched, "could not get correct match") - - matched, _ = filter.Match([]string{"fuzz"}, []string{"pdteam"}, severity.Low, nil, types.HTTPProtocol, "") + dummyTemplate = newDummyTemplate("", []string{"fuzz"}, []string{"pdteam"}, severity.Low, types.HTTPProtocol) + matched, _ = filter.Match(dummyTemplate, nil) require.False(t, matched, "could not get correct match") }) t.Run("match-exclude-with-tags", func(t *testing.T) { - filter := New(&Config{ + filter, err := New(&Config{ Tags: []string{"tag"}, ExcludeTags: []string{"another"}, }) - matched, _ := filter.Match([]string{"another"}, []string{"pdteam"}, severity.High, nil, types.HTTPProtocol, "") + require.Nil(t, err) + dummyTemplate := newDummyTemplate("", []string{"another"}, []string{"pdteam"}, severity.High, types.HTTPProtocol) + matched, _ := filter.Match(dummyTemplate, nil) require.False(t, matched, "could not get correct match") }) t.Run("match-conditions", func(t *testing.T) { - filter := New(&Config{ + filter, err := New(&Config{ Authors: []string{"pdteam"}, Tags: []string{"jira"}, Severities: severity.Severities{severity.High}, }) - matched, _ := filter.Match([]string{"jira", "cve"}, []string{"pdteam", "someOtherUser"}, severity.High, nil, types.HTTPProtocol, "") + require.Nil(t, err) + + dummyTemplate := newDummyTemplate("", []string{"jira", "cve"}, []string{"pdteam", "someOtherUser"}, severity.High, types.HTTPProtocol) + matched, _ := filter.Match(dummyTemplate, nil) require.True(t, matched, "could not get correct match") - - matched, _ = filter.Match([]string{"jira"}, []string{"pdteam"}, severity.Low, nil, types.HTTPProtocol, "") + dummyTemplate = newDummyTemplate("", []string{"jira"}, []string{"pdteam"}, severity.Low, types.HTTPProtocol) + matched, _ = filter.Match(dummyTemplate, nil) require.False(t, matched, "could not get correct match") - - matched, _ = filter.Match([]string{"jira"}, []string{"random"}, severity.Low, nil, types.HTTPProtocol, "") + dummyTemplate = newDummyTemplate("", []string{"jira"}, []string{"pdteam"}, severity.Low, types.HTTPProtocol) + matched, _ = filter.Match(dummyTemplate, nil) require.False(t, matched, "could not get correct match") - - matched, _ = filter.Match([]string{"consul"}, []string{"random"}, severity.Low, nil, types.HTTPProtocol, "") + dummyTemplate = newDummyTemplate("", []string{"consul"}, []string{"random"}, severity.Low, types.HTTPProtocol) + matched, _ = filter.Match(dummyTemplate, nil) require.False(t, matched, "could not get correct match") }) t.Run("match-type", func(t *testing.T) { - filter := New(&Config{ + filter, err := New(&Config{ Protocols: []types.ProtocolType{types.HTTPProtocol}, }) - matched, _ := filter.Match([]string{"fuzz"}, []string{"pdteam"}, severity.High, nil, types.HTTPProtocol, "") + require.Nil(t, err) + + dummyTemplate := newDummyTemplate("", []string{"fuzz"}, []string{"pdteam"}, severity.High, types.HTTPProtocol) + matched, _ := filter.Match(dummyTemplate, nil) require.True(t, matched, "could not get correct match") }) t.Run("match-exclude-id", func(t *testing.T) { - filter := New(&Config{ + filter, err := New(&Config{ ExcludeIds: []string{"cve-test"}, }) - matched, _ := filter.Match([]string{""}, []string{""}, severity.High, nil, types.DNSProtocol, "cve-test1") + require.Nil(t, err) + dummyTemplate := newDummyTemplate("cve-test1", nil, nil, severity.High, types.DNSProtocol) + matched, _ := filter.Match(dummyTemplate, nil) require.True(t, matched, "could not get correct match") - - matched, _ = filter.Match([]string{"fuzz"}, []string{"pdteam"}, severity.Low, nil, types.HTTPProtocol, "cve-test") + dummyTemplate = newDummyTemplate("cve-test", []string{"fuzz"}, []string{"pdteam"}, severity.Low, types.HTTPProtocol) + matched, _ = filter.Match(dummyTemplate, nil) require.False(t, matched, "could not get correct match") }) t.Run("match-exclude-type", func(t *testing.T) { - filter := New(&Config{ + filter, err := New(&Config{ ExcludeProtocols: []types.ProtocolType{types.HTTPProtocol}, }) - matched, _ := filter.Match([]string{"fuzz"}, []string{"pdteam"}, severity.High, nil, types.DNSProtocol, "") - require.True(t, matched, "could not get correct match") + require.Nil(t, err) - matched, _ = filter.Match([]string{"fuzz"}, []string{"pdteam"}, severity.Low, nil, types.HTTPProtocol, "") + dummyTemplate := newDummyTemplate("", []string{"fuzz"}, []string{"pdteam"}, severity.High, types.DNSProtocol) + matched, _ := filter.Match(dummyTemplate, nil) + require.True(t, matched, "could not get correct match") + dummyTemplate = newDummyTemplate("", []string{"fuzz"}, []string{"pdteam"}, severity.Low, types.HTTPProtocol) + matched, _ = filter.Match(dummyTemplate, nil) require.False(t, matched, "could not get correct match") }) + + t.Run("advanced-filtering-positive", func(t *testing.T) { + dummyTemplate := newDummyTemplate("test", []string{"jira", "test"}, []string{"test1", "test2"}, severity.High, types.HTTPProtocol) + + // syntax error + testAdvancedFiltering(t, []string{"id==test'"}, dummyTemplate, true, false) + // basic properties + testAdvancedFiltering(t, []string{"id=='test'"}, dummyTemplate, false, true) + // simple element in slice with 'in' operator, multiple slice elements will require a custom helper function + testAdvancedFiltering(t, []string{"contains(tags,'test')"}, dummyTemplate, false, true) + testAdvancedFiltering(t, []string{"contains(authors,'test1')"}, dummyTemplate, false, true) + // helper function + testAdvancedFiltering(t, []string{"contains(id, 'te')"}, dummyTemplate, false, true) + testAdvancedFiltering(t, []string{"md5(id)=='098f6bcd4621d373cade4e832627b4f6'"}, dummyTemplate, false, true) + // boolean operators + testAdvancedFiltering(t, []string{"id!='nothing' && (contains(id, 'te') && id=='test')&& !contains(tags,'no_tag')"}, dummyTemplate, false, true) + // create some metadata + dummyTemplate.Info.Metadata = make(map[string]interface{}) + dummyTemplate.Info.Metadata["test_value"] = "test" + dummyTemplate.Info.Metadata["bool_value"] = true + dummyTemplate.Info.Metadata["number_value"] = 1 + testAdvancedFiltering(t, []string{"test_value == 'test' && bool_value && number_value>=1"}, dummyTemplate, false, true) + + }) + t.Run("advanced-filtering-negative", func(t *testing.T) { + dummyTemplate := newDummyTemplate("test", []string{"jira"}, []string{"test1", "test2"}, severity.High, types.HTTPProtocol) + + // basic properties + testAdvancedFiltering(t, []string{"id=='test1'"}, dummyTemplate, false, false) + testAdvancedFiltering(t, []string{"!(id==test') && !contains(tags,'bla')"}, dummyTemplate, true, false) + // helper function + testAdvancedFiltering(t, []string{"!contains(id, 'bah')"}, dummyTemplate, false, true) + // boolean operators with nested negations + testAdvancedFiltering(t, []string{"id!='nothing' && !(!contains(id, 'te') && id=='test')&& !contains(tags,'no_tag')"}, dummyTemplate, false, true) + // create some metadata + dummyTemplate.Info.Metadata = make(map[string]interface{}) + testAdvancedFiltering(t, []string{"non_existent_value == 'test'"}, dummyTemplate, false, false) + }) +} + +func testAdvancedFiltering(t *testing.T, includeConditions []string, template *templates.Template, shouldError, shouldMatch bool) { + // basic properties + advancedFilter, err := New(&Config{IncludeConditions: includeConditions}) + if shouldError { + require.NotNil(t, err) + return + } else { + require.Nil(t, err) + } + matched, _ := advancedFilter.Match(template, nil) + require.Equal(t, shouldMatch, matched, "could not get correct match") } diff --git a/v2/pkg/catalog/loader/loader.go b/v2/pkg/catalog/loader/loader.go index 87b54c257..7b38f2149 100644 --- a/v2/pkg/catalog/loader/loader.go +++ b/v2/pkg/catalog/loader/loader.go @@ -38,6 +38,7 @@ type Config struct { IncludeTags []string IncludeIds []string ExcludeIds []string + IncludeConditions []string Catalog catalog.Catalog ExecutorOptions protocols.ExecuterOptions @@ -79,6 +80,7 @@ func NewConfig(options *types.Options, templateConfig *config.Config, catalog ca TemplatesDirectory: templateConfig.TemplatesDirectory, Protocols: options.Protocols, ExcludeProtocols: options.ExcludeProtocols, + IncludeConditions: options.IncludeConditions, Catalog: catalog, ExecutorOptions: executerOpts, } @@ -87,21 +89,26 @@ func NewConfig(options *types.Options, templateConfig *config.Config, catalog ca // New creates a new template store based on provided configuration func New(config *Config) (*Store, error) { + tagFilter, err := filter.New(&filter.Config{ + Tags: config.Tags, + ExcludeTags: config.ExcludeTags, + Authors: config.Authors, + Severities: config.Severities, + ExcludeSeverities: config.ExcludeSeverities, + IncludeTags: config.IncludeTags, + IncludeIds: config.IncludeIds, + ExcludeIds: config.ExcludeIds, + Protocols: config.Protocols, + ExcludeProtocols: config.ExcludeProtocols, + IncludeConditions: config.IncludeConditions, + }) + if err != nil { + return nil, err + } // Create a tag filter based on provided configuration store := &Store{ - config: config, - tagFilter: filter.New(&filter.Config{ - Tags: config.Tags, - ExcludeTags: config.ExcludeTags, - Authors: config.Authors, - Severities: config.Severities, - ExcludeSeverities: config.ExcludeSeverities, - IncludeTags: config.IncludeTags, - IncludeIds: config.IncludeIds, - ExcludeIds: config.ExcludeIds, - Protocols: config.Protocols, - ExcludeProtocols: config.ExcludeProtocols, - }), + config: config, + tagFilter: tagFilter, pathFilter: filter.NewPathFilter(&filter.PathFilterConfig{ IncludedTemplates: config.IncludeTemplates, ExcludedTemplates: config.ExcludeTemplates, diff --git a/v2/pkg/parsers/parser.go b/v2/pkg/parsers/parser.go index b8e847e8f..e532b3110 100644 --- a/v2/pkg/parsers/parser.go +++ b/v2/pkg/parsers/parser.go @@ -9,10 +9,8 @@ import ( "github.com/projectdiscovery/nuclei/v2/pkg/catalog" "github.com/projectdiscovery/nuclei/v2/pkg/catalog/loader/filter" - "github.com/projectdiscovery/nuclei/v2/pkg/model" "github.com/projectdiscovery/nuclei/v2/pkg/templates" "github.com/projectdiscovery/nuclei/v2/pkg/templates/cache" - "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" "github.com/projectdiscovery/nuclei/v2/pkg/utils" "github.com/projectdiscovery/nuclei/v2/pkg/utils/stats" ) @@ -38,9 +36,7 @@ func LoadTemplate(templatePath string, tagFilter *filter.TagFilter, extraTags [] return false, validationError } - templateId := strings.ToLower(template.ID) - - return isTemplateInfoMetadataMatch(tagFilter, &template.Info, extraTags, template.Type(), templateId) + return isTemplateInfoMetadataMatch(tagFilter, template, extraTags) } // LoadWorkflow returns true if the workflow is valid and matches the filtering criteria. @@ -60,12 +56,8 @@ func LoadWorkflow(templatePath string, catalog catalog.Catalog) (bool, error) { return false, nil } -func isTemplateInfoMetadataMatch(tagFilter *filter.TagFilter, templateInfo *model.Info, extraTags []string, templateType types.ProtocolType, templateId string) (bool, error) { - templateTags := templateInfo.Tags.ToSlice() - templateAuthors := templateInfo.Authors.ToSlice() - templateSeverity := templateInfo.SeverityHolder.Severity - - match, err := tagFilter.Match(templateTags, templateAuthors, templateSeverity, extraTags, templateType, templateId) +func isTemplateInfoMetadataMatch(tagFilter *filter.TagFilter, template *templates.Template, extraTags []string) (bool, error) { + match, err := tagFilter.Match(template, extraTags) if err == filter.ErrExcluded { return false, filter.ErrExcluded diff --git a/v2/pkg/parsers/parser_test.go b/v2/pkg/parsers/parser_test.go index 9eb66016e..a16e00ddf 100644 --- a/v2/pkg/parsers/parser_test.go +++ b/v2/pkg/parsers/parser_test.go @@ -57,7 +57,8 @@ func TestLoadTemplate(t *testing.T) { t.Run(tc.name, func(t *testing.T) { parsedTemplatesCache.Store(tc.name, tc.template, tc.templateErr) - tagFilter := filter.New(&filter.Config{}) + tagFilter, err := filter.New(&filter.Config{}) + require.Nil(t, err) success, err := LoadTemplate(tc.name, tagFilter, nil, catalog) if tc.expectedErr == nil { require.NoError(t, err) @@ -98,7 +99,8 @@ func TestLoadTemplate(t *testing.T) { } parsedTemplatesCache.Store(name, template, nil) - tagFilter := filter.New(&filter.Config{}) + tagFilter, err := filter.New(&filter.Config{}) + require.Nil(t, err) success, err := LoadTemplate(name, tagFilter, nil, catalog) if tc.success { require.NoError(t, err) diff --git a/v2/pkg/parsers/workflow_loader.go b/v2/pkg/parsers/workflow_loader.go index 70669e9ba..505b4b774 100644 --- a/v2/pkg/parsers/workflow_loader.go +++ b/v2/pkg/parsers/workflow_loader.go @@ -15,19 +15,24 @@ type workflowLoader struct { // NewLoader returns a new workflow loader structure func NewLoader(options *protocols.ExecuterOptions) (model.WorkflowLoader, error) { - tagFilter := filter.New(&filter.Config{ - Tags: options.Options.Tags, - ExcludeTags: options.Options.ExcludeTags, - Authors: options.Options.Authors, - Severities: options.Options.Severities, - IncludeTags: options.Options.IncludeTags, - IncludeIds: options.Options.IncludeIds, - ExcludeIds: options.Options.ExcludeIds, + tagFilter, err := filter.New(&filter.Config{ + Tags: options.Options.Tags, + ExcludeTags: options.Options.ExcludeTags, + Authors: options.Options.Authors, + Severities: options.Options.Severities, + IncludeTags: options.Options.IncludeTags, + IncludeIds: options.Options.IncludeIds, + ExcludeIds: options.Options.ExcludeIds, + IncludeConditions: options.Options.IncludeConditions, }) + if err != nil { + return nil, err + } pathFilter := filter.NewPathFilter(&filter.PathFilterConfig{ IncludedTemplates: options.Options.IncludeTemplates, ExcludedTemplates: options.Options.ExcludedTemplates, }, options.Catalog) + return &workflowLoader{pathFilter: pathFilter, tagFilter: tagFilter, options: options}, nil } diff --git a/v2/pkg/projectfile/httputil.go b/v2/pkg/projectfile/httputil.go index 6be0b40db..3f00b8e81 100644 --- a/v2/pkg/projectfile/httputil.go +++ b/v2/pkg/projectfile/httputil.go @@ -6,7 +6,6 @@ import ( "encoding/gob" "encoding/hex" "io" - "io/ioutil" "net/http" ) @@ -105,6 +104,6 @@ func fromInternalResponse(intResp *InternalResponse) *http.Response { StatusCode: intResp.StatusCode, Header: intResp.Headers, ContentLength: contentLength, - Body: ioutil.NopCloser(bytes.NewReader(intResp.Body)), + Body: io.NopCloser(bytes.NewReader(intResp.Body)), } } diff --git a/v2/pkg/protocols/common/automaticscan/automaticscan.go b/v2/pkg/protocols/common/automaticscan/automaticscan.go index 7821784bc..0e6f1137b 100644 --- a/v2/pkg/protocols/common/automaticscan/automaticscan.go +++ b/v2/pkg/protocols/common/automaticscan/automaticscan.go @@ -2,7 +2,6 @@ package automaticscan import ( "io" - "io/ioutil" "net/http" "os" "path/filepath" @@ -19,6 +18,7 @@ import ( "github.com/projectdiscovery/nuclei/v2/pkg/templates" "github.com/projectdiscovery/nuclei/v2/pkg/templates/types" "github.com/projectdiscovery/retryablehttp-go" + "github.com/projectdiscovery/sliceutil" wappalyzer "github.com/projectdiscovery/wappalyzergo" "gopkg.in/yaml.v2" ) @@ -159,7 +159,7 @@ func (s *Service) processWappalyzerInputPair(input string) { return } reader := io.LimitReader(resp.Body, maxDefaultBody) - data, err := ioutil.ReadAll(reader) + data, err := io.ReadAll(reader) if err != nil { resp.Body.Close() return @@ -196,7 +196,7 @@ func (s *Service) processWappalyzerInputPair(input string) { if len(items) == 0 { return } - uniqueTags := uniqueSlice(items) + uniqueTags := sliceutil.Dedupe(items) templatesList := s.store.LoadTemplatesWithTags(s.allTemplates, uniqueTags) gologger.Info().Msgf("Executing tags (%v) for host %s (%d templates)", strings.Join(uniqueTags, ","), input, len(templatesList)) @@ -221,17 +221,3 @@ func normalizeAppName(appName string) string { } return strings.ToLower(appName) } - -func uniqueSlice(slice []string) []string { - data := make(map[string]struct{}, len(slice)) - for _, item := range slice { - if _, ok := data[item]; !ok { - data[item] = struct{}{} - } - } - finalSlice := make([]string, 0, len(data)) - for item := range data { - finalSlice = append(finalSlice, item) - } - return finalSlice -} diff --git a/v2/pkg/protocols/file/find_test.go b/v2/pkg/protocols/file/find_test.go index 5f8ee41c6..f14cd7334 100644 --- a/v2/pkg/protocols/file/find_test.go +++ b/v2/pkg/protocols/file/find_test.go @@ -1,7 +1,6 @@ package file import ( - "io/ioutil" "os" "path/filepath" "testing" @@ -32,7 +31,7 @@ func TestFindInputPaths(t *testing.T) { err := request.Compile(executerOpts) require.Nil(t, err, "could not compile file request") - tempDir, err := ioutil.TempDir("", "test-*") + tempDir, err := os.MkdirTemp("", "test-*") require.Nil(t, err, "could not create temporary directory") defer os.RemoveAll(tempDir) @@ -44,7 +43,7 @@ func TestFindInputPaths(t *testing.T) { "test.js": "TEST", } for k, v := range files { - err = ioutil.WriteFile(filepath.Join(tempDir, k), []byte(v), os.ModePerm) + err = os.WriteFile(filepath.Join(tempDir, k), []byte(v), os.ModePerm) require.Nil(t, err, "could not write temporary file") } expected := []string{"config.yaml", "final.yaml", "test.js"} diff --git a/v2/pkg/protocols/file/request_test.go b/v2/pkg/protocols/file/request_test.go index 268f9c509..74aea078f 100644 --- a/v2/pkg/protocols/file/request_test.go +++ b/v2/pkg/protocols/file/request_test.go @@ -1,7 +1,6 @@ package file import ( - "io/ioutil" "os" "path/filepath" "testing" @@ -50,7 +49,7 @@ func TestFileExecuteWithResults(t *testing.T) { err := request.Compile(executerOpts) require.Nil(t, err, "could not compile file request") - tempDir, err := ioutil.TempDir("", "test-*") + tempDir, err := os.MkdirTemp("", "test-*") require.Nil(t, err, "could not create temporary directory") defer os.RemoveAll(tempDir) @@ -58,7 +57,7 @@ func TestFileExecuteWithResults(t *testing.T) { "config.yaml": "TEST\r\n1.1.1.1\r\n", } for k, v := range files { - err = ioutil.WriteFile(filepath.Join(tempDir, k), []byte(v), os.ModePerm) + err = os.WriteFile(filepath.Join(tempDir, k), []byte(v), os.ModePerm) require.Nil(t, err, "could not write temporary file") } diff --git a/v2/pkg/protocols/headless/engine/engine.go b/v2/pkg/protocols/headless/engine/engine.go index 9f40ab751..695fb4bfd 100644 --- a/v2/pkg/protocols/headless/engine/engine.go +++ b/v2/pkg/protocols/headless/engine/engine.go @@ -2,7 +2,6 @@ package engine import ( "fmt" - "io/ioutil" "net/http" "os" "runtime" @@ -30,7 +29,7 @@ type Browser struct { // New creates a new nuclei headless browser module func New(options *types.Options) (*Browser, error) { - dataStore, err := ioutil.TempDir("", "nuclei-*") + dataStore, err := os.MkdirTemp("", "nuclei-*") if err != nil { return nil, errors.Wrap(err, "could not create temporary directory") } diff --git a/v2/pkg/protocols/headless/engine/page_actions.go b/v2/pkg/protocols/headless/engine/page_actions.go index 5b2b60ed2..cfb2910bb 100644 --- a/v2/pkg/protocols/headless/engine/page_actions.go +++ b/v2/pkg/protocols/headless/engine/page_actions.go @@ -2,9 +2,9 @@ package engine import ( "context" - "io/ioutil" "net" "net/url" + "os" "regexp" "strconv" "strings" @@ -348,7 +348,7 @@ func (p *Page) Screenshot(act *Action, out map[string]string) error { if err != nil { return errors.Wrap(err, "could not take screenshot") } - err = ioutil.WriteFile(to+".png", data, 0540) + err = os.WriteFile(to+".png", data, 0540) if err != nil { return errors.Wrap(err, "could not write screenshot") } diff --git a/v2/pkg/protocols/http/build_request.go b/v2/pkg/protocols/http/build_request.go index 64471f90c..43df543d0 100644 --- a/v2/pkg/protocols/http/build_request.go +++ b/v2/pkg/protocols/http/build_request.go @@ -5,7 +5,6 @@ import ( "context" "fmt" "io" - "io/ioutil" "net" "net/http" "net/url" @@ -273,7 +272,7 @@ func (r *requestGenerator) handleRawWithPayloads(ctx context.Context, rawRequest // retryablehttp var body io.ReadCloser - body = ioutil.NopCloser(strings.NewReader(rawRequestData.Data)) + body = io.NopCloser(strings.NewReader(rawRequestData.Data)) if r.request.Race { // More or less this ensures that all requests hit the endpoint at the same approximated time // Todo: sync internally upon writing latest request byte @@ -337,7 +336,7 @@ func (r *requestGenerator) fillRequest(req *http.Request, values map[string]inte if err != nil { return nil, errors.Wrap(err, evaluateHelperExpressionErrorMessage) } - req.Body = ioutil.NopCloser(strings.NewReader(body)) + req.Body = io.NopCloser(strings.NewReader(body)) } if !r.request.Unsafe { setHeader(req, "User-Agent", uarand.GetRandom()) diff --git a/v2/pkg/protocols/http/request.go b/v2/pkg/protocols/http/request.go index 235516a6e..2cf4a1b4c 100644 --- a/v2/pkg/protocols/http/request.go +++ b/v2/pkg/protocols/http/request.go @@ -6,7 +6,6 @@ import ( "encoding/hex" "fmt" "io" - "io/ioutil" "net/http" "net/http/httputil" "net/url" @@ -403,7 +402,7 @@ func (request *Request) executeRequest(reqURL string, generatedRequest *generate if parsed, parseErr := url.Parse(formedURL); parseErr == nil { hostname = parsed.Host } - resp, err = generatedRequest.pipelinedClient.DoRaw(generatedRequest.rawRequest.Method, reqURL, generatedRequest.rawRequest.Path, generators.ExpandMapValues(generatedRequest.rawRequest.Headers), ioutil.NopCloser(strings.NewReader(generatedRequest.rawRequest.Data))) + resp, err = generatedRequest.pipelinedClient.DoRaw(generatedRequest.rawRequest.Method, reqURL, generatedRequest.rawRequest.Path, generators.ExpandMapValues(generatedRequest.rawRequest.Headers), io.NopCloser(strings.NewReader(generatedRequest.rawRequest.Data))) } else if generatedRequest.request != nil { resp, err = generatedRequest.pipelinedClient.Dor(generatedRequest.request) } @@ -421,7 +420,7 @@ func (request *Request) executeRequest(reqURL string, generatedRequest *generate options.CustomRawBytes = generatedRequest.rawRequest.UnsafeRawBytes options.ForceReadAllBody = request.ForceReadAllBody options.SNI = request.options.Options.SNI - resp, err = generatedRequest.original.rawhttpClient.DoRawWithOptions(generatedRequest.rawRequest.Method, reqURL, generatedRequest.rawRequest.Path, generators.ExpandMapValues(generatedRequest.rawRequest.Headers), ioutil.NopCloser(strings.NewReader(generatedRequest.rawRequest.Data)), &options) + resp, err = generatedRequest.original.rawhttpClient.DoRawWithOptions(generatedRequest.rawRequest.Method, reqURL, generatedRequest.rawRequest.Path, generators.ExpandMapValues(generatedRequest.rawRequest.Headers), io.NopCloser(strings.NewReader(generatedRequest.rawRequest.Data)), &options) } else { hostname = generatedRequest.request.URL.Host formedURL = generatedRequest.request.URL.String() @@ -469,7 +468,7 @@ func (request *Request) executeRequest(reqURL string, generatedRequest *generate if err != nil { // rawhttp doesn't support draining response bodies. if resp != nil && resp.Body != nil && generatedRequest.rawRequest == nil && !generatedRequest.original.Pipeline { - _, _ = io.CopyN(ioutil.Discard, resp.Body, drainReqSize) + _, _ = io.CopyN(io.Discard, resp.Body, drainReqSize) resp.Body.Close() } request.options.Output.Request(request.options.TemplatePath, formedURL, request.Type().String(), err) @@ -494,7 +493,7 @@ func (request *Request) executeRequest(reqURL string, generatedRequest *generate } defer func() { if resp.StatusCode != http.StatusSwitchingProtocols { - _, _ = io.CopyN(ioutil.Discard, resp.Body, drainReqSize) + _, _ = io.CopyN(io.Discard, resp.Body, drainReqSize) } resp.Body.Close() }() @@ -502,7 +501,7 @@ func (request *Request) executeRequest(reqURL string, generatedRequest *generate var curlCommand string if !request.Unsafe && resp != nil && generatedRequest.request != nil && resp.Request != nil && !request.Race { bodyBytes, _ := generatedRequest.request.BodyBytes() - resp.Request.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes)) + resp.Request.Body = io.NopCloser(bytes.NewReader(bodyBytes)) command, _ := http2curl.GetCurlCommand(resp.Request) if err == nil && command != nil { curlCommand = command.String() diff --git a/v2/pkg/protocols/http/utils.go b/v2/pkg/protocols/http/utils.go index 1fd93bcbf..ef085858b 100644 --- a/v2/pkg/protocols/http/utils.go +++ b/v2/pkg/protocols/http/utils.go @@ -6,7 +6,6 @@ import ( "compress/zlib" "context" "io" - "io/ioutil" "net/http" "net/http/httputil" "strings" @@ -124,7 +123,7 @@ func dump(req *generatedRequest, reqURL string) ([]byte, error) { if len(bodyBytes) > 0 { dumpBody = true cloned.ContentLength = int64(len(bodyBytes)) - cloned.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes)) + cloned.Body = io.NopCloser(bytes.NewReader(bodyBytes)) } else { cloned.ContentLength = 0 cloned.Body = nil @@ -138,7 +137,7 @@ func dump(req *generatedRequest, reqURL string) ([]byte, error) { return dumpBytes, nil } rawHttpOptions := &rawhttp.Options{CustomHeaders: req.rawRequest.UnsafeHeaders, CustomRawBytes: req.rawRequest.UnsafeRawBytes} - return rawhttp.DumpRequestRaw(req.rawRequest.Method, reqURL, req.rawRequest.Path, generators.ExpandMapValues(req.rawRequest.Headers), ioutil.NopCloser(strings.NewReader(req.rawRequest.Data)), rawHttpOptions) + return rawhttp.DumpRequestRaw(req.rawRequest.Method, reqURL, req.rawRequest.Path, generators.ExpandMapValues(req.rawRequest.Headers), io.NopCloser(strings.NewReader(req.rawRequest.Data)), rawHttpOptions) } // handleDecompression if the user specified a custom encoding (as golang transport doesn't do this automatically) diff --git a/v2/pkg/protocols/offlinehttp/find_test.go b/v2/pkg/protocols/offlinehttp/find_test.go index 367d00096..333e3b218 100644 --- a/v2/pkg/protocols/offlinehttp/find_test.go +++ b/v2/pkg/protocols/offlinehttp/find_test.go @@ -1,7 +1,6 @@ package offlinehttp import ( - "io/ioutil" "os" "path/filepath" "testing" @@ -28,7 +27,7 @@ func TestFindResponses(t *testing.T) { err := request.Compile(executerOpts) require.Nil(t, err, "could not compile file request") - tempDir, err := ioutil.TempDir("", "test-*") + tempDir, err := os.MkdirTemp("", "test-*") require.Nil(t, err, "could not create temporary directory") defer os.RemoveAll(tempDir) @@ -40,7 +39,7 @@ func TestFindResponses(t *testing.T) { "test.txt": "TEST", } for k, v := range files { - err = ioutil.WriteFile(filepath.Join(tempDir, k), []byte(v), os.ModePerm) + err = os.WriteFile(filepath.Join(tempDir, k), []byte(v), os.ModePerm) require.Nil(t, err, "could not write temporary file") } expected := []string{"config.txt", "final.txt", "test.txt"} diff --git a/v2/pkg/reporting/dedupe/dedupe.go b/v2/pkg/reporting/dedupe/dedupe.go index 7a688581d..c56e2205b 100644 --- a/v2/pkg/reporting/dedupe/dedupe.go +++ b/v2/pkg/reporting/dedupe/dedupe.go @@ -6,7 +6,6 @@ package dedupe import ( "crypto/sha1" - "io/ioutil" "os" "reflect" "unsafe" @@ -30,7 +29,7 @@ func New(dbPath string) (*Storage, error) { var err error if dbPath == "" { - dbPath, err = ioutil.TempDir("", "nuclei-report-*") + dbPath, err = os.MkdirTemp("", "nuclei-report-*") storage.temporary = dbPath } if err != nil { diff --git a/v2/pkg/reporting/dedupe/dedupe_test.go b/v2/pkg/reporting/dedupe/dedupe_test.go index eb5014425..db9ef670b 100644 --- a/v2/pkg/reporting/dedupe/dedupe_test.go +++ b/v2/pkg/reporting/dedupe/dedupe_test.go @@ -1,7 +1,6 @@ package dedupe import ( - "io/ioutil" "os" "testing" @@ -11,7 +10,7 @@ import ( ) func TestDedupeDuplicates(t *testing.T) { - tempDir, err := ioutil.TempDir("", "nuclei") + tempDir, err := os.MkdirTemp("", "nuclei") require.Nil(t, err, "could not create temporary storage") defer os.RemoveAll(tempDir) diff --git a/v2/pkg/reporting/exporters/es/elasticsearch.go b/v2/pkg/reporting/exporters/es/elasticsearch.go index 545ab9487..de1663abf 100644 --- a/v2/pkg/reporting/exporters/es/elasticsearch.go +++ b/v2/pkg/reporting/exporters/es/elasticsearch.go @@ -7,7 +7,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "net/http" "time" @@ -111,7 +110,7 @@ func (exporter *Exporter) Export(event *output.ResultEvent) error { if err != nil { return err } - req.Body = ioutil.NopCloser(bytes.NewReader(b)) + req.Body = io.NopCloser(bytes.NewReader(b)) res, err := exporter.elasticsearch.Do(req) if err != nil { diff --git a/v2/pkg/reporting/exporters/markdown/markdown.go b/v2/pkg/reporting/exporters/markdown/markdown.go index 3540d60bb..d19956d67 100644 --- a/v2/pkg/reporting/exporters/markdown/markdown.go +++ b/v2/pkg/reporting/exporters/markdown/markdown.go @@ -2,7 +2,6 @@ package markdown import ( "bytes" - "io/ioutil" "os" "path/filepath" "strings" @@ -67,7 +66,7 @@ func (exporter *Exporter) Export(event *output.ResultEvent) error { dataBuilder.WriteString(description) data := dataBuilder.Bytes() - return ioutil.WriteFile(filepath.Join(exporter.directory, finalFilename), data, 0644) + return os.WriteFile(filepath.Join(exporter.directory, finalFilename), data, 0644) } // Close closes the exporter after operation diff --git a/v2/pkg/types/types.go b/v2/pkg/types/types.go index bddbb84b4..e9938863c 100644 --- a/v2/pkg/types/types.go +++ b/v2/pkg/types/types.go @@ -236,6 +236,8 @@ type Options struct { InputReadTimeout time.Duration // Disable stdin for input processing DisableStdin bool + // IncludeConditions is the list of conditions templates should match + IncludeConditions goflags.FileStringSlice // Custom Config Directory CustomConfigDir string } diff --git a/v2/pkg/utils/monitor/monitor.go b/v2/pkg/utils/monitor/monitor.go index 84196afe6..a74cb264e 100644 --- a/v2/pkg/utils/monitor/monitor.go +++ b/v2/pkg/utils/monitor/monitor.go @@ -7,7 +7,6 @@ import ( "bytes" "context" "fmt" - "io/ioutil" "os" "runtime" "strings" @@ -80,7 +79,7 @@ func (s *Agent) monitorWorker() { s.cancel() stackTraceFile := fmt.Sprintf("nuclei-stacktrace-%s.dump", xid.New().String()) gologger.Error().Msgf("Detected hanging goroutine (count=%d/%d) = %s\n", current, s.goroutineCount, stackTraceFile) - if err := ioutil.WriteFile(stackTraceFile, currentStack, os.ModePerm); err != nil { + if err := os.WriteFile(stackTraceFile, currentStack, os.ModePerm); err != nil { gologger.Error().Msgf("Could not write stack trace for goroutines: %s\n", err) } os.Exit(1) // exit forcefully if we've been stuck From 011da1388d34c1f76cce9e03cbb057b53eb7ef61 Mon Sep 17 00:00:00 2001 From: Sajad Date: Thu, 25 Aug 2022 17:42:35 +0530 Subject: [PATCH 23/25] add option to specify network interface (#2384) * add option to specify network interface * add source-ip flag * fix typo * fix err return * readme update Co-authored-by: Sandeep Singh --- README.md | 3 + v2/cmd/nuclei/main.go | 2 + .../protocols/common/protocolstate/state.go | 97 +++++++++++++++++++ v2/pkg/types/types.go | 4 + 4 files changed, 106 insertions(+) diff --git a/README.md b/README.md index d17009052..161b82e26 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ OUTPUT: CONFIGURATIONS: -config string path to the nuclei configuration file + -config-directory string override the default config path ($home/.config) -fr, -follow-redirects enable following redirects for http templates -mr, -max-redirects int max number of redirects to follow for http templates (default 10) -dr, -disable-redirects disable redirects for http templates @@ -158,6 +159,8 @@ CONFIGURATIONS: -sml, -show-match-line show match lines for file templates, works with extractors only -ztls use ztls library with autofallback to standard one for tls13 -sni string tls sni hostname to use (default: input domain name) + -i, -interface string network interface to use for network scan + -sip, -source-ip string source ip address to use for network scan INTERACTSH: -iserver, -interactsh-server string interactsh server url for self-hosted instance (default: oast.pro,oast.live,oast.site,oast.online,oast.fun,oast.me) diff --git a/v2/cmd/nuclei/main.go b/v2/cmd/nuclei/main.go index f066ffa14..72dc98108 100644 --- a/v2/cmd/nuclei/main.go +++ b/v2/cmd/nuclei/main.go @@ -183,6 +183,8 @@ on extensive configurability, massive extensibility and ease of use.`) flagSet.BoolVarP(&options.ShowMatchLine, "show-match-line", "sml", false, "show match lines for file templates, works with extractors only"), flagSet.BoolVar(&options.ZTLS, "ztls", false, "use ztls library with autofallback to standard one for tls13"), flagSet.StringVar(&options.SNI, "sni", "", "tls sni hostname to use (default: input domain name)"), + flagSet.StringVarP(&options.Interface, "interface", "i", "", "network interface to use for network scan"), + flagSet.StringVarP(&options.SourceIP, "source-ip", "sip", "", "source ip address to use for network scan"), flagSet.StringVar(&options.CustomConfigDir, "config-directory", "", "Override the default config path ($home/.config)"), ) diff --git a/v2/pkg/protocols/common/protocolstate/state.go b/v2/pkg/protocols/common/protocolstate/state.go index a08cb7076..e422c0d13 100644 --- a/v2/pkg/protocols/common/protocolstate/state.go +++ b/v2/pkg/protocols/common/protocolstate/state.go @@ -1,6 +1,9 @@ package protocolstate import ( + "fmt" + "net" + "github.com/pkg/errors" "github.com/projectdiscovery/fastdialer/fastdialer" @@ -16,6 +19,48 @@ func Init(options *types.Options) error { return nil } opts := fastdialer.DefaultOptions + + switch { + case options.SourceIP != "" && options.Interface != "": + isAssociated, err := isIpAssociatedWithInterface(options.SourceIP, options.Interface) + if err != nil { + return err + } + if isAssociated { + opts.Dialer = &net.Dialer{ + LocalAddr: &net.TCPAddr{ + IP: net.ParseIP(options.SourceIP), + }, + } + } else { + return fmt.Errorf("source ip (%s) is not associated with the interface (%s)", options.SourceIP, options.Interface) + } + case options.SourceIP != "": + isAssociated, err := isIpAssociatedWithInterface(options.SourceIP, "any") + if err != nil { + return err + } + if isAssociated { + opts.Dialer = &net.Dialer{ + LocalAddr: &net.TCPAddr{ + IP: net.ParseIP(options.SourceIP), + }, + } + } else { + return fmt.Errorf("source ip (%s) is not associated with any network interface", options.SourceIP) + } + case options.Interface != "": + ifadrr, err := interfaceAddress(options.Interface) + if err != nil { + return err + } + opts.Dialer = &net.Dialer{ + LocalAddr: &net.TCPAddr{ + IP: ifadrr, + }, + } + } + if options.SystemResolvers { opts.EnableFallback = true } @@ -33,6 +78,58 @@ func Init(options *types.Options) error { return nil } +// isIpAssociatedWithInterface checks if the given IP is associated with the given interface. +func isIpAssociatedWithInterface(souceIP, interfaceName string) (bool, error) { + addrs, err := interfaceAddresses(interfaceName) + if err != nil { + return false, err + } + for _, addr := range addrs { + if ipnet, ok := addr.(*net.IPNet); ok { + if ipnet.IP.String() == souceIP { + return true, nil + } + } + } + return false, nil +} + +// interfaceAddress returns the first IPv4 address of the given interface. +func interfaceAddress(interfaceName string) (net.IP, error) { + addrs, err := interfaceAddresses(interfaceName) + if err != nil { + return nil, err + } + var address net.IP + for _, addr := range addrs { + if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + if ipnet.IP.To4() != nil { + address = ipnet.IP + } + } + } + if address == nil { + return nil, fmt.Errorf("no suitable address found for interface: `%s`", interfaceName) + } + return address, nil +} + +// interfaceAddresses returns all interface addresses. +func interfaceAddresses(interfaceName string) ([]net.Addr, error) { + if interfaceName == "any" { + return net.InterfaceAddrs() + } + ief, err := net.InterfaceByName(interfaceName) + if err != nil { + return nil, errors.Wrapf(err, "failed to get interface: `%s`", interfaceName) + } + addrs, err := ief.Addrs() + if err != nil { + return nil, errors.Wrapf(err, "failed to get interface addresses for: `%s`", interfaceName) + } + return addrs, nil +} + // Close closes the global shared fastdialer func Close() { if Dialer != nil { diff --git a/v2/pkg/types/types.go b/v2/pkg/types/types.go index e9938863c..8d7107897 100644 --- a/v2/pkg/types/types.go +++ b/v2/pkg/types/types.go @@ -230,6 +230,10 @@ type Options struct { DisableRedirects bool // SNI custom hostname SNI string + // Interface to use for network scan + Interface string + // SourceIP sets custom source IP address for network requests + SourceIP string // Health Check HealthCheck bool // Time to wait between each input read operation before closing the stream From efdc57c7b26358c6b5e23d27e96b6522eb6184ee Mon Sep 17 00:00:00 2001 From: sandeep Date: Fri, 26 Aug 2022 14:18:32 +0530 Subject: [PATCH 24/25] version update --- v2/pkg/catalog/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2/pkg/catalog/config/config.go b/v2/pkg/catalog/config/config.go index 69a034020..840d15fa1 100644 --- a/v2/pkg/catalog/config/config.go +++ b/v2/pkg/catalog/config/config.go @@ -28,7 +28,7 @@ type Config struct { const nucleiConfigFilename = ".templates-config.json" // Version is the current version of nuclei -const Version = `2.7.6` +const Version = `2.7.7` var customConfigDirectory string From 8f8ab429fffa2e0e3c76172e8e7c9c489748b2ef Mon Sep 17 00:00:00 2001 From: sandeep Date: Fri, 26 Aug 2022 14:20:18 +0530 Subject: [PATCH 25/25] readme update --- README.md | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 161b82e26..bafcf3077 100644 --- a/README.md +++ b/README.md @@ -111,19 +111,20 @@ TEMPLATES: -tl list all available templates FILTERING: - -a, -author string[] templates to run based on authors (comma-separated, file) - -tags string[] templates to run based on tags (comma-separated, file) - -etags, -exclude-tags string[] templates to exclude based on tags (comma-separated, file) - -itags, -include-tags string[] tags to be executed even if they are excluded either by default or configuration - -id, -template-id string[] templates to run based on template ids (comma-separated, file) - -eid, -exclude-id string[] templates to exclude based on template ids (comma-separated, file) - -it, -include-templates string[] templates to be executed even if they are excluded either by default or configuration - -et, -exclude-templates string[] template or template directory to exclude (comma-separated, file) - -em, -exclude-matchers string[] template matchers to exclude in result - -s, -severity value[] templates to run based on severity. Possible values: info, low, medium, high, critical, unknown - -es, -exclude-severity value[] templates to exclude based on severity. Possible values: info, low, medium, high, critical, unknown - -pt, -type value[] templates to run based on protocol type. Possible values: dns, file, http, headless, network, workflow, ssl, websocket, whois - -ept, -exclude-type value[] templates to exclude based on protocol type. Possible values: dns, file, http, headless, network, workflow, ssl, websocket, whois + -a, -author string[] templates to run based on authors (comma-separated, file) + -tags string[] templates to run based on tags (comma-separated, file) + -etags, -exclude-tags string[] templates to exclude based on tags (comma-separated, file) + -itags, -include-tags string[] tags to be executed even if they are excluded either by default or configuration + -id, -template-id string[] templates to run based on template ids (comma-separated, file) + -eid, -exclude-id string[] templates to exclude based on template ids (comma-separated, file) + -it, -include-templates string[] templates to be executed even if they are excluded either by default or configuration + -et, -exclude-templates string[] template or template directory to exclude (comma-separated, file) + -em, -exclude-matchers string[] template matchers to exclude in result + -s, -severity value[] templates to run based on severity. Possible values: info, low, medium, high, critical, unknown + -es, -exclude-severity value[] templates to exclude based on severity. Possible values: info, low, medium, high, critical, unknown + -pt, -type value[] templates to run based on protocol type. Possible values: dns, file, http, headless, network, workflow, ssl, websocket, whois + -ept, -exclude-type value[] templates to exclude based on protocol type. Possible values: dns, file, http, headless, network, workflow, ssl, websocket, whois + -tc, -template-condition string[] templates to run based on expression condition OUTPUT: -o, -output string output file to write found issues/vulnerabilities @@ -142,7 +143,6 @@ OUTPUT: CONFIGURATIONS: -config string path to the nuclei configuration file - -config-directory string override the default config path ($home/.config) -fr, -follow-redirects enable following redirects for http templates -mr, -max-redirects int max number of redirects to follow for http templates (default 10) -dr, -disable-redirects disable redirects for http templates @@ -161,6 +161,7 @@ CONFIGURATIONS: -sni string tls sni hostname to use (default: input domain name) -i, -interface string network interface to use for network scan -sip, -source-ip string source ip address to use for network scan + -config-directory string Override the default config path ($home/.config) INTERACTSH: -iserver, -interactsh-server string interactsh server url for self-hosted instance (default: oast.pro,oast.live,oast.site,oast.online,oast.fun,oast.me) @@ -192,10 +193,10 @@ OPTIMIZATIONS: -no-stdin Disable Stdin processing HEADLESS: - -headless enable templates that require headless browser support (root user on linux will disable sandbox) - -page-timeout int seconds to wait for each page in headless mode (default 20) - -sb, -show-browser show the browser on the screen when running templates with headless mode - -sc, -system-chrome Use local installed chrome browser instead of nuclei installed + -headless enable templates that require headless browser support (root user on linux will disable sandbox) + -page-timeout int seconds to wait for each page in headless mode (default 20) + -sb, -show-browser show the browser on the screen when running templates with headless mode + -sc, -system-chrome Use local installed chrome browser instead of nuclei installed -lha, -list-headless-action list available headless actions DEBUG: @@ -209,6 +210,7 @@ DEBUG: -version show nuclei version -hm, -hang-monitor enable nuclei hang monitoring -v, -verbose show verbose output + -profile-mem string optional nuclei memory profile dump file -vv display templates loaded for scan -ep, -enable-pprof enable pprof debugging server -tv, -templates-version shows the version of the installed nuclei-templates