2021-09-09 18:55:25 +05:30
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
2022-05-14 14:06:48 +02:00
|
|
|
"bytes"
|
2021-09-09 18:55:25 +05:30
|
|
|
"flag"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io/ioutil"
|
|
|
|
|
"log"
|
|
|
|
|
"os"
|
|
|
|
|
"regexp"
|
|
|
|
|
"strings"
|
|
|
|
|
|
2022-03-14 12:37:43 +05:30
|
|
|
"github.com/projectdiscovery/nvd"
|
2022-05-14 14:06:48 +02:00
|
|
|
"github.com/projectdiscovery/sliceutil"
|
|
|
|
|
"github.com/projectdiscovery/stringsutil"
|
|
|
|
|
"gopkg.in/yaml.v3"
|
2021-11-24 17:09:38 +02:00
|
|
|
|
2021-09-09 18:55:25 +05:30
|
|
|
"github.com/projectdiscovery/nuclei/v2/pkg/catalog"
|
|
|
|
|
)
|
|
|
|
|
|
2022-05-14 14:06:48 +02:00
|
|
|
const (
|
|
|
|
|
yamlIndentSpaces = 2
|
|
|
|
|
)
|
|
|
|
|
|
2021-09-09 18:55:25 +05:30
|
|
|
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 {
|
2022-02-23 13:54:46 +01:00
|
|
|
data, err := os.ReadFile(path)
|
2021-09-09 18:55:25 +05:30
|
|
|
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]+)`)
|
|
|
|
|
)
|
|
|
|
|
|
2022-05-14 14:06:48 +02:00
|
|
|
const maxReferenceCount = 5
|
|
|
|
|
|
2022-06-14 01:34:00 -04:00
|
|
|
// dead sites to skip for references
|
2022-07-08 10:58:22 -04:00
|
|
|
var badRefs = []string{
|
|
|
|
|
"osvdb.org/",
|
|
|
|
|
"securityfocus.com/",
|
|
|
|
|
"archives.neohapsis.com/",
|
|
|
|
|
"iss.net/",
|
|
|
|
|
"ntelbras.com/",
|
|
|
|
|
"andmp.com/",
|
|
|
|
|
"blacklanternsecurity.com/",
|
|
|
|
|
"pwnwiki.org/",
|
|
|
|
|
"0dayhack.net/",
|
|
|
|
|
"correkt.horse/",
|
|
|
|
|
"poc.wgpsec.org/",
|
|
|
|
|
"ctf-writeup.revers3c.com/",
|
2022-06-14 01:34:00 -04:00
|
|
|
}
|
|
|
|
|
|
2021-09-09 18:55:25 +05:30
|
|
|
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)
|
2021-12-20 12:17:04 +01:00
|
|
|
if len(severityMatches) == 0 {
|
2021-09-09 18:55:25 +05:30
|
|
|
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:")
|
2022-05-14 14:06:48 +02:00
|
|
|
variablesIndex := strings.Index(infoBlockIndexData, "variables:")
|
|
|
|
|
if requestsIndex == -1 && networkIndex == -1 && variablesIndex == -1 {
|
2021-09-09 18:55:25 +05:30
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if networkIndex != -1 {
|
|
|
|
|
requestsIndex = networkIndex
|
|
|
|
|
}
|
2022-05-14 14:06:48 +02:00
|
|
|
if variablesIndex != -1 {
|
|
|
|
|
requestsIndex = variablesIndex
|
|
|
|
|
}
|
2021-09-09 18:55:25 +05:30
|
|
|
infoBlockData := infoBlockIndexData[:requestsIndex]
|
|
|
|
|
infoBlockClean := strings.TrimRight(infoBlockData, "\n")
|
|
|
|
|
|
2022-05-14 14:06:48 +02:00
|
|
|
infoBlock := InfoBlock{}
|
|
|
|
|
err = yaml.Unmarshal([]byte(data), &infoBlock)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Could not unmarshal info block: %s\n", err)
|
|
|
|
|
}
|
2021-09-09 18:55:25 +05:30
|
|
|
|
2022-05-14 14:06:48 +02:00
|
|
|
var changed bool
|
2021-09-09 18:55:25 +05:30
|
|
|
if newSeverity := isSeverityMatchingCvssScore(severityValue, cvssScore); newSeverity != "" {
|
|
|
|
|
changed = true
|
2022-05-14 14:06:48 +02:00
|
|
|
infoBlock.Info.Severity = newSeverity
|
2021-09-09 18:55:25 +05:30
|
|
|
fmt.Printf("Adjusting severity for %s from %s=>%s (%.2f)\n", filePath, severityValue, newSeverity, cvssScore)
|
|
|
|
|
}
|
2022-05-14 14:06:48 +02:00
|
|
|
isCvssEmpty := cvssScore == 0 || cvssMetrics == ""
|
|
|
|
|
hasCvssChanged := infoBlock.Info.Classification.CvssScore != cvssScore || cvssMetrics != infoBlock.Info.Classification.CvssMetrics
|
|
|
|
|
if !isCvssEmpty && hasCvssChanged {
|
2021-09-09 18:55:25 +05:30
|
|
|
changed = true
|
2022-05-14 14:06:48 +02:00
|
|
|
infoBlock.Info.Classification.CvssMetrics = cvssMetrics
|
|
|
|
|
infoBlock.Info.Classification.CvssScore = cvssScore
|
|
|
|
|
infoBlock.Info.Classification.CveId = cveName
|
2021-09-09 18:55:25 +05:30
|
|
|
if len(cweID) > 0 && (cweID[0] != "NVD-CWE-Other" && cweID[0] != "NVD-CWE-noinfo") {
|
2022-05-14 14:06:48 +02:00
|
|
|
infoBlock.Info.Classification.CweId = strings.Join(cweID, ",")
|
2021-09-09 18:55:25 +05:30
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// If there is no description field, fill the description from CVE information
|
2022-05-14 14:06:48 +02:00
|
|
|
hasDescriptionData := len(cveItem.CVE.Description.DescriptionData) > 0
|
|
|
|
|
isDescriptionEmpty := infoBlock.Info.Description == ""
|
|
|
|
|
if isDescriptionEmpty && hasDescriptionData {
|
2021-09-09 18:55:25 +05:30
|
|
|
changed = true
|
2022-05-14 14:06:48 +02:00
|
|
|
// removes all new lines
|
|
|
|
|
description := stringsutil.ReplaceAny(cveItem.CVE.Description.DescriptionData[0].Value, "", "\n", "\\", "'", "\t")
|
|
|
|
|
description += "\n"
|
|
|
|
|
infoBlock.Info.Description = description
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// we are unmarshaling info block to have valid data
|
|
|
|
|
var referenceDataURLs []string
|
2022-06-14 01:34:00 -04:00
|
|
|
|
|
|
|
|
// skip sites that are no longer alive
|
2022-05-14 14:06:48 +02:00
|
|
|
for _, reference := range cveItem.CVE.References.ReferenceData {
|
2022-07-08 10:58:22 -04:00
|
|
|
if stringsutil.ContainsAny(reference.URL, badRefs...) {
|
|
|
|
|
continue
|
2022-06-14 01:34:00 -04:00
|
|
|
}
|
2022-07-08 10:58:22 -04:00
|
|
|
referenceDataURLs = append(referenceDataURLs, reference.URL)
|
2021-09-09 18:55:25 +05:30
|
|
|
}
|
2022-05-14 14:06:48 +02:00
|
|
|
hasReferenceData := len(cveItem.CVE.References.ReferenceData) > 0
|
|
|
|
|
areCveReferencesContained := sliceutil.ContainsItems(infoBlock.Info.Reference, referenceDataURLs)
|
|
|
|
|
referencesCount := len(infoBlock.Info.Reference)
|
|
|
|
|
if hasReferenceData && !areCveReferencesContained {
|
2021-09-09 18:55:25 +05:30
|
|
|
changed = true
|
2022-06-14 01:34:00 -04:00
|
|
|
for _, ref := range referenceDataURLs {
|
2022-05-14 14:06:48 +02:00
|
|
|
referencesCount++
|
|
|
|
|
if referencesCount >= maxReferenceCount {
|
|
|
|
|
break
|
|
|
|
|
}
|
2022-06-14 01:34:00 -04:00
|
|
|
infoBlock.Info.Reference = append(infoBlock.Info.Reference, ref)
|
2021-09-09 18:55:25 +05:30
|
|
|
}
|
2022-05-14 14:06:48 +02:00
|
|
|
infoBlock.Info.Reference = sliceutil.PruneEmptyStrings(sliceutil.Dedupe(infoBlock.Info.Reference))
|
2021-09-09 18:55:25 +05:30
|
|
|
}
|
2022-05-14 14:06:48 +02:00
|
|
|
|
|
|
|
|
var newInfoBlock bytes.Buffer
|
|
|
|
|
yamlEncoder := yaml.NewEncoder(&newInfoBlock)
|
|
|
|
|
yamlEncoder.SetIndent(yamlIndentSpaces)
|
|
|
|
|
err = yamlEncoder.Encode(infoBlock)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("Could not marshal info block: %s\n", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
newInfoBlockData := strings.TrimSuffix(newInfoBlock.String(), "\n")
|
|
|
|
|
|
|
|
|
|
newTemplate := strings.ReplaceAll(data, infoBlockClean, newInfoBlockData)
|
2021-09-09 18:55:25 +05:30
|
|
|
if changed {
|
2021-11-24 17:09:38 +02:00
|
|
|
_ = ioutil.WriteFile(filePath, []byte(newTemplate), 0644)
|
2021-09-09 18:55:25 +05:30
|
|
|
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 ""
|
|
|
|
|
}
|
2022-05-14 14:06:48 +02:00
|
|
|
|
|
|
|
|
// Cloning struct from nuclei as we don't want any validation
|
|
|
|
|
type InfoBlock struct {
|
|
|
|
|
Info TemplateInfo `yaml:"info"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type TemplateClassification struct {
|
|
|
|
|
CvssMetrics string `yaml:"cvss-metrics,omitempty"`
|
|
|
|
|
CvssScore float64 `yaml:"cvss-score,omitempty"`
|
|
|
|
|
CveId string `yaml:"cve-id,omitempty"`
|
|
|
|
|
CweId string `yaml:"cwe-id,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type TemplateInfo struct {
|
|
|
|
|
Name string `yaml:"name"`
|
|
|
|
|
Author string `yaml:"author"`
|
|
|
|
|
Severity string `yaml:"severity"`
|
|
|
|
|
Description string `yaml:"description,omitempty"`
|
|
|
|
|
Reference []string `yaml:"reference,omitempty"`
|
|
|
|
|
Remediation string `yaml:"remediation,omitempty"`
|
|
|
|
|
Classification TemplateClassification `yaml:"classification,omitempty"`
|
|
|
|
|
Metadata map[string]string `yaml:"metadata,omitempty"`
|
|
|
|
|
Tags string `yaml:"tags,omitempty"`
|
|
|
|
|
}
|