mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-24 05:35:27 +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()
|
return CreateHorizontalLine()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (markdownFormatter MarkdownFormatter) FormatLineBreaks(text string) string {
|
||||||
|
return strings.ReplaceAll(text, "\n", "<br>")
|
||||||
|
}
|
||||||
|
|
||||||
// escapeCodeBlockMarkdown only escapes the bare minimum characters needed
|
// escapeCodeBlockMarkdown only escapes the bare minimum characters needed
|
||||||
// for code blocks and other sections where readability is important
|
// for code blocks and other sections where readability is important
|
||||||
//
|
//
|
||||||
|
|||||||
@ -6,4 +6,5 @@ type ResultFormatter interface {
|
|||||||
CreateTable(headers []string, rows [][]string) (string, error)
|
CreateTable(headers []string, rows [][]string) (string, error)
|
||||||
CreateLink(title string, url string) string
|
CreateLink(title string, url string) string
|
||||||
CreateHorizontalLine() 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/catalog/config"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/model"
|
"github.com/projectdiscovery/nuclei/v3/pkg/model"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
"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/types"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
|
"github.com/projectdiscovery/nuclei/v3/pkg/utils"
|
||||||
unitutils "github.com/projectdiscovery/utils/unit"
|
unitutils "github.com/projectdiscovery/utils/unit"
|
||||||
@ -171,20 +170,20 @@ func CreateTemplateInfoTable(templateInfo *model.Info, formatter ResultFormatter
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !utils.IsBlank(templateInfo.Description) {
|
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) {
|
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
|
classification := templateInfo.Classification
|
||||||
if classification != nil {
|
if classification != nil {
|
||||||
if classification.CVSSMetrics != "" {
|
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)})
|
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
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateCVSSMetricsFromClassification(classification *model.Classification) string {
|
func generateCVSSMetricsFromClassification(classification *model.Classification, formatter ResultFormatter) string {
|
||||||
var cvssLinkPrefix string
|
var cvssLinkPrefix string
|
||||||
if strings.Contains(classification.CVSSMetrics, "CVSS:3.0") {
|
if strings.Contains(classification.CVSSMetrics, "CVSS:3.0") {
|
||||||
cvssLinkPrefix = "https://www.first.org/cvss/calculator/3.0#"
|
cvssLinkPrefix = "https://www.first.org/cvss/calculator/3.0#"
|
||||||
@ -213,11 +212,11 @@ func generateCVSSMetricsFromClassification(classification *model.Classification)
|
|||||||
if cvssLinkPrefix == "" {
|
if cvssLinkPrefix == "" {
|
||||||
return classification.CVSSMetrics
|
return classification.CVSSMetrics
|
||||||
} else {
|
} 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()
|
cwes := classification.CWEID.ToSlice()
|
||||||
|
|
||||||
cweIDs := make([]string, 0, len(cwes))
|
cweIDs := make([]string, 0, len(cwes))
|
||||||
@ -226,7 +225,7 @@ func generateCVECWEIDLinksFromClassification(classification *model.Classificatio
|
|||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
continue
|
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
|
var rows [][]string
|
||||||
@ -238,7 +237,7 @@ func generateCVECWEIDLinksFromClassification(classification *model.Classificatio
|
|||||||
cves := classification.CVEID.ToSlice()
|
cves := classification.CVEID.ToSlice()
|
||||||
cveIDs := make([]string, 0, len(cves))
|
cveIDs := make([]string, 0, len(cves))
|
||||||
for _, value := range 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 {
|
if len(cveIDs) > 0 {
|
||||||
rows = append(rows, []string{"CVE-ID", strings.Join(cveIDs, ",")})
|
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) {
|
func (jiraFormatter *Formatter) CreateTable(headers []string, rows [][]string) (string, error) {
|
||||||
table, err := jiraFormatter.MarkdownFormatter.CreateTable(headers, rows)
|
if len(headers) == 0 {
|
||||||
if err != nil {
|
return "", fmt.Errorf("no headers provided")
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
tableRows := strings.Split(table, "\n")
|
|
||||||
tableRowsWithoutHeaderSeparator := append(tableRows[:1], tableRows[2:]...)
|
var builder strings.Builder
|
||||||
return strings.Join(tableRowsWithoutHeaderSeparator, "\n"), nil
|
|
||||||
|
// 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 {
|
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
|
// Integration is a client for an issue tracker integration
|
||||||
|
|||||||
@ -3,10 +3,13 @@ package jira
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/model"
|
"github.com/projectdiscovery/nuclei/v3/pkg/model"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
|
"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/output"
|
||||||
|
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/format"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters"
|
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -14,7 +17,18 @@ import (
|
|||||||
func TestLinkCreation(t *testing.T) {
|
func TestLinkCreation(t *testing.T) {
|
||||||
jiraIntegration := &Integration{}
|
jiraIntegration := &Integration{}
|
||||||
link := jiraIntegration.CreateLink("ProjectDiscovery", "https://projectdiscovery.io")
|
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) {
|
func TestHorizontalLineCreation(t *testing.T) {
|
||||||
@ -34,6 +48,7 @@ func TestTableCreation(t *testing.T) {
|
|||||||
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
expected := `| key | value |
|
expected := `| key | value |
|
||||||
|
|-----------|-----------|
|
||||||
| a | b |
|
| a | b |
|
||||||
| c | |
|
| c | |
|
||||||
| d | e |
|
| d | e |
|
||||||
@ -41,6 +56,129 @@ func TestTableCreation(t *testing.T) {
|
|||||||
require.Equal(t, expected, table)
|
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) {
|
func Test_ShouldFilter_Tracker(t *testing.T) {
|
||||||
jiraIntegration := &Integration{
|
jiraIntegration := &Integration{
|
||||||
options: &Options{AllowList: &filters.Filter{
|
options: &Options{AllowList: &filters.Filter{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user