mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2026-11-10 12:11:33 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f491b8c6c | ||
|
|
86ced85a02 | ||
|
|
29b99fecac | ||
|
|
893aff46f6 | ||
|
|
363fb34520 | ||
|
|
9521daf3aa | ||
|
|
bfa2dacb7b | ||
|
|
6794cbf4b6 | ||
|
|
2bd2286115 | ||
|
|
fbc5b31341 | ||
|
|
6b14e9071c | ||
|
|
f6a480f0b4 | ||
|
|
c6e7847c4e | ||
|
|
39d57ea509 | ||
|
|
709420dbf6 | ||
|
|
15b1a490fc | ||
|
|
648d94da56 | ||
|
|
bbac3dbd2e | ||
|
|
4d752b4811 | ||
|
|
266e3320ca | ||
|
|
35402d6414 | ||
|
|
4a96f95086 | ||
|
|
35bdde6be6 | ||
|
|
788465fc36 | ||
|
|
bafa8661c9 | ||
|
|
15111ec4ca | ||
|
|
fffc1d1058 | ||
|
|
657713eb8f | ||
|
|
000e3a305e | ||
|
|
d8bb580618 | ||
|
|
f6fc4e58e7 | ||
|
|
1d42dc4e64 | ||
|
|
a3029142f6 | ||
|
|
754a8555f3 | ||
|
|
31094ab299 |
No files matched your search
+2
-1
@@ -1,6 +1,7 @@
|
||||
FROM golang:1.16.0-alpine as build-env
|
||||
FROM golang:1.15-alpine as build-env
|
||||
RUN GO111MODULE=on go get -v github.com/projectdiscovery/nuclei/v2/cmd/nuclei
|
||||
|
||||
FROM alpine:latest
|
||||
RUN apk add --no-cache bind-tools ca-certificates
|
||||
COPY --from=build-env /go/bin/nuclei /usr/local/bin/nuclei
|
||||
ENTRYPOINT ["nuclei"]
|
||||
@@ -12,6 +12,7 @@
|
||||
<a href="https://github.com/projectdiscovery/nuclei/releases"><img src="https://img.shields.io/github/release/projectdiscovery/nuclei"></a>
|
||||
<a href="https://twitter.com/pdnuclei"><img src="https://img.shields.io/twitter/follow/pdnuclei.svg?logo=twitter"></a>
|
||||
<a href="https://discord.gg/KECAGdH"><img src="https://img.shields.io/discord/695645237418131507.svg?logo=discord"></a>
|
||||
<a href="https://github.com/projectdiscovery/nuclei/actions/workflows/build.yaml"><img src="https://github.com/projectdiscovery/nuclei/actions/workflows/build.yaml/badge.svg?branch=master"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#allow-list:
|
||||
# severity: high,critical
|
||||
#deny-list:
|
||||
# severity: low
|
||||
|
||||
# github contains configuration options for github issue tracker
|
||||
#github:
|
||||
# # base-url is the optional self-hosted github application url
|
||||
# base-url: ""
|
||||
# # username is the username of the github user
|
||||
# username: ""
|
||||
# # owner is the owner name of the repository for issues.
|
||||
# owner: ""
|
||||
# # token is the token for github account.
|
||||
# token: ""
|
||||
# # project-name is the name of the repository.
|
||||
# project-name: ""
|
||||
# # issue-label is the label of the created issue type
|
||||
# issue-label: ""
|
||||
|
||||
# gitlab contains configuration options for gitlab issue tracker
|
||||
#gitlab:
|
||||
# # base-url is the optional self-hosted gitlab application url
|
||||
# base-url: ""
|
||||
# # username is the username of the gitlab user
|
||||
# username: ""
|
||||
# # token is the token for gitlab account.
|
||||
# token: ""
|
||||
# # project-id is the ID of the repository.
|
||||
# project-id: ""
|
||||
# # issue-label is the label of the created issue type
|
||||
# issue-label: ""
|
||||
|
||||
# jira contains configuration options for jira issue tracker
|
||||
#jira:
|
||||
# # URL is the jira application url
|
||||
# url: ""
|
||||
# # account-id is the account-id of the jira user
|
||||
# account-id: ""
|
||||
# # email is the email of the user for jira instance
|
||||
# email: ""
|
||||
# # token is the token for jira instance.
|
||||
# token: ""
|
||||
# # project-name is the name of the project.
|
||||
# project-name: ""
|
||||
# # issue-type is the name of the created issue type
|
||||
# issue-type: ""
|
||||
@@ -24,9 +24,7 @@ func main() {
|
||||
if err != nil {
|
||||
gologger.Fatal().Msgf("Could not create runner: %s\n", err)
|
||||
}
|
||||
|
||||
nucleiRunner.RunEnumeration()
|
||||
nucleiRunner.Close()
|
||||
}
|
||||
|
||||
func readConfig() {
|
||||
@@ -77,8 +75,9 @@ based on templates offering massive extensibility and ease of use.`)
|
||||
set.BoolVar(&options.OfflineHTTP, "passive", false, "Enable Passive HTTP response processing mode")
|
||||
set.StringVarP(&options.BurpCollaboratorBiid, "burp-collaborator-biid", "biid", "", "Burp Collaborator BIID")
|
||||
set.StringVarP(&options.ReportingConfig, "report-config", "rc", "", "Nuclei Reporting Module configuration file")
|
||||
set.StringVarP(&options.ReportingDB, "report-db", "rdb", "", "Local Nuclei Reporting Database")
|
||||
set.StringVarP(&options.ReportingDB, "report-db", "rdb", "", "Local Nuclei Reporting Database (Always use this to persistent report data)")
|
||||
set.StringSliceVar(&options.Tags, "tags", []string{}, "Tags to execute templates for")
|
||||
set.StringSliceVarP(&options.ExcludeTags, "exclude-tags", "etags", []string{}, "Exclude templates with the provided tags")
|
||||
set.StringVarP(&options.ResolversFile, "resolvers", "r", "", "File containing resolver list for nuclei")
|
||||
set.BoolVar(&options.Headless, "headless", false, "Enable headless browser based templates support")
|
||||
set.BoolVar(&options.ShowBrowser, "show-browser", false, "Show the browser on the screen")
|
||||
@@ -86,6 +85,7 @@ based on templates offering massive extensibility and ease of use.`)
|
||||
set.BoolVar(&options.SystemResolvers, "system-resolvers", false, "Use system dns resolving as error fallback")
|
||||
set.IntVar(&options.PageTimeout, "page-timeout", 20, "Seconds to wait for each page in headless")
|
||||
set.BoolVarP(&options.NewTemplates, "new-templates", "nt", false, "Only run newly added templates")
|
||||
set.StringVarP(&options.DiskExportDirectory, "disk-export", "de", "", "Directory on disk to export reports in markdown to")
|
||||
_ = set.Parse()
|
||||
|
||||
if cfgFile != "" {
|
||||
|
||||
@@ -23,14 +23,14 @@ require (
|
||||
github.com/mitchellh/go-ps v1.0.0
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/projectdiscovery/clistats v0.0.7
|
||||
github.com/projectdiscovery/clistats v0.0.8
|
||||
github.com/projectdiscovery/collaborator v0.0.2
|
||||
github.com/projectdiscovery/fastdialer v0.0.8
|
||||
github.com/projectdiscovery/goflags v0.0.3
|
||||
github.com/projectdiscovery/goflags v0.0.4
|
||||
github.com/projectdiscovery/gologger v1.1.4
|
||||
github.com/projectdiscovery/hmap v0.0.1
|
||||
github.com/projectdiscovery/rawhttp v0.0.6
|
||||
github.com/projectdiscovery/retryabledns v1.0.7
|
||||
github.com/projectdiscovery/retryabledns v1.0.10
|
||||
github.com/projectdiscovery/retryablehttp-go v1.0.1
|
||||
github.com/remeh/sizedwaitgroup v1.0.0
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
|
||||
@@ -101,7 +101,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw=
|
||||
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
@@ -158,12 +157,10 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8
|
||||
github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw=
|
||||
github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
@@ -198,22 +195,23 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/projectdiscovery/clistats v0.0.7 h1:Q/erjrk2p3BIQq1RaHVtBpgboghNz0u1/lyQ2fr8Cn0=
|
||||
github.com/projectdiscovery/clistats v0.0.7/go.mod h1:lV6jUHAv2bYWqrQstqW8iVIydKJhWlVaLl3Xo9ioVGg=
|
||||
github.com/projectdiscovery/clistats v0.0.8 h1:tjmWb15mqsPf/yrQXVHLe2ThZX/5+mgKSfZBKWWLh20=
|
||||
github.com/projectdiscovery/clistats v0.0.8/go.mod h1:lV6jUHAv2bYWqrQstqW8iVIydKJhWlVaLl3Xo9ioVGg=
|
||||
github.com/projectdiscovery/collaborator v0.0.2 h1:BSiMlWM3NvuKbpedn6fIjjEo5b7q5zmiJ6tI7+6mB3s=
|
||||
github.com/projectdiscovery/collaborator v0.0.2/go.mod h1:J1z0fC7Svutz3LJqoRyTHA3F0Suh4livmkYv8MnKw20=
|
||||
github.com/projectdiscovery/fastdialer v0.0.8 h1:mEMc8bfXV5hc1PUEkJiUnR5imYQe6+839Zezd5jLkc0=
|
||||
github.com/projectdiscovery/fastdialer v0.0.8/go.mod h1:AuaV0dzrNeBLHqjNnzpFSnTXnHGIZAlGQE+WUMmSIW4=
|
||||
github.com/projectdiscovery/goflags v0.0.3 h1:5s9qblVIP/dQt7Mr3PMvZLvekEyioOS5oZtZ6ncLQHA=
|
||||
github.com/projectdiscovery/goflags v0.0.3/go.mod h1:Ae1mJ5MIIqjys0lFe3GiMZ10Z8VLaxkYJ1ySA4Zv8HA=
|
||||
github.com/projectdiscovery/goflags v0.0.4 h1:fWKLMAr3KmPlZxE1b54pfei+vGIUJn9q6aM7woZIbCY=
|
||||
github.com/projectdiscovery/goflags v0.0.4/go.mod h1:Ae1mJ5MIIqjys0lFe3GiMZ10Z8VLaxkYJ1ySA4Zv8HA=
|
||||
github.com/projectdiscovery/gologger v1.1.4 h1:qWxGUq7ukHWT849uGPkagPKF3yBPYAsTtMKunQ8O2VI=
|
||||
github.com/projectdiscovery/gologger v1.1.4/go.mod h1:Bhb6Bdx2PV1nMaFLoXNBmHIU85iROS9y1tBuv7T5pMY=
|
||||
github.com/projectdiscovery/hmap v0.0.1 h1:VAONbJw5jP+syI5smhsfkrq9XPGn4aiYy5pR6KR1wog=
|
||||
github.com/projectdiscovery/hmap v0.0.1/go.mod h1:VDEfgzkKQdq7iGTKz8Ooul0NuYHQ8qiDs6r8bPD1Sb0=
|
||||
github.com/projectdiscovery/rawhttp v0.0.6 h1:HbgPB1eKXQVV5F9sq0Uxflm95spWFyZYD8dgFpeOC9M=
|
||||
github.com/projectdiscovery/rawhttp v0.0.6/go.mod h1:PQERZAhAv7yxI/hR6hdDPgK1WTU56l204BweXrBec+0=
|
||||
github.com/projectdiscovery/retryabledns v1.0.7 h1:fwbwrl+0XmWRvwiNzm/YSBjmUh7KwIsryScUK9juOuA=
|
||||
github.com/projectdiscovery/retryabledns v1.0.7/go.mod h1:/UzJn4I+cPdQl6pKiiQfvVAT636YZvJQYZhYhGB0dUQ=
|
||||
github.com/projectdiscovery/retryabledns v1.0.10 h1:xJZ2aKoqrNg/OZEw1+4+QIOH40V/WkZDYY1ZZc+uphE=
|
||||
github.com/projectdiscovery/retryabledns v1.0.10/go.mod h1:4sMC8HZyF01HXukRleSQYwz4870bwgb4+hTSXTMrkf4=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.0.1 h1:V7wUvsZNq1Rcz7+IlcyoyQlNwshuwptuBVYWw9lx8RE=
|
||||
github.com/projectdiscovery/retryablehttp-go v1.0.1/go.mod h1:SrN6iLZilNG1X4neq1D+SBxoqfAF4nyzvmevkTkWsek=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
@@ -532,7 +530,6 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
@@ -545,7 +542,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -7,11 +7,11 @@ const banner = `
|
||||
____ __ _______/ /__ (_)
|
||||
/ __ \/ / / / ___/ / _ \/ /
|
||||
/ / / / /_/ / /__/ / __/ /
|
||||
/_/ /_/\__,_/\___/_/\___/_/ v2.3.0
|
||||
/_/ /_/\__,_/\___/_/\___/_/ v2.3.2
|
||||
`
|
||||
|
||||
// Version is the current version of nuclei
|
||||
const Version = `2.3.0`
|
||||
const Version = `2.3.2`
|
||||
|
||||
// showBanner is used to show the banner to the user
|
||||
func showBanner() {
|
||||
|
||||
@@ -16,13 +16,15 @@ type nucleiConfig struct {
|
||||
TemplatesDirectory string `json:"templates-directory,omitempty"`
|
||||
CurrentVersion string `json:"current-version,omitempty"`
|
||||
LastChecked time.Time `json:"last-checked,omitempty"`
|
||||
|
||||
IgnoreURL string `json:"ignore-url,omitempty"`
|
||||
NucleiVersion string `json:"nuclei-version,omitempty"`
|
||||
LastCheckedIgnore time.Time `json:"last-checked-ignore,omitempty"`
|
||||
// IgnorePaths ignores all the paths listed unless specified manually
|
||||
IgnorePaths []string `json:"ignore-paths,omitempty"`
|
||||
}
|
||||
|
||||
// nucleiConfigFilename is the filename of nuclei configuration file.
|
||||
const nucleiConfigFilename = ".nuclei-config.json"
|
||||
const nucleiConfigFilename = ".templates-config.json"
|
||||
|
||||
var reVersion = regexp.MustCompile(`\d+\.\d+\.\d+`)
|
||||
|
||||
@@ -32,8 +34,10 @@ func readConfiguration() (*nucleiConfig, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configDir := path.Join(home, "/.config", "/nuclei")
|
||||
_ = os.MkdirAll(configDir, os.ModePerm)
|
||||
|
||||
templatesConfigFile := path.Join(home, nucleiConfigFilename)
|
||||
templatesConfigFile := path.Join(configDir, nucleiConfigFilename)
|
||||
file, err := os.Open(templatesConfigFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -54,9 +58,16 @@ func (r *Runner) writeConfiguration(config *nucleiConfig) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configDir := path.Join(home, "/.config", "/nuclei")
|
||||
_ = os.MkdirAll(configDir, os.ModePerm)
|
||||
|
||||
if config.IgnoreURL == "" {
|
||||
config.IgnoreURL = "https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/master/.nuclei-ignore"
|
||||
}
|
||||
config.LastChecked = time.Now()
|
||||
templatesConfigFile := path.Join(home, nucleiConfigFilename)
|
||||
config.LastCheckedIgnore = time.Now()
|
||||
config.NucleiVersion = Version
|
||||
templatesConfigFile := path.Join(configDir, nucleiConfigFilename)
|
||||
file, err := os.OpenFile(templatesConfigFile, os.O_WRONLY|os.O_CREATE, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -95,7 +106,15 @@ func (r *Runner) readNucleiIgnoreFile() {
|
||||
|
||||
// getIgnoreFilePath returns the ignore file path for the runner
|
||||
func (r *Runner) getIgnoreFilePath() string {
|
||||
defIgnoreFilePath := path.Join(r.templatesConfig.TemplatesDirectory, nucleiIgnoreFile)
|
||||
var defIgnoreFilePath string
|
||||
|
||||
home, err := os.UserHomeDir()
|
||||
if err == nil {
|
||||
configDir := path.Join(home, "/.config", "/nuclei")
|
||||
_ = os.MkdirAll(configDir, os.ModePerm)
|
||||
|
||||
defIgnoreFilePath = path.Join(configDir, nucleiIgnoreFile)
|
||||
}
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
|
||||
@@ -25,6 +25,7 @@ func ParseOptions(options *types.Options) {
|
||||
// Show the user the banner
|
||||
showBanner()
|
||||
|
||||
options.ExcludeTags = append(options.ExcludeTags, "dos")
|
||||
if options.Version {
|
||||
gologger.Info().Msgf("Current Version: %s\n", Version)
|
||||
os.Exit(0)
|
||||
|
||||
@@ -19,13 +19,15 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/clusterer"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/headless/engine"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/issues"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/exporters/disk"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/templates"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||
"github.com/remeh/sizedwaitgroup"
|
||||
"github.com/rs/xid"
|
||||
"go.uber.org/atomic"
|
||||
"go.uber.org/ratelimit"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Runner is a client for running the enumeration process.
|
||||
@@ -39,7 +41,7 @@ type Runner struct {
|
||||
catalog *catalog.Catalog
|
||||
progress progress.Progress
|
||||
colorizer aurora.Aurora
|
||||
issuesClient *issues.Client
|
||||
issuesClient *reporting.Client
|
||||
severityColors *colorizer.Colorizer
|
||||
browser *engine.Browser
|
||||
ratelimiter ratelimit.Limiter
|
||||
@@ -66,13 +68,36 @@ func New(options *types.Options) (*Runner, error) {
|
||||
}
|
||||
runner.catalog = catalog.New(runner.options.TemplatesDirectory)
|
||||
|
||||
var reportingOptions *reporting.Options
|
||||
if options.ReportingConfig != "" {
|
||||
if client, err := issues.New(options.ReportingConfig, options.ReportingDB); err != nil {
|
||||
file, err := os.Open(options.ReportingConfig)
|
||||
if err != nil {
|
||||
gologger.Fatal().Msgf("Could not open reporting config file: %s\n", err)
|
||||
}
|
||||
|
||||
reportingOptions = &reporting.Options{}
|
||||
if parseErr := yaml.NewDecoder(file).Decode(reportingOptions); parseErr != nil {
|
||||
file.Close()
|
||||
gologger.Fatal().Msgf("Could not parse reporting config file: %s\n", parseErr)
|
||||
}
|
||||
file.Close()
|
||||
}
|
||||
if options.DiskExportDirectory != "" {
|
||||
if reportingOptions != nil {
|
||||
reportingOptions.DiskExporter = &disk.Options{Directory: options.DiskExportDirectory}
|
||||
} else {
|
||||
reportingOptions = &reporting.Options{}
|
||||
reportingOptions.DiskExporter = &disk.Options{Directory: options.DiskExportDirectory}
|
||||
}
|
||||
}
|
||||
if reportingOptions != nil {
|
||||
if client, err := reporting.New(reportingOptions, options.ReportingDB); err != nil {
|
||||
gologger.Fatal().Msgf("Could not create issue reporting client: %s\n", err)
|
||||
} else {
|
||||
runner.issuesClient = client
|
||||
}
|
||||
}
|
||||
|
||||
// output coloring
|
||||
useColor := !options.NoColor
|
||||
runner.colorizer = aurora.NewAurora(useColor)
|
||||
@@ -197,8 +222,10 @@ func (r *Runner) Close() {
|
||||
// RunEnumeration sets up the input layer for giving input nuclei.
|
||||
// binary and runs the actual enumeration
|
||||
func (r *Runner) RunEnumeration() {
|
||||
// resolves input templates definitions and any optional exclusion
|
||||
if len(r.options.Templates) == 0 && len(r.options.Tags) > 0 {
|
||||
defer r.Close()
|
||||
|
||||
// If we have no templates, run on whole template directory with provided tags
|
||||
if len(r.options.Templates) == 0 && len(r.options.Workflows) == 0 && !r.options.NewTemplates && (len(r.options.Tags) > 0 || len(r.options.ExcludeTags) > 0) {
|
||||
r.options.Templates = append(r.options.Templates, r.options.TemplatesDirectory)
|
||||
}
|
||||
if r.options.NewTemplates {
|
||||
|
||||
@@ -47,7 +47,7 @@ func (r *Runner) getParsedTemplatesFor(templatePaths, severities []string, workf
|
||||
parsedTemplates[t.ID] = t
|
||||
gologger.Info().Msgf("%s\n", r.templateLogMsg(t.ID, types.ToString(t.Info["name"]), types.ToString(t.Info["author"]), sev))
|
||||
} else {
|
||||
gologger.Error().Msgf("Excluding template %s due to severity filter (%s not in [%s])", t.ID, sev, severities)
|
||||
gologger.Warning().Msgf("Excluding template %s due to severity filter (%s not in [%s])", t.ID, sev, severities)
|
||||
}
|
||||
}
|
||||
return parsedTemplates, workflowCount
|
||||
@@ -131,9 +131,18 @@ func (r *Runner) listAvailableTemplates() {
|
||||
|
||||
func hasMatchingSeverity(templateSeverity string, allowedSeverities []string) bool {
|
||||
for _, s := range allowedSeverities {
|
||||
s = strings.ToLower(s)
|
||||
if s != "" && strings.HasPrefix(templateSeverity, s) {
|
||||
return true
|
||||
finalSeverities := []string{}
|
||||
if strings.Contains(s, ",") {
|
||||
finalSeverities = strings.Split(s, ",")
|
||||
} else {
|
||||
finalSeverities = append(finalSeverities, s)
|
||||
}
|
||||
|
||||
for _, sev := range finalSeverities {
|
||||
sev = strings.ToLower(sev)
|
||||
if sev != "" && strings.HasPrefix(templateSeverity, sev) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -41,6 +41,8 @@ func (r *Runner) updateTemplates() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configDir := path.Join(home, "/.config", "/nuclei")
|
||||
_ = os.MkdirAll(configDir, os.ModePerm)
|
||||
|
||||
templatesConfigFile := path.Join(home, nucleiConfigFilename)
|
||||
if _, statErr := os.Stat(templatesConfigFile); !os.IsNotExist(statErr) {
|
||||
@@ -51,15 +53,61 @@ func (r *Runner) updateTemplates() error {
|
||||
r.templatesConfig = config
|
||||
}
|
||||
|
||||
ignoreURL := "https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/master/.nuclei-ignore"
|
||||
if r.templatesConfig == nil {
|
||||
currentConfig := &nucleiConfig{
|
||||
TemplatesDirectory: path.Join(home, "nuclei-templates"),
|
||||
IgnoreURL: ignoreURL,
|
||||
NucleiVersion: Version,
|
||||
}
|
||||
if writeErr := r.writeConfiguration(currentConfig); writeErr != nil {
|
||||
return errors.Wrap(writeErr, "could not write template configuration")
|
||||
}
|
||||
r.templatesConfig = currentConfig
|
||||
}
|
||||
// Check if last checked for nuclei-ignore is more than 1 hours.
|
||||
// and if true, run the check.
|
||||
if r.templatesConfig == nil || time.Since(r.templatesConfig.LastCheckedIgnore) > 1*time.Hour || r.options.UpdateTemplates {
|
||||
if r.templatesConfig != nil && r.templatesConfig.IgnoreURL != "" {
|
||||
ignoreURL = r.templatesConfig.IgnoreURL
|
||||
}
|
||||
gologger.Verbose().Msgf("Downloading config file from %s", ignoreURL)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
req, reqErr := http.NewRequestWithContext(ctx, http.MethodGet, ignoreURL, nil)
|
||||
if reqErr == nil {
|
||||
resp, httpGet := http.DefaultClient.Do(req)
|
||||
if httpGet != nil {
|
||||
if resp != nil && resp.Body != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
gologger.Warning().Msgf("Could not get ignore-file from %s: %s", ignoreURL, err)
|
||||
} else {
|
||||
data, _ := ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
|
||||
if len(data) > 0 {
|
||||
_ = ioutil.WriteFile(path.Join(configDir, nucleiIgnoreFile), data, 0644)
|
||||
}
|
||||
if r.templatesConfig != nil {
|
||||
r.templatesConfig.LastCheckedIgnore = time.Now()
|
||||
}
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
if r.templatesConfig == nil || (r.options.TemplatesDirectory != "" && r.templatesConfig.TemplatesDirectory != r.options.TemplatesDirectory) {
|
||||
if r.templatesConfig.CurrentVersion == "" || (r.options.TemplatesDirectory != "" && r.templatesConfig.TemplatesDirectory != r.options.TemplatesDirectory) {
|
||||
if !r.options.UpdateTemplates {
|
||||
gologger.Warning().Msgf("nuclei-templates are not installed, use update-templates flag.\n")
|
||||
gologger.Warning().Msgf("nuclei-templates are not installed (or indexed), use update-templates flag.\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use custom location if user has given a template directory
|
||||
r.templatesConfig = &nucleiConfig{TemplatesDirectory: path.Join(home, "nuclei-templates")}
|
||||
r.templatesConfig = &nucleiConfig{
|
||||
TemplatesDirectory: path.Join(home, "nuclei-templates"),
|
||||
}
|
||||
if r.options.TemplatesDirectory != "" && r.options.TemplatesDirectory != path.Join(home, "nuclei-templates") {
|
||||
r.templatesConfig.TemplatesDirectory = r.options.TemplatesDirectory
|
||||
}
|
||||
@@ -132,7 +180,6 @@ func (r *Runner) updateTemplates() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = r.writeConfiguration(r.templatesConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -10,11 +10,11 @@ import (
|
||||
func (w *StandardWriter) formatScreen(output *ResultEvent) []byte {
|
||||
builder := &bytes.Buffer{}
|
||||
|
||||
builder.WriteRune('[')
|
||||
builder.WriteString(w.aurora.Cyan(output.Timestamp.Format("2006-01-02 15:04:05")).String())
|
||||
builder.WriteString("] ")
|
||||
|
||||
if !w.noMetadata {
|
||||
builder.WriteRune('[')
|
||||
builder.WriteString(w.aurora.Cyan(output.Timestamp.Format("2006-01-02 15:04:05")).String())
|
||||
builder.WriteString("] ")
|
||||
|
||||
builder.WriteRune('[')
|
||||
builder.WriteString(w.aurora.BrightGreen(output.TemplateID).String())
|
||||
|
||||
|
||||
@@ -183,7 +183,7 @@ func (p *StatsTicker) getMetrics() map[string]interface{} {
|
||||
results["templates"] = clistats.String(templates)
|
||||
hosts, _ := p.stats.GetStatic("hosts")
|
||||
results["hosts"] = clistats.String(hosts)
|
||||
matched, _ := p.stats.GetStatic("matched")
|
||||
matched, _ := p.stats.GetCounter("matched")
|
||||
results["matched"] = clistats.String(matched)
|
||||
requests, _ := p.stats.GetCounter("requests")
|
||||
results["requests"] = clistats.String(requests)
|
||||
|
||||
@@ -57,5 +57,8 @@ func fileExists(filename string) bool {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
if info == nil {
|
||||
return false
|
||||
}
|
||||
return !info.IsDir()
|
||||
}
|
||||
@@ -39,6 +39,8 @@ func (r *Request) ExecuteWithResults(input string, metadata, previous output.Int
|
||||
if err != nil {
|
||||
r.options.Output.Request(r.options.TemplateID, domain, "dns", err)
|
||||
r.options.Progress.IncrementFailedRequestsBy(1)
|
||||
}
|
||||
if resp == nil {
|
||||
return errors.Wrap(err, "could not send dns request")
|
||||
}
|
||||
r.options.Progress.IncrementRequests()
|
||||
|
||||
@@ -24,7 +24,11 @@ func (r *Request) Match(data map[string]interface{}, matcher *matchers.Matcher)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return matcher.Result(matcher.MatchStatusCode(statusCode.(int)))
|
||||
status, ok := statusCode.(int)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return matcher.Result(matcher.MatchStatusCode(status))
|
||||
case matchers.SizeMatcher:
|
||||
return matcher.Result(matcher.MatchSize(len(item)))
|
||||
case matchers.WordsMatcher:
|
||||
|
||||
@@ -113,13 +113,8 @@ func (r *Request) executeAddress(actualAddress, address, input string, shouldUse
|
||||
inputEvents[input.Name] = string(buffer[:n])
|
||||
}
|
||||
}
|
||||
r.options.Progress.IncrementRequests()
|
||||
}
|
||||
if err != nil {
|
||||
r.options.Output.Request(r.options.TemplateID, address, "network", err)
|
||||
r.options.Progress.IncrementFailedRequestsBy(1)
|
||||
return errors.Wrap(err, "could not write request to server")
|
||||
}
|
||||
r.options.Progress.IncrementRequests()
|
||||
|
||||
if r.options.Options.Debug || r.options.Options.DebugRequests {
|
||||
gologger.Info().Str("address", actualAddress).Msgf("[%s] Dumped Network request for %s", r.options.TemplateID, actualAddress)
|
||||
@@ -137,7 +132,6 @@ func (r *Request) executeAddress(actualAddress, address, input string, shouldUse
|
||||
n, err := conn.Read(final)
|
||||
if err != nil && err != io.EOF {
|
||||
r.options.Output.Request(r.options.TemplateID, address, "network", err)
|
||||
r.options.Progress.IncrementFailedRequestsBy(1)
|
||||
return errors.Wrap(err, "could not read from server")
|
||||
}
|
||||
responseBuilder.Write(final[:n])
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/progress"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/projectfile"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/protocols/headless/engine"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/issues"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||
"go.uber.org/ratelimit"
|
||||
)
|
||||
@@ -39,7 +39,7 @@ type ExecuterOptions struct {
|
||||
// Options contains configuration options for the executer.
|
||||
Options *types.Options
|
||||
// IssuesClient is a client for nuclei issue tracker reporting
|
||||
IssuesClient *issues.Client
|
||||
IssuesClient *reporting.Client
|
||||
// Progress is a progress client for scan reporting
|
||||
Progress progress.Progress
|
||||
// RateLimiter is a rate-limiter for limiting sent number of requests.
|
||||
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,60 @@
|
||||
package disk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/format"
|
||||
)
|
||||
|
||||
type Exporter struct {
|
||||
directory string
|
||||
options *Options
|
||||
}
|
||||
|
||||
// Options contains the configuration options for github issue tracker client
|
||||
type Options struct {
|
||||
// Directory is the directory to export found results to
|
||||
Directory string `yaml:"directory"`
|
||||
}
|
||||
|
||||
// New creates a new disk exporter integration client based on options.
|
||||
func New(options *Options) (*Exporter, error) {
|
||||
directory := options.Directory
|
||||
if options.Directory == "" {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
directory = dir
|
||||
}
|
||||
_ = os.MkdirAll(directory, os.ModePerm)
|
||||
return &Exporter{options: options, directory: directory}, nil
|
||||
}
|
||||
|
||||
// Export exports a passed result event to disk
|
||||
func (i *Exporter) Export(event *output.ResultEvent) error {
|
||||
summary := format.Summary(event)
|
||||
description := format.MarkdownDescription(event)
|
||||
|
||||
filenameBuilder := &strings.Builder{}
|
||||
filenameBuilder.WriteString(event.TemplateID)
|
||||
filenameBuilder.WriteString("-")
|
||||
filenameBuilder.WriteString(strings.ReplaceAll(strings.ReplaceAll(event.Matched, "/", "_"), ":", "_"))
|
||||
filenameBuilder.WriteString(".md")
|
||||
finalFilename := filenameBuilder.String()
|
||||
|
||||
dataBuilder := &bytes.Buffer{}
|
||||
dataBuilder.WriteString("### ")
|
||||
dataBuilder.WriteString(summary)
|
||||
dataBuilder.WriteString("\n---\n")
|
||||
dataBuilder.WriteString(description)
|
||||
data := dataBuilder.Bytes()
|
||||
|
||||
err := ioutil.WriteFile(path.Join(i.directory, finalFilename), data, 0644)
|
||||
return err
|
||||
}
|
||||
@@ -46,11 +46,22 @@ func MarkdownDescription(event *output.ResultEvent) string {
|
||||
for k, v := range event.Info {
|
||||
builder.WriteString(fmt.Sprintf("| %s | %s |\n", k, v))
|
||||
}
|
||||
builder.WriteString("\n**Request**\n\n```\n")
|
||||
builder.WriteString(event.Request)
|
||||
builder.WriteString("\n```\n\n<details><summary>**Response**</summary>\n\n```\n")
|
||||
builder.WriteString(event.Response)
|
||||
builder.WriteString("\n```\n\n")
|
||||
if event.Request != "" {
|
||||
builder.WriteString("\n**Request**\n\n```http\n")
|
||||
builder.WriteString(event.Request)
|
||||
builder.WriteString("\n```\n")
|
||||
}
|
||||
if event.Response != "" {
|
||||
builder.WriteString("\n**Response**\n\n```http\n")
|
||||
// If the response is larger than 5 kb, truncate it before writing.
|
||||
if len(event.Response) > 5*1024 {
|
||||
builder.WriteString(event.Response[:5*1024])
|
||||
builder.WriteString(".... Truncated ....")
|
||||
} else {
|
||||
builder.WriteString(event.Response)
|
||||
}
|
||||
builder.WriteString("\n```\n\n")
|
||||
}
|
||||
|
||||
if len(event.ExtractedResults) > 0 || len(event.Metadata) > 0 {
|
||||
builder.WriteString("**Extra Information**\n\n")
|
||||
@@ -75,6 +86,7 @@ func MarkdownDescription(event *output.ResultEvent) string {
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
}
|
||||
builder.WriteString("\n---\nGenerated by [Nuclei](https://github.com/projectdiscovery/nuclei)")
|
||||
data := builder.String()
|
||||
return data
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
package issues
|
||||
package reporting
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/issues/dedupe"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/issues/github"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/issues/gitlab"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/issues/jira"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/dedupe"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/exporters/disk"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/trackers/github"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/trackers/gitlab"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/trackers/jira"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||
"gopkg.in/yaml.v2"
|
||||
"go.uber.org/multierr"
|
||||
)
|
||||
|
||||
// Options is a configuration file for nuclei reporting module
|
||||
@@ -26,6 +26,8 @@ type Options struct {
|
||||
Gitlab *gitlab.Options `yaml:"gitlab"`
|
||||
// Jira contains configuration options for Jira Issue Tracker
|
||||
Jira *jira.Options `yaml:"jira"`
|
||||
// DiskExporter contains configuration options for Disk Exporter Module
|
||||
DiskExporter *disk.Options `yaml:"disk"`
|
||||
}
|
||||
|
||||
// Filter filters the received event and decides whether to perform
|
||||
@@ -75,25 +77,22 @@ type Tracker interface {
|
||||
CreateIssue(event *output.ResultEvent) error
|
||||
}
|
||||
|
||||
// Exporter is an interface implemented by an issue exporter
|
||||
type Exporter interface {
|
||||
// Export exports an issue to an exporter
|
||||
Export(event *output.ResultEvent) error
|
||||
}
|
||||
|
||||
// Client is a client for nuclei issue tracking module
|
||||
type Client struct {
|
||||
tracker Tracker
|
||||
options *Options
|
||||
dedupe *dedupe.Storage
|
||||
trackers []Tracker
|
||||
exporters []Exporter
|
||||
options *Options
|
||||
dedupe *dedupe.Storage
|
||||
}
|
||||
|
||||
// New creates a new nuclei issue tracker reporting client
|
||||
func New(config, db string) (*Client, error) {
|
||||
file, err := os.Open(config)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not open reporting config file")
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
options := &Options{}
|
||||
if parseErr := yaml.NewDecoder(file).Decode(options); parseErr != nil {
|
||||
return nil, parseErr
|
||||
}
|
||||
func New(options *Options, db string) (*Client, error) {
|
||||
if options.AllowList != nil {
|
||||
options.AllowList.Compile()
|
||||
}
|
||||
@@ -101,27 +100,41 @@ func New(config, db string) (*Client, error) {
|
||||
options.DenyList.Compile()
|
||||
}
|
||||
|
||||
var tracker Tracker
|
||||
client := &Client{options: options}
|
||||
if options.Github != nil {
|
||||
tracker, err = github.New(options.Github)
|
||||
tracker, err := github.New(options.Github)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create reporting client")
|
||||
}
|
||||
client.trackers = append(client.trackers, tracker)
|
||||
}
|
||||
if options.Gitlab != nil {
|
||||
tracker, err = gitlab.New(options.Gitlab)
|
||||
tracker, err := gitlab.New(options.Gitlab)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create reporting client")
|
||||
}
|
||||
client.trackers = append(client.trackers, tracker)
|
||||
}
|
||||
if options.Jira != nil {
|
||||
tracker, err = jira.New(options.Jira)
|
||||
tracker, err := jira.New(options.Jira)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create reporting client")
|
||||
}
|
||||
client.trackers = append(client.trackers, tracker)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create reporting client")
|
||||
}
|
||||
if tracker == nil {
|
||||
return nil, errors.New("no issue tracker configuration found")
|
||||
if options.DiskExporter != nil {
|
||||
exporter, err := disk.New(options.DiskExporter)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create exporting client")
|
||||
}
|
||||
client.exporters = append(client.exporters, exporter)
|
||||
}
|
||||
storage, err := dedupe.New(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Client{tracker: tracker, dedupe: storage, options: options}, nil
|
||||
client.dedupe = storage
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// Close closes the issue tracker reporting client
|
||||
@@ -138,15 +151,20 @@ func (c *Client) CreateIssue(event *output.ResultEvent) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
found, err := c.dedupe.Index(event)
|
||||
if err != nil {
|
||||
_ = c.tracker.CreateIssue(event)
|
||||
return err
|
||||
unique, err := c.dedupe.Index(event)
|
||||
if unique {
|
||||
for _, tracker := range c.trackers {
|
||||
if trackerErr := tracker.CreateIssue(event); trackerErr != nil {
|
||||
err = multierr.Append(err, trackerErr)
|
||||
}
|
||||
}
|
||||
for _, exporter := range c.exporters {
|
||||
if exportErr := exporter.Export(event); exportErr != nil {
|
||||
err = multierr.Append(err, exportErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
if found {
|
||||
return c.tracker.CreateIssue(event)
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func stringSliceContains(slice []string, item string) bool {
|
||||
+1
-1
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/google/go-github/github"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/issues/format"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/format"
|
||||
)
|
||||
|
||||
// Integration is a client for a issue tracker integration
|
||||
+1
-1
@@ -2,7 +2,7 @@ package gitlab
|
||||
|
||||
import (
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/issues/format"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/format"
|
||||
"github.com/xanzy/go-gitlab"
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
jira "github.com/andygrunwald/go-jira"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/issues/format"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/format"
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||
)
|
||||
|
||||
@@ -97,7 +97,13 @@ func jiraFormatDescription(event *output.ResultEvent) string {
|
||||
builder.WriteString("\n*Request*\n\n{code}\n")
|
||||
builder.WriteString(event.Request)
|
||||
builder.WriteString("\n{code}\n\n*Response*\n\n{code}\n")
|
||||
builder.WriteString(event.Response)
|
||||
// If the response is larger than 5 kb, truncate it before writing.
|
||||
if len(event.Response) > 5*1024 {
|
||||
builder.WriteString(event.Response[:5*1024])
|
||||
builder.WriteString(".... Truncated ....")
|
||||
} else {
|
||||
builder.WriteString(event.Response)
|
||||
}
|
||||
builder.WriteString("\n{code}\n\n")
|
||||
|
||||
if len(event.ExtractedResults) > 0 || len(event.Metadata) > 0 {
|
||||
@@ -123,6 +129,7 @@ func jiraFormatDescription(event *output.ResultEvent) string {
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
}
|
||||
builder.WriteString("\n---\nGenerated by [Nuclei|https://github.com/projectdiscovery/nuclei]")
|
||||
data := builder.String()
|
||||
return data
|
||||
}
|
||||
@@ -45,13 +45,20 @@ func Parse(filePath string, options protocols.ExecuterOptions) (*Template, error
|
||||
if _, ok := template.Info["author"]; !ok {
|
||||
return nil, errors.New("no template author field provided")
|
||||
}
|
||||
templateTags, ok := template.Info["tags"]
|
||||
if !ok {
|
||||
templateTags = ""
|
||||
}
|
||||
matchWithTags := false
|
||||
if len(options.Options.Tags) > 0 {
|
||||
templateTags, ok := template.Info["tags"]
|
||||
if !ok {
|
||||
return nil, nil
|
||||
if err := matchTemplateWithTags(types.ToString(templateTags), types.ToString(template.Info["severity"]), options.Options.Tags); err != nil {
|
||||
return nil, fmt.Errorf("tags filter not matched %s", templateTags)
|
||||
}
|
||||
if err := matchTemplateWithTags(types.ToString(templateTags), options.Options); err != nil {
|
||||
return nil, nil
|
||||
matchWithTags = true
|
||||
}
|
||||
if len(options.Options.ExcludeTags) > 0 && !matchWithTags {
|
||||
if err := matchTemplateWithTags(types.ToString(templateTags), types.ToString(template.Info["severity"]), options.Options.ExcludeTags); err == nil {
|
||||
return nil, fmt.Errorf("exclude-tags filter matched %s", templateTags)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,12 +207,15 @@ func (t *Template) parseWorkflowTemplate(workflow *workflows.WorkflowTemplate, o
|
||||
}
|
||||
|
||||
// matchTemplateWithTags matches if the template matches a tag
|
||||
func matchTemplateWithTags(tags string, options *types.Options) error {
|
||||
func matchTemplateWithTags(tags, severity string, tagsInput []string) error {
|
||||
actualTags := strings.Split(tags, ",")
|
||||
if severity != "" {
|
||||
actualTags = append(actualTags, severity) // also add severity to tag
|
||||
}
|
||||
|
||||
matched := false
|
||||
mainLoop:
|
||||
for _, t := range options.Tags {
|
||||
for _, t := range tagsInput {
|
||||
commaTags := strings.Split(t, ",")
|
||||
for _, tag := range commaTags {
|
||||
tag = strings.TrimSpace(tag)
|
||||
|
||||
@@ -3,30 +3,39 @@ package templates
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v2/pkg/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMatchTemplateWithTags(t *testing.T) {
|
||||
err := matchTemplateWithTags("php,linux,symfony", &types.Options{Tags: []string{"php"}})
|
||||
err := matchTemplateWithTags("php,linux,symfony", "", []string{"php"})
|
||||
require.Nil(t, err, "could not get php tag from input slice")
|
||||
|
||||
err = matchTemplateWithTags("lang:php,os:linux,cms:symfony", &types.Options{Tags: []string{"cms:symfony"}})
|
||||
err = matchTemplateWithTags("lang:php,os:linux,cms:symfony", "", []string{"cms:symfony"})
|
||||
require.Nil(t, err, "could not get php tag from input key value")
|
||||
|
||||
err = matchTemplateWithTags("lang:php,os:linux,symfony", &types.Options{Tags: []string{"cms:symfony"}})
|
||||
err = matchTemplateWithTags("lang:php,os:linux,symfony", "", []string{"cms:symfony"})
|
||||
require.NotNil(t, err, "could get key value tag from input key value")
|
||||
|
||||
err = matchTemplateWithTags("lang:php,os:linux,cms:jira", &types.Options{Tags: []string{"cms:symfony"}})
|
||||
err = matchTemplateWithTags("lang:php,os:linux,cms:jira", "", []string{"cms:symfony"})
|
||||
require.NotNil(t, err, "could get key value tag from input key value")
|
||||
|
||||
t.Run("space", func(t *testing.T) {
|
||||
err = matchTemplateWithTags("lang:php, os:linux, cms:symfony", &types.Options{Tags: []string{"cms:symfony"}})
|
||||
err = matchTemplateWithTags("lang:php, os:linux, cms:symfony", "", []string{"cms:symfony"})
|
||||
require.Nil(t, err, "could get key value tag from input key value with space")
|
||||
})
|
||||
|
||||
t.Run("comma-tags", func(t *testing.T) {
|
||||
err = matchTemplateWithTags("lang:php,os:linux,cms:symfony", &types.Options{Tags: []string{"test,cms:symfony"}})
|
||||
err = matchTemplateWithTags("lang:php,os:linux,cms:symfony", "", []string{"test,cms:symfony"})
|
||||
require.Nil(t, err, "could get key value tag from input key value with comma")
|
||||
})
|
||||
|
||||
t.Run("severity", func(t *testing.T) {
|
||||
err = matchTemplateWithTags("lang:php,os:linux,cms:symfony", "low", []string{"low"})
|
||||
require.Nil(t, err, "could get key value tag for severity")
|
||||
})
|
||||
|
||||
t.Run("blank-tags", func(t *testing.T) {
|
||||
err = matchTemplateWithTags("", "low", []string{"jira"})
|
||||
require.NotNil(t, err, "could get value tag for blank severity")
|
||||
})
|
||||
}
|
||||
@@ -8,6 +8,8 @@ type Options struct {
|
||||
// can be specified with -l flag and -tags can be used in combination with
|
||||
// the -l flag.
|
||||
Tags goflags.StringSlice
|
||||
// ExcludeTags is the list of tags to exclude
|
||||
ExcludeTags goflags.StringSlice
|
||||
// Workflows specifies any workflows to run by nuclei
|
||||
Workflows goflags.StringSlice
|
||||
// Templates specifies the template/templates to use
|
||||
@@ -41,6 +43,8 @@ type Options struct {
|
||||
ReportingDB string
|
||||
// ReportingConfig is the config file for nuclei reporting module
|
||||
ReportingConfig string
|
||||
// DiskExportDirectory is the directory to export reports in markdown on disk to
|
||||
DiskExportDirectory string
|
||||
// ResolversFile is a file containing resolvers for nuclei.
|
||||
ResolversFile string
|
||||
// StatsInterval is the number of seconds to display stats after
|
||||
|
||||
Reference in new issue
Block a user