diff --git a/v2/cmd/cve-annotate/main.go b/v2/cmd/cve-annotate/main.go new file mode 100644 index 000000000..a78f180ed --- /dev/null +++ b/v2/cmd/cve-annotate/main.go @@ -0,0 +1,160 @@ +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "regexp" + "strings" + + "github.com/daehee/nvd" + "github.com/projectdiscovery/nuclei/v2/pkg/catalog" +) + +var ( + input = flag.String("i", "", "Templates to annotate") + templateDir = flag.String("d", "", "Custom template directory for update") +) + +func main() { + flag.Parse() + + if *input == "" || *templateDir == "" { + log.Fatalf("invalid input, see -h\n") + } + + if err := process(); err != nil { + log.Fatalf("could not process: %s\n", err) + } +} + +func process() error { + tempDir, err := ioutil.TempDir("", "nuclei-nvd-%s") + if err != nil { + return err + } + defer os.RemoveAll(tempDir) + + client, err := nvd.NewClient(tempDir) + if err != nil { + return err + } + + catalog := catalog.New(*templateDir) + + paths, err := catalog.GetTemplatePath(*input) + if err != nil { + return err + } + for _, path := range paths { + data, err := ioutil.ReadFile(path) + if err != nil { + return err + } + getCVEData(client, path, string(data)) + } + return nil +} + +var ( + idRegex = regexp.MustCompile("id: ([C|c][V|v][E|e]-[0-9]+-[0-9]+)") + severityRegex = regexp.MustCompile(`severity: ([a-z]+)`) +) + +func getCVEData(client *nvd.Client, filePath, data string) { + matches := idRegex.FindAllStringSubmatch(data, 1) + if len(matches) == 0 { + return + } + cveName := matches[0][1] + + severityMatches := severityRegex.FindAllStringSubmatch(data, 1) + if len(matches) == 0 { + return + } + severityValue := severityMatches[0][1] + + cveItem, err := client.FetchCVE(cveName) + if err != nil { + log.Printf("Could not fetch cve %s: %s\n", cveName, err) + return + } + var cweID []string + for _, problemData := range cveItem.CVE.Problemtype.ProblemtypeData { + for _, description := range problemData.Description { + cweID = append(cweID, description.Value) + } + } + cvssScore := cveItem.Impact.BaseMetricV3.CvssV3.BaseScore + cvssMetrics := cveItem.Impact.BaseMetricV3.CvssV3.VectorString + + // Perform some hacky string replacement to place the metadata in templates + infoBlockIndexData := data[strings.Index(data, "info:"):] + requestsIndex := strings.Index(infoBlockIndexData, "requests:") + networkIndex := strings.Index(infoBlockIndexData, "network:") + if requestsIndex == -1 && networkIndex == -1 { + return + } + if networkIndex != -1 { + requestsIndex = networkIndex + } + infoBlockData := infoBlockIndexData[:requestsIndex] + infoBlockClean := strings.TrimRight(infoBlockData, "\n") + + newInfoBlock := infoBlockClean + var changed bool + + if newSeverity := isSeverityMatchingCvssScore(severityValue, cvssScore); newSeverity != "" { + changed = true + newInfoBlock = strings.ReplaceAll(newInfoBlock, severityMatches[0][0], "severity: "+newSeverity) + fmt.Printf("Adjusting severity for %s from %s=>%s (%.2f)\n", filePath, severityValue, newSeverity, cvssScore) + } + // Start with additional-fields as that is the one most likely to break stuff. + if !strings.Contains(infoBlockClean, "classification") && (cvssScore != 0 && cvssMetrics != "") { + changed = true + newInfoBlock = newInfoBlock + fmt.Sprintf("\n classification:\n cvss-metrics: %s\n cvss-score: %.2f\n cve-id: %s", cvssMetrics, cvssScore, cveName) + if len(cweID) > 0 && (cweID[0] != "NVD-CWE-Other" && cweID[0] != "NVD-CWE-noinfo") { + newInfoBlock = newInfoBlock + fmt.Sprintf("\n cwe-id: %s", strings.Join(cweID, ",")) + } + } + // If there is no description field, fill the description from CVE information + if !strings.Contains(infoBlockClean, "description:") { + changed = true + newInfoBlock = newInfoBlock + fmt.Sprintf("\n description: %s", fmt.Sprintf("%q", cveItem.CVE.Description.DescriptionData[0].Value)) + } + if !strings.Contains(infoBlockClean, "reference:") && len(cveItem.CVE.References.ReferenceData) > 0 { + changed = true + newInfoBlock = newInfoBlock + "\n reference:" + for _, reference := range cveItem.CVE.References.ReferenceData { + newInfoBlock = newInfoBlock + fmt.Sprintf("\n - %s", reference.URL) + } + } + newTemplate := strings.ReplaceAll(data, infoBlockClean, newInfoBlock) + if changed { + _ = ioutil.WriteFile(filePath, []byte(newTemplate), 0777) + fmt.Printf("Wrote updated template to %s\n", filePath) + } +} + +func isSeverityMatchingCvssScore(severity string, score float64) string { + if score == 0.0 { + return "" + } + var expected string + + if score >= 0.1 && score <= 3.9 { + expected = "low" + } else if score >= 4.0 && score <= 6.9 { + expected = "medium" + } else if score >= 7.0 && score <= 8.9 { + expected = "high" + } else if score >= 9.0 && score <= 10.0 { + expected = "critical" + } + if expected != "" && expected != severity { + return expected + } + return "" +} diff --git a/v2/go.mod b/v2/go.mod index a509f4c29..7a9dee877 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -13,7 +13,7 @@ require ( github.com/bluele/gcache v0.0.2 github.com/c4milo/unpackit v0.1.0 // indirect github.com/corpix/uarand v0.1.1 - github.com/davecgh/go-spew v1.1.1 + github.com/daehee/nvd v1.0.4 github.com/go-rod/rod v0.91.1 github.com/google/go-github v17.0.0+incompatible github.com/gosuri/uilive v0.0.4 // indirect diff --git a/v2/go.sum b/v2/go.sum index 97e87b5d9..c31f68c9b 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -45,9 +45,10 @@ github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHS github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/PuerkitoBio/goquery v1.6.0 h1:j7taAbelrdcsOlGeMenZxc2AWXD5fieT1/znArdnx94= +github.com/PuerkitoBio/goquery v1.6.0/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= -github.com/akrylysov/pogreb v0.10.0 h1:pVKi+uf3EzZUmiwr9bZnPk4W379KP8QsFzAa9IUuOog= github.com/akrylysov/pogreb v0.10.0/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI= github.com/akrylysov/pogreb v0.10.1 h1:FqlR8VR7uCbJdfUob916tPM+idpKgeESDXOA1K0DK4w= github.com/akrylysov/pogreb v0.10.1/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI= @@ -55,6 +56,8 @@ github.com/alecthomas/jsonschema v0.0.0-20210818095345-1014919a589c h1:oJsq4z4xK github.com/alecthomas/jsonschema v0.0.0-20210818095345-1014919a589c/go.mod h1:/n6+1/DWPltRLWL/VKyUxg6tzsl5kHUCcraimt4vr60= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= +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 h1:7GT/3qhar2dGJ0kq8w0d63liNyHOnxZsUZ9Pe4+AKBI= github.com/andygrunwald/go-jira v1.14.0/go.mod h1:KMo2f4DgMZA1C9FdImuLc04x4WQhn5derQpnsuBFgqE= github.com/antchfx/htmlquery v1.2.3 h1:sP3NFDneHx2stfNXCKbhHFo8XgNjCACnU/4AO5gWz6M= @@ -96,7 +99,8 @@ github.com/corpix/uarand v0.1.1 h1:RMr1TWc9F4n5jiPDzFHtmaUXLKLNUFK0SgCLo4BhX/U= github.com/corpix/uarand v0.1.1/go.mod h1:SFKZvkcRoLqVRFZ4u25xPmp6m9ktANfbpXZ7SJ0/FNU= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/dave/dst v0.26.2 h1:lnxLAKI3tx7MgLNVDirFCsDTlTG9nKTk7GcptKcWSwY= +github.com/daehee/nvd v1.0.4 h1:qC0kJ68vAYS86v8GwBORReBhyC5yUaUzsBokxjlsT98= +github.com/daehee/nvd v1.0.4/go.mod h1:iBRJHIdIs+ylfq8630my2eMw8kwzH4Z7qsetjJZxCzs= github.com/dave/dst v0.26.2/go.mod h1:UMDJuIRPfyUCC78eFuB+SV/WI8oDeyFDvM/JR6NI3IU= github.com/dave/gopackages v0.0.0-20170318123100-46e7023ec56e/go.mod h1:i00+b/gKdIDIxuLDFob7ustLAVqhsZRk2qVZrArELGQ= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= @@ -229,7 +233,6 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/itchyny/go-flags v1.5.0/go.mod h1:lenkYuCobuxLBAd/HGFE4LRoW8D3B6iXRQfWYJ+MNbA= github.com/itchyny/gojq v0.12.4 h1:8zgOZWMejEWCLjbF/1mWY7hY7QEARm7dtuhC6Bp4R8o= @@ -276,13 +279,11 @@ github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczG github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -508,8 +509,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -614,7 +615,6 @@ golang.org/x/sys v0.0.0-20210415045647-66c3f260301c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -676,7 +676,6 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -705,7 +704,6 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= @@ -792,7 +790,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -mvdan.cc/gofumpt v0.1.1 h1:bi/1aS/5W00E2ny5q65w9SnKpWEF/UIOqDYBILpo9rA= mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= diff --git a/v2/internal/runner/runner.go b/v2/internal/runner/runner.go index 89d7b6414..0989fe020 100644 --- a/v2/internal/runner/runner.go +++ b/v2/internal/runner/runner.go @@ -189,7 +189,7 @@ func New(options *types.Options) (*Runner, error) { } // Create the output file if asked - outputWriter, err := output.NewStandardWriter(!options.NoColor, options.NoMeta, options.NoTimestamp, options.JSON, options.Output, options.TraceLogFile) + outputWriter, err := output.NewStandardWriter(!options.NoColor, options.NoMeta, options.NoTimestamp, options.JSON, options.JSONRequests, options.Output, options.TraceLogFile) if err != nil { return nil, errors.Wrap(err, "could not create output file") } diff --git a/v2/pkg/model/model.go b/v2/pkg/model/model.go index 838cc0858..85df3724a 100644 --- a/v2/pkg/model/model.go +++ b/v2/pkg/model/model.go @@ -65,4 +65,41 @@ type Info struct { // - value: > // map[string]string{"customField1":"customValue1"} AdditionalFields map[string]string `json:"additional-fields,omitempty" yaml:"additional-fields,omitempty" jsonschema:"title=additional metadata for the template,description=Additional metadata fields for the template"` + + // description: | + // Classification contains classification information about the template. + Classification *Classification `json:"classification,omitempty" yaml:"classification,omitempty" jsonschema:"title=classification info for the template,description=Classification information for the template"` + + // description: | + // Remediation steps for the template. + // + // You can go in-depth here on how to mitigate the problem found by this template. + // + // examples: + // - value: "\"Change the default administrative username and password of Apache ActiveMQ by editing the file jetty-realm.properties\"" + Remediation string `json:"remediation,omitempty" yaml:"remediation,omitempty" jsonschema:"title=remediation steps for the template,description=In-depth explanation on how to fix the issues found by the template,example=Change the default administrative username and password of Apache ActiveMQ by editing the file jetty-realm.properties"` +} + +// Classification contains the vulnerability classification data for a template. +type Classification struct { + // description: | + // CVE ID for the template + // examples: + // - value: "\"CVE-2020-14420\"" + CVEID stringslice.StringSlice `json:"cve-id,omitempty" yaml:"cve-id,omitempty" jsonschema:"title=cve ids for the template,description=CVE IDs for the template,example=CVE-2020-14420"` + // description: | + // CWE ID for the template. + // examples: + // - value: "\"CWE-22\"" + CWEID stringslice.StringSlice `json:"cwe-id,omitempty" yaml:"cwe-id,omitempty" jsonschema:"title=cwe ids for the template,description=CWE IDs for the template,example=CWE-22"` + // description: | + // CVSS Metrics for the template. + // examples: + // - value: "\"3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H\"" + CVSSMetrics string `json:"cvss-metrics,omitempty" yaml:"cvss-metrics,omitempty" jsonschema:"title=cvss metrics for the template,description=CVSS Metrics for the template,example=3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"` + // description: | + // CVSS Score for the template. + // examples: + // - value: "\"9.8\"" + CVSSScore float64 `json:"cvss-score,omitempty" yaml:"cvss-score,omitempty" jsonschema:"title=cvss score for the template,description=CVSS Score for the template,example=9.8"` } diff --git a/v2/pkg/output/format_json.go b/v2/pkg/output/format_json.go index 814bab1fd..29fd41e0e 100644 --- a/v2/pkg/output/format_json.go +++ b/v2/pkg/output/format_json.go @@ -6,5 +6,9 @@ import ( // formatJSON formats the output for json based formatting func (w *StandardWriter) formatJSON(output *ResultEvent) ([]byte, error) { + if !w.jsonReqResp { // don't show request-response in json if not asked + output.Request = "" + output.Response = "" + } return jsoniter.Marshal(output) } diff --git a/v2/pkg/output/output.go b/v2/pkg/output/output.go index 7005a9ce4..176bafa61 100644 --- a/v2/pkg/output/output.go +++ b/v2/pkg/output/output.go @@ -33,6 +33,7 @@ type Writer interface { // StandardWriter is a writer writing output to file and screen for results. type StandardWriter struct { json bool + jsonReqResp bool noTimestamp bool noMetadata bool aurora aurora.Aurora @@ -94,7 +95,7 @@ type ResultEvent struct { } // NewStandardWriter creates a new output writer based on user configurations -func NewStandardWriter(colors, noMetadata, noTimestamp, json bool, file, traceFile string) (*StandardWriter, error) { +func NewStandardWriter(colors, noMetadata, noTimestamp, json, jsonReqResp bool, file, traceFile string) (*StandardWriter, error) { auroraColorizer := aurora.NewAurora(colors) var outputFile *fileWriter @@ -115,6 +116,7 @@ func NewStandardWriter(colors, noMetadata, noTimestamp, json bool, file, traceFi } writer := &StandardWriter{ json: json, + jsonReqResp: jsonReqResp, noMetadata: noMetadata, noTimestamp: noTimestamp, aurora: auroraColorizer, diff --git a/v2/pkg/protocols/dns/operators.go b/v2/pkg/protocols/dns/operators.go index 1120aff81..8fe38c939 100644 --- a/v2/pkg/protocols/dns/operators.go +++ b/v2/pkg/protocols/dns/operators.go @@ -147,10 +147,8 @@ func (r *Request) makeResultEventItem(wrapped *output.InternalWrappedEvent) *out Matched: types.ToString(wrapped.InternalEvent["matched"]), ExtractedResults: wrapped.OperatorsResult.OutputExtracts, Timestamp: time.Now(), - } - if r.options.Options.JSONRequests { - data.Request = types.ToString(wrapped.InternalEvent["request"]) - data.Response = types.ToString(wrapped.InternalEvent["raw"]) + Request: types.ToString(wrapped.InternalEvent["request"]), + Response: types.ToString(wrapped.InternalEvent["raw"]), } return data } diff --git a/v2/pkg/protocols/file/operators.go b/v2/pkg/protocols/file/operators.go index 4b8af5053..0202d5892 100644 --- a/v2/pkg/protocols/file/operators.go +++ b/v2/pkg/protocols/file/operators.go @@ -143,10 +143,8 @@ func (r *Request) makeResultEventItem(wrapped *output.InternalWrappedEvent) *out Matched: types.ToString(wrapped.InternalEvent["matched"]), Host: types.ToString(wrapped.InternalEvent["matched"]), ExtractedResults: wrapped.OperatorsResult.OutputExtracts, + Response: types.ToString(wrapped.InternalEvent["raw"]), Timestamp: time.Now(), } - if r.options.Options.JSONRequests { - data.Response = types.ToString(wrapped.InternalEvent["raw"]) - } return data } diff --git a/v2/pkg/protocols/headless/operators.go b/v2/pkg/protocols/headless/operators.go index d0a87aca6..4579c3c47 100644 --- a/v2/pkg/protocols/headless/operators.go +++ b/v2/pkg/protocols/headless/operators.go @@ -116,10 +116,8 @@ func (r *Request) makeResultEventItem(wrapped *output.InternalWrappedEvent) *out ExtractedResults: wrapped.OperatorsResult.OutputExtracts, Timestamp: time.Now(), IP: types.ToString(wrapped.InternalEvent["ip"]), - } - if r.options.Options.JSONRequests { - data.Request = types.ToString(wrapped.InternalEvent["request"]) - data.Response = types.ToString(wrapped.InternalEvent["data"]) + Request: types.ToString(wrapped.InternalEvent["request"]), + Response: types.ToString(wrapped.InternalEvent["data"]), } return data } diff --git a/v2/pkg/protocols/http/operators.go b/v2/pkg/protocols/http/operators.go index 81320de8f..83f1b77b7 100644 --- a/v2/pkg/protocols/http/operators.go +++ b/v2/pkg/protocols/http/operators.go @@ -154,10 +154,8 @@ func (r *Request) makeResultEventItem(wrapped *output.InternalWrappedEvent) *out ExtractedResults: wrapped.OperatorsResult.OutputExtracts, Timestamp: time.Now(), IP: types.ToString(wrapped.InternalEvent["ip"]), - } - if r.options.Options.JSONRequests { - data.Request = types.ToString(wrapped.InternalEvent["request"]) - data.Response = types.ToString(wrapped.InternalEvent["response"]) + Request: types.ToString(wrapped.InternalEvent["request"]), + Response: types.ToString(wrapped.InternalEvent["response"]), } return data } diff --git a/v2/pkg/protocols/network/operators.go b/v2/pkg/protocols/network/operators.go index c658dddca..807da5a61 100644 --- a/v2/pkg/protocols/network/operators.go +++ b/v2/pkg/protocols/network/operators.go @@ -118,10 +118,8 @@ func (r *Request) makeResultEventItem(wrapped *output.InternalWrappedEvent) *out Metadata: wrapped.OperatorsResult.PayloadValues, Timestamp: time.Now(), IP: types.ToString(wrapped.InternalEvent["ip"]), - } - if r.options.Options.JSONRequests { - data.Request = types.ToString(wrapped.InternalEvent["request"]) - data.Response = types.ToString(wrapped.InternalEvent["data"]) + Request: types.ToString(wrapped.InternalEvent["request"]), + Response: types.ToString(wrapped.InternalEvent["data"]), } return data } diff --git a/v2/pkg/protocols/offlinehttp/operators.go b/v2/pkg/protocols/offlinehttp/operators.go index 3acdbf4fa..1ab9e2ef3 100644 --- a/v2/pkg/protocols/offlinehttp/operators.go +++ b/v2/pkg/protocols/offlinehttp/operators.go @@ -145,10 +145,8 @@ func (r *Request) makeResultEventItem(wrapped *output.InternalWrappedEvent) *out Metadata: wrapped.OperatorsResult.PayloadValues, ExtractedResults: wrapped.OperatorsResult.OutputExtracts, IP: types.ToString(wrapped.InternalEvent["ip"]), - } - if r.options.Options.JSONRequests { - data.Request = types.ToString(wrapped.InternalEvent["request"]) - data.Response = types.ToString(wrapped.InternalEvent["raw"]) + Request: types.ToString(wrapped.InternalEvent["request"]), + Response: types.ToString(wrapped.InternalEvent["raw"]), } return data } diff --git a/v2/pkg/reporting/format/format.go b/v2/pkg/reporting/format/format.go index a90130020..88492d2d3 100644 --- a/v2/pkg/reporting/format/format.go +++ b/v2/pkg/reporting/format/format.go @@ -3,9 +3,12 @@ package format import ( "bytes" "fmt" - "github.com/projectdiscovery/nuclei/v2/pkg/utils" + "strconv" "strings" + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" + "github.com/projectdiscovery/nuclei/v2/pkg/utils" + "github.com/projectdiscovery/nuclei/v2/pkg/model" "github.com/projectdiscovery/nuclei/v2/pkg/output" "github.com/projectdiscovery/nuclei/v2/pkg/types" @@ -16,13 +19,10 @@ func Summary(event *output.ResultEvent) string { template := GetMatchedTemplate(event) builder := &strings.Builder{} - builder.WriteString("[") - builder.WriteString(template) - builder.WriteString("] [") - builder.WriteString(types.ToString(event.Info.SeverityHolder)) - builder.WriteString("] ") builder.WriteString(types.ToString(event.Info.Name)) - builder.WriteString(" found on ") + builder.WriteString(" (") + builder.WriteString(template) + builder.WriteString(") found on ") builder.WriteString(event.Host) data := builder.String() return data @@ -71,6 +71,7 @@ func MarkdownDescription(event *output.ResultEvent) string { // TODO remove the if len(event.ExtractedResults) > 0 || len(event.Metadata) > 0 { builder.WriteString("\n**Extra Information**\n\n") + if len(event.ExtractedResults) > 0 { builder.WriteString("**Extracted results**:\n\n") for _, v := range event.ExtractedResults { @@ -131,7 +132,7 @@ func MarkdownDescription(event *output.ResultEvent) string { // TODO remove the } } - builder.WriteString("\n---\nGenerated by [Nuclei](https://github.com/projectdiscovery/nuclei)") + builder.WriteString(fmt.Sprintf("\n---\nGenerated by [Nuclei %s](https://github.com/projectdiscovery/nuclei)", config.Version)) data := builder.String() return data } @@ -159,6 +160,16 @@ func ToMarkdownTableString(templateInfo *model.Info) string { fields.Set("Tags", templateInfo.Tags.String()) fields.Set("Severity", templateInfo.SeverityHolder.Severity.String()) fields.Set("Description", templateInfo.Description) + fields.Set("Remediation", templateInfo.Remediation) + + classification := templateInfo.Classification + if classification != nil { + if classification.CVSSMetrics != "" { + generateCVSSMetricsFromClassification(classification, fields) + } + generateCVECWEIDLinksFromClassification(classification, fields) + fields.Set("CVSS-Score", strconv.FormatFloat(classification.CVSSScore, 'f', 2, 64)) + } builder := &bytes.Buffer{} @@ -175,3 +186,44 @@ func ToMarkdownTableString(templateInfo *model.Info) string { return builder.String() } + +func generateCVSSMetricsFromClassification(classification *model.Classification, fields *utils.InsertionOrderedStringMap) { + // Generate cvss link + var cvssLinkPrefix string + if strings.Contains(classification.CVSSMetrics, "CVSS:3.0") { + cvssLinkPrefix = "https://www.first.org/cvss/calculator/3.0#" + } else if strings.Contains(classification.CVSSMetrics, "CVSS:3.1") { + cvssLinkPrefix = "https://www.first.org/cvss/calculator/3.1#" + } + if cvssLinkPrefix != "" { + fields.Set("CVSS-Metrics", fmt.Sprintf("[%s](%s%s)", classification.CVSSMetrics, cvssLinkPrefix, classification.CVSSMetrics)) + } else { + fields.Set("CVSS-Metrics", classification.CVSSMetrics) + } +} + +func generateCVECWEIDLinksFromClassification(classification *model.Classification, fields *utils.InsertionOrderedStringMap) { + cwes := classification.CWEID.ToSlice() + + cweIDs := make([]string, 0, len(cwes)) + for _, value := range cwes { + parts := strings.Split(value, "-") + if len(parts) != 2 { + continue + } + cweIDs = append(cweIDs, fmt.Sprintf("[%s](https://cwe.mitre.org/data/definitions/%s.html)", strings.ToUpper(value), parts[1])) + } + if len(cweIDs) > 0 { + fields.Set("CWE-ID", strings.Join(cweIDs, ",")) + } + + cves := classification.CVEID.ToSlice() + + cveIDs := make([]string, 0, len(cves)) + for _, value := range cves { + cveIDs = append(cveIDs, fmt.Sprintf("[%s](https://cve.mitre.org/cgi-bin/cvename.cgi?name=%s)", strings.ToUpper(value), value)) + } + if len(cveIDs) > 0 { + fields.Set("CVE-ID", strings.Join(cveIDs, ",")) + } +} diff --git a/v2/pkg/reporting/trackers/jira/jira.go b/v2/pkg/reporting/trackers/jira/jira.go index a3d50db55..1bfe2edd5 100644 --- a/v2/pkg/reporting/trackers/jira/jira.go +++ b/v2/pkg/reporting/trackers/jira/jira.go @@ -8,6 +8,7 @@ import ( "github.com/andygrunwald/go-jira" "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/nuclei/v2/pkg/catalog/config" "github.com/projectdiscovery/nuclei/v2/pkg/output" "github.com/projectdiscovery/nuclei/v2/pkg/reporting/format" "github.com/projectdiscovery/nuclei/v2/pkg/types" @@ -240,7 +241,7 @@ func jiraFormatDescription(event *output.ResultEvent) string { // TODO remove th } } } - builder.WriteString("\n---\nGenerated by [Nuclei|https://github.com/projectdiscovery/nuclei]") + builder.WriteString(fmt.Sprintf("\n---\nGenerated by [Nuclei v%s](https://github.com/projectdiscovery/nuclei)", config.Version)) data := builder.String() return data }