mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-18 04:55:28 +00:00
fixed jira formatting issue
This commit is contained in:
parent
a4859df5e9
commit
51cf69d7c4
@ -28,6 +28,10 @@ func (markdownFormatter MarkdownFormatter) CreateHorizontalLine() string {
|
||||
return CreateHorizontalLine()
|
||||
}
|
||||
|
||||
func (markdownFormatter MarkdownFormatter) FormatLineBreaks(text string) string {
|
||||
return strings.ReplaceAll(text, "\n", "<br>")
|
||||
}
|
||||
|
||||
// escapeCodeBlockMarkdown only escapes the bare minimum characters needed
|
||||
// for code blocks and other sections where readability is important
|
||||
//
|
||||
|
||||
@ -6,4 +6,5 @@ type ResultFormatter interface {
|
||||
CreateTable(headers []string, rows [][]string) (string, error)
|
||||
CreateLink(title string, url string) string
|
||||
CreateHorizontalLine() string
|
||||
FormatLineBreaks(text string) string
|
||||
}
|
||||
|
||||
@ -9,7 +9,6 @@ import (
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/model"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/types"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
|
||||
unitutils "github.com/projectdiscovery/utils/unit"
|
||||
@ -171,20 +170,20 @@ func CreateTemplateInfoTable(templateInfo *model.Info, formatter ResultFormatter
|
||||
}
|
||||
|
||||
if !utils.IsBlank(templateInfo.Description) {
|
||||
rows = append(rows, []string{"Description", lineBreakToHTML(templateInfo.Description)})
|
||||
rows = append(rows, []string{"Description", formatter.FormatLineBreaks(templateInfo.Description)})
|
||||
}
|
||||
|
||||
if !utils.IsBlank(templateInfo.Remediation) {
|
||||
rows = append(rows, []string{"Remediation", lineBreakToHTML(templateInfo.Remediation)})
|
||||
rows = append(rows, []string{"Remediation", formatter.FormatLineBreaks(templateInfo.Remediation)})
|
||||
}
|
||||
|
||||
classification := templateInfo.Classification
|
||||
if classification != nil {
|
||||
if classification.CVSSMetrics != "" {
|
||||
rows = append(rows, []string{"CVSS-Metrics", generateCVSSMetricsFromClassification(classification)})
|
||||
rows = append(rows, []string{"CVSS-Metrics", generateCVSSMetricsFromClassification(classification, formatter)})
|
||||
}
|
||||
|
||||
rows = append(rows, generateCVECWEIDLinksFromClassification(classification)...)
|
||||
rows = append(rows, generateCVECWEIDLinksFromClassification(classification, formatter)...)
|
||||
rows = append(rows, []string{"CVSS-Score", strconv.FormatFloat(classification.CVSSScore, 'f', 2, 64)})
|
||||
}
|
||||
|
||||
@ -202,7 +201,7 @@ func CreateTemplateInfoTable(templateInfo *model.Info, formatter ResultFormatter
|
||||
return table
|
||||
}
|
||||
|
||||
func generateCVSSMetricsFromClassification(classification *model.Classification) string {
|
||||
func generateCVSSMetricsFromClassification(classification *model.Classification, formatter ResultFormatter) string {
|
||||
var cvssLinkPrefix string
|
||||
if strings.Contains(classification.CVSSMetrics, "CVSS:3.0") {
|
||||
cvssLinkPrefix = "https://www.first.org/cvss/calculator/3.0#"
|
||||
@ -213,11 +212,11 @@ func generateCVSSMetricsFromClassification(classification *model.Classification)
|
||||
if cvssLinkPrefix == "" {
|
||||
return classification.CVSSMetrics
|
||||
} else {
|
||||
return util.CreateLink(classification.CVSSMetrics, cvssLinkPrefix+classification.CVSSMetrics)
|
||||
return formatter.CreateLink(classification.CVSSMetrics, cvssLinkPrefix+classification.CVSSMetrics)
|
||||
}
|
||||
}
|
||||
|
||||
func generateCVECWEIDLinksFromClassification(classification *model.Classification) [][]string {
|
||||
func generateCVECWEIDLinksFromClassification(classification *model.Classification, formatter ResultFormatter) [][]string {
|
||||
cwes := classification.CWEID.ToSlice()
|
||||
|
||||
cweIDs := make([]string, 0, len(cwes))
|
||||
@ -226,7 +225,7 @@ func generateCVECWEIDLinksFromClassification(classification *model.Classificatio
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
cweIDs = append(cweIDs, util.CreateLink(strings.ToUpper(value), fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html", parts[1])))
|
||||
cweIDs = append(cweIDs, formatter.CreateLink(strings.ToUpper(value), fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html", parts[1])))
|
||||
}
|
||||
|
||||
var rows [][]string
|
||||
@ -238,7 +237,7 @@ func generateCVECWEIDLinksFromClassification(classification *model.Classificatio
|
||||
cves := classification.CVEID.ToSlice()
|
||||
cveIDs := make([]string, 0, len(cves))
|
||||
for _, value := range cves {
|
||||
cveIDs = append(cveIDs, util.CreateLink(strings.ToUpper(value), fmt.Sprintf("https://cve.mitre.org/cgi-bin/cvename.cgi?name=%s", value)))
|
||||
cveIDs = append(cveIDs, formatter.CreateLink(strings.ToUpper(value), fmt.Sprintf("https://cve.mitre.org/cgi-bin/cvename.cgi?name=%s", value)))
|
||||
}
|
||||
if len(cveIDs) > 0 {
|
||||
rows = append(rows, []string{"CVE-ID", strings.Join(cveIDs, ",")})
|
||||
|
||||
@ -35,17 +35,57 @@ func (jiraFormatter *Formatter) CreateCodeBlock(title string, content string, _
|
||||
}
|
||||
|
||||
func (jiraFormatter *Formatter) CreateTable(headers []string, rows [][]string) (string, error) {
|
||||
table, err := jiraFormatter.MarkdownFormatter.CreateTable(headers, rows)
|
||||
if err != nil {
|
||||
return "", err
|
||||
if len(headers) == 0 {
|
||||
return "", fmt.Errorf("no headers provided")
|
||||
}
|
||||
tableRows := strings.Split(table, "\n")
|
||||
tableRowsWithoutHeaderSeparator := append(tableRows[:1], tableRows[2:]...)
|
||||
return strings.Join(tableRowsWithoutHeaderSeparator, "\n"), nil
|
||||
|
||||
var builder strings.Builder
|
||||
|
||||
// Create header row with leading and trailing pipes
|
||||
builder.WriteString("| ")
|
||||
builder.WriteString(strings.Join(headers, " | "))
|
||||
builder.WriteString(" |")
|
||||
builder.WriteString("\n")
|
||||
|
||||
// Create separator row with leading and trailing pipes
|
||||
separators := make([]string, len(headers))
|
||||
for i := range separators {
|
||||
separators[i] = "-----------"
|
||||
}
|
||||
builder.WriteString("|")
|
||||
builder.WriteString(strings.Join(separators, "|"))
|
||||
builder.WriteString("|")
|
||||
builder.WriteString("\n")
|
||||
|
||||
// Create data rows with leading and trailing pipes
|
||||
for _, row := range rows {
|
||||
builder.WriteString("| ")
|
||||
if len(row) < len(headers) {
|
||||
extendedRow := make([]string, len(headers))
|
||||
copy(extendedRow, row)
|
||||
builder.WriteString(strings.Join(extendedRow, " | "))
|
||||
} else if len(row) > len(headers) {
|
||||
builder.WriteString(strings.Join(row[:len(headers)], " | "))
|
||||
} else {
|
||||
builder.WriteString(strings.Join(row, " | "))
|
||||
}
|
||||
builder.WriteString(" |")
|
||||
builder.WriteString("\n")
|
||||
}
|
||||
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
func (jiraFormatter *Formatter) CreateHorizontalLine() string {
|
||||
return "----\n"
|
||||
}
|
||||
|
||||
func (jiraFormatter *Formatter) FormatLineBreaks(text string) string {
|
||||
return strings.ReplaceAll(text, "\n", "\\\\")
|
||||
}
|
||||
|
||||
func (jiraFormatter *Formatter) CreateLink(title string, url string) string {
|
||||
return fmt.Sprintf("[%s|%s]", title, url)
|
||||
return fmt.Sprintf("[%s](%s)", title, url)
|
||||
}
|
||||
|
||||
// Integration is a client for an issue tracker integration
|
||||
|
||||
@ -3,10 +3,13 @@ package jira
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/model"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/format"
|
||||
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -14,7 +17,18 @@ import (
|
||||
func TestLinkCreation(t *testing.T) {
|
||||
jiraIntegration := &Integration{}
|
||||
link := jiraIntegration.CreateLink("ProjectDiscovery", "https://projectdiscovery.io")
|
||||
require.Equal(t, "[ProjectDiscovery|https://projectdiscovery.io]", link)
|
||||
require.Equal(t, "[ProjectDiscovery](https://projectdiscovery.io)", link)
|
||||
}
|
||||
|
||||
func TestLinkCreationWithSpecialCharacters(t *testing.T) {
|
||||
jiraIntegration := &Integration{}
|
||||
|
||||
link := jiraIntegration.CreateLink("Nuclei [v3.4.4]", "https://github.com/projectdiscovery/nuclei")
|
||||
expected := "[Nuclei [v3.4.4]](https://github.com/projectdiscovery/nuclei)"
|
||||
require.Equal(t, expected, link)
|
||||
|
||||
require.NotContains(t, link, "%5D")
|
||||
require.NotContains(t, link, "%5B")
|
||||
}
|
||||
|
||||
func TestHorizontalLineCreation(t *testing.T) {
|
||||
@ -34,6 +48,7 @@ func TestTableCreation(t *testing.T) {
|
||||
|
||||
require.Nil(t, err)
|
||||
expected := `| key | value |
|
||||
|-----------|-----------|
|
||||
| a | b |
|
||||
| c | |
|
||||
| d | e |
|
||||
@ -41,6 +56,129 @@ func TestTableCreation(t *testing.T) {
|
||||
require.Equal(t, expected, table)
|
||||
}
|
||||
|
||||
func TestTableCreationWithComplexData(t *testing.T) {
|
||||
jiraIntegration := &Integration{}
|
||||
|
||||
table, err := jiraIntegration.CreateTable([]string{"Key", "Value"}, [][]string{
|
||||
{"Name", "GraphQL CSRF / GET method"},
|
||||
{"Authors", "Dolev Farhi"},
|
||||
{"Tags", "graphql, misconfig"},
|
||||
{"Severity", "info"},
|
||||
})
|
||||
|
||||
require.Nil(t, err)
|
||||
|
||||
require.Contains(t, table, "| Key | Value |")
|
||||
require.Contains(t, table, "|-----------|-----------|")
|
||||
require.Contains(t, table, "| Name | GraphQL CSRF / GET method |")
|
||||
|
||||
require.Contains(t, table, "GraphQL CSRF")
|
||||
require.NotContains(t, table, "| ame |")
|
||||
require.NotContains(t, table, "| uthors |")
|
||||
}
|
||||
|
||||
func TestFormatLineBreaks(t *testing.T) {
|
||||
jiraIntegration := &Integration{}
|
||||
|
||||
input := "Line 1\nLine 2\nLine 3"
|
||||
result := jiraIntegration.FormatLineBreaks(input)
|
||||
expected := "Line 1\\\\Line 2\\\\Line 3"
|
||||
|
||||
require.Equal(t, expected, result)
|
||||
|
||||
require.NotContains(t, result, "<br>")
|
||||
}
|
||||
|
||||
func TestFormatLineBreaksWithMultipleBreaks(t *testing.T) {
|
||||
jiraIntegration := &Integration{}
|
||||
|
||||
input := "Cross Site Request Forgery happens when an external website gains ability to make API calls impersonating an user.\nAllowing API calls through GET requests can lead to CSRF attacks."
|
||||
result := jiraIntegration.FormatLineBreaks(input)
|
||||
expected := "Cross Site Request Forgery happens when an external website gains ability to make API calls impersonating an user.\\\\Allowing API calls through GET requests can lead to CSRF attacks."
|
||||
|
||||
require.Equal(t, expected, result)
|
||||
}
|
||||
|
||||
func TestCompleteReportGeneration(t *testing.T) {
|
||||
jiraIntegration := &Integration{}
|
||||
|
||||
event := &output.ResultEvent{
|
||||
TemplateID: "graphql-get-method",
|
||||
Info: model.Info{
|
||||
Name: "GraphQL CSRF / GET method",
|
||||
Authors: stringslice.StringSlice{Value: []string{"Dolev Farhi"}},
|
||||
Tags: stringslice.StringSlice{Value: []string{"graphql", "misconfig"}},
|
||||
SeverityHolder: severity.Holder{Severity: severity.Info},
|
||||
Description: "Cross Site Request Forgery happens when an external website gains ability to make API calls impersonating an user.\nAllowing API calls through GET requests can lead to CSRF attacks.",
|
||||
Reference: stringslice.NewRawStringSlice([]string{
|
||||
"https://graphql.org/learn/serving-over-http/#get-request",
|
||||
"https://github.com/dolevf/Damn-Vulnerable-GraphQL-Application",
|
||||
}),
|
||||
},
|
||||
Host: "example.com",
|
||||
Matched: "example.com/graphql",
|
||||
Timestamp: time.Date(2025, 5, 31, 12, 0, 0, 0, time.UTC),
|
||||
Type: "http",
|
||||
}
|
||||
|
||||
description := format.CreateReportDescription(event, jiraIntegration, false)
|
||||
|
||||
require.Contains(t, description, "*Details*: *graphql-get-method* matched at example.com")
|
||||
require.Contains(t, description, "*Protocol*: HTTP")
|
||||
require.Contains(t, description, "*Template Information*")
|
||||
|
||||
require.Contains(t, description, "| Key | Value |")
|
||||
require.Contains(t, description, "|-----------|-----------|")
|
||||
require.Contains(t, description, "| Name | GraphQL CSRF / GET method |")
|
||||
|
||||
require.Contains(t, description, "impersonating an user.\\\\Allowing API calls")
|
||||
require.NotContains(t, description, "<br>")
|
||||
|
||||
require.Contains(t, description, "[Nuclei")
|
||||
require.Contains(t, description, "](https://github.com/projectdiscovery/nuclei)")
|
||||
|
||||
require.NotContains(t, description, "%5D")
|
||||
require.NotContains(t, description, "%5B")
|
||||
|
||||
require.Contains(t, description, "References:")
|
||||
require.Contains(t, description, "https://graphql.org/learn/serving-over-http/#get-request")
|
||||
}
|
||||
|
||||
func TestReportWithCVELinks(t *testing.T) {
|
||||
jiraIntegration := &Integration{}
|
||||
|
||||
event := &output.ResultEvent{
|
||||
TemplateID: "test-cve",
|
||||
Info: model.Info{
|
||||
Name: "Test CVE Template",
|
||||
Authors: stringslice.StringSlice{Value: []string{"test-author"}},
|
||||
Tags: stringslice.StringSlice{Value: []string{"cve", "test"}},
|
||||
SeverityHolder: severity.Holder{Severity: severity.High},
|
||||
Description: "Test template with CVE links",
|
||||
Classification: &model.Classification{
|
||||
CVEID: stringslice.StringSlice{Value: []string{"CVE-2021-44228"}},
|
||||
CWEID: stringslice.StringSlice{Value: []string{"CWE-502"}},
|
||||
CVSSMetrics: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H",
|
||||
CVSSScore: 10.0,
|
||||
},
|
||||
},
|
||||
Host: "example.com",
|
||||
Matched: "example.com/test",
|
||||
Timestamp: time.Date(2025, 5, 31, 12, 0, 0, 0, time.UTC),
|
||||
Type: "http",
|
||||
}
|
||||
|
||||
description := format.CreateReportDescription(event, jiraIntegration, false)
|
||||
|
||||
require.Contains(t, description, "[CVE-2021-44228](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-44228)")
|
||||
require.Contains(t, description, "[CWE-502](https://cwe.mitre.org/data/definitions/502.html)")
|
||||
require.Contains(t, description, "[CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H](https://www.first.org/cvss/calculator/3.1#CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)")
|
||||
|
||||
require.NotContains(t, description, "|https://")
|
||||
|
||||
require.NotContains(t, description, "%5D")
|
||||
}
|
||||
|
||||
func Test_ShouldFilter_Tracker(t *testing.T) {
|
||||
jiraIntegration := &Integration{
|
||||
options: &Options{AllowList: &filters.Filter{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user