package format import ( "bytes" "fmt" "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" ) // Summary returns a formatted built one line summary of the event func Summary(event *output.ResultEvent) string { template := GetMatchedTemplate(event) builder := &strings.Builder{} builder.WriteString(types.ToString(event.Info.Name)) builder.WriteString(" (") builder.WriteString(template) builder.WriteString(") found on ") builder.WriteString(event.Host) data := builder.String() return data } // MarkdownDescription formats a short description of the generated // event by the nuclei scanner in Markdown format. func MarkdownDescription(event *output.ResultEvent) string { // TODO remove the code duplication: format.go <-> jira.go template := GetMatchedTemplate(event) builder := &bytes.Buffer{} builder.WriteString("**Details**: **") builder.WriteString(template) builder.WriteString("** ") builder.WriteString(" matched at ") builder.WriteString(event.Host) builder.WriteString("\n\n**Protocol**: ") builder.WriteString(strings.ToUpper(event.Type)) builder.WriteString("\n\n**Full URL**: ") builder.WriteString(event.Matched) builder.WriteString("\n\n**Timestamp**: ") builder.WriteString(event.Timestamp.Format("Mon Jan 2 15:04:05 -0700 MST 2006")) builder.WriteString("\n\n**Template Information**\n\n| Key | Value |\n|---|---|\n") builder.WriteString(ToMarkdownTableString(&event.Info)) 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") } 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 { builder.WriteString("- ") builder.WriteString(v) builder.WriteString("\n") } builder.WriteString("\n") } if len(event.Metadata) > 0 { builder.WriteString("**Metadata**:\n\n") for k, v := range event.Metadata { builder.WriteString("- ") builder.WriteString(k) builder.WriteString(": ") builder.WriteString(types.ToString(v)) builder.WriteString("\n") } builder.WriteString("\n") } } if event.Interaction != nil { builder.WriteString("**Interaction Data**\n---\n") builder.WriteString(event.Interaction.Protocol) if event.Interaction.QType != "" { builder.WriteString(" (") builder.WriteString(event.Interaction.QType) builder.WriteString(")") } builder.WriteString(" Interaction from ") builder.WriteString(event.Interaction.RemoteAddress) builder.WriteString(" at ") builder.WriteString(event.Interaction.UniqueID) if event.Interaction.RawRequest != "" { builder.WriteString("\n\n**Interaction Request**\n\n```\n") builder.WriteString(event.Interaction.RawRequest) builder.WriteString("\n```\n") } if event.Interaction.RawResponse != "" { builder.WriteString("\n**Interaction Response**\n\n```\n") builder.WriteString(event.Interaction.RawResponse) builder.WriteString("\n```\n") } } reference := event.Info.Reference if !reference.IsEmpty() { builder.WriteString("\nReferences: \n") referenceSlice := reference.ToSlice() for i, item := range referenceSlice { builder.WriteString("- ") builder.WriteString(item) if len(referenceSlice)-1 != i { builder.WriteString("\n") } } } builder.WriteString(fmt.Sprintf("\n---\nGenerated by [Nuclei %s](https://github.com/projectdiscovery/nuclei)", config.Version)) data := builder.String() return data } // GetMatchedTemplate returns the matched template from a result event func GetMatchedTemplate(event *output.ResultEvent) string { builder := &strings.Builder{} builder.WriteString(event.TemplateID) if event.MatcherName != "" { builder.WriteString(":") builder.WriteString(event.MatcherName) } if event.ExtractorName != "" { builder.WriteString(":") builder.WriteString(event.ExtractorName) } template := builder.String() return template } func ToMarkdownTableString(templateInfo *model.Info) string { fields := utils.NewEmptyInsertionOrderedStringMap(5) fields.Set("Name", templateInfo.Name) fields.Set("Authors", templateInfo.Authors.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{} toMarkDownTable := func(insertionOrderedStringMap *utils.InsertionOrderedStringMap) { insertionOrderedStringMap.ForEach(func(key string, value string) { if utils.IsNotBlank(value) { builder.WriteString(fmt.Sprintf("| %s | %s |\n", key, value)) } }) } toMarkDownTable(fields) toMarkDownTable(utils.NewInsertionOrderedStringMap(templateInfo.AdditionalFields)) 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, ",")) } }