From 759ee3d5f8821c8134ff02800d7a52a98d584817 Mon Sep 17 00:00:00 2001 From: Keith Chason Date: Fri, 21 Jul 2023 16:54:06 -0400 Subject: [PATCH] Markdown Export Sorting (#3961) * Sort markdown exports by host, severity, or template * Switch default to empty string * use fileutil to create folder --------- Co-authored-by: Tarun Koyalwar --- v2/internal/runner/options.go | 7 ++++ v2/internal/runner/runner.go | 2 ++ .../reporting/exporters/markdown/markdown.go | 36 +++++++++++++++++-- v2/pkg/types/types.go | 2 ++ 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/v2/internal/runner/options.go b/v2/internal/runner/options.go index 3da37f45c4..ebbe3e66d4 100644 --- a/v2/internal/runner/options.go +++ b/v2/internal/runner/options.go @@ -407,6 +407,13 @@ func readEnvInputVars(options *types.Options) { options.GitLabTemplateDisableDownload = getBoolEnvValue("DISABLE_NUCLEI_TEMPLATES_GITLAB_DOWNLOAD") options.AwsTemplateDisableDownload = getBoolEnvValue("DISABLE_NUCLEI_TEMPLATES_AWS_DOWNLOAD") options.AzureTemplateDisableDownload = getBoolEnvValue("DISABLE_NUCLEI_TEMPLATES_AZURE_DOWNLOAD") + + // Options to modify the behavior of exporters + options.MarkdownExportSortMode = strings.ToLower(os.Getenv("MARKDOWN_EXPORT_SORT_MODE")) + // If the user has not specified a valid sort mode, use the default + if options.MarkdownExportSortMode != "template" && options.MarkdownExportSortMode != "severity" && options.MarkdownExportSortMode != "host" { + options.MarkdownExportSortMode = "" + } } func getBoolEnvValue(key string) bool { diff --git a/v2/internal/runner/runner.go b/v2/internal/runner/runner.go index 711ea769e2..01c49c9e95 100644 --- a/v2/internal/runner/runner.go +++ b/v2/internal/runner/runner.go @@ -346,12 +346,14 @@ func createReportingOptions(options *types.Options) (*reporting.Options, error) reportingOptions.MarkdownExporter = &markdown.Options{ Directory: options.MarkdownExportDirectory, IncludeRawPayload: !options.OmitRawRequests, + SortMode: options.MarkdownExportSortMode, } } else { reportingOptions = &reporting.Options{} reportingOptions.MarkdownExporter = &markdown.Options{ Directory: options.MarkdownExportDirectory, IncludeRawPayload: !options.OmitRawRequests, + SortMode: options.MarkdownExportSortMode, } } } diff --git a/v2/pkg/reporting/exporters/markdown/markdown.go b/v2/pkg/reporting/exporters/markdown/markdown.go index 362c65718e..e3458f767d 100644 --- a/v2/pkg/reporting/exporters/markdown/markdown.go +++ b/v2/pkg/reporting/exporters/markdown/markdown.go @@ -6,9 +6,12 @@ import ( "path/filepath" "strings" + "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/nuclei/v2/pkg/output" "github.com/projectdiscovery/nuclei/v2/pkg/reporting/exporters/markdown/util" "github.com/projectdiscovery/nuclei/v2/pkg/reporting/format" + fileutil "github.com/projectdiscovery/utils/file" stringsutil "github.com/projectdiscovery/utils/strings" ) @@ -25,6 +28,7 @@ type Options struct { // Directory is the directory to export found results to Directory string `yaml:"directory"` IncludeRawPayload bool `yaml:"include-raw-payload"` + SortMode string `yaml:"sort-mode"` } // New creates a new markdown exporter integration client based on options. @@ -69,7 +73,35 @@ func (exporter *Exporter) Export(event *output.ResultEvent) error { defer file.Close() filename := createFileName(event) - host := util.CreateLink(event.Host, filename) + + // If the sort mode is set to severity, host, or template, then we need to get a safe version of the name for a + // subdirectory to store the file in. + // This will allow us to sort the files into subdirectories based on the sort mode. The subdirectory will need to + // be created if it does not exist. + fileUrl := filename + subdirectory := "" + switch exporter.options.SortMode { + case "severity": + subdirectory = event.Info.SeverityHolder.Severity.String() + case "host": + subdirectory = event.Host + case "template": + subdirectory = event.TemplateID + } + if subdirectory != "" { + // Sanitize the subdirectory name to remove any characters that are not allowed in a directory name + subdirectory = sanitizeFilename(subdirectory) + + // Prepend the subdirectory name to the filename for the fileUrl + fileUrl = filepath.Join(subdirectory, filename) + + // Create the subdirectory if it does not exist + if err = fileutil.CreateFolders(filepath.Join(exporter.directory, subdirectory)); err != nil { + gologger.Warning().Msgf("Could not create subdirectory for markdown report: %s", err) + } + } + + host := util.CreateLink(event.Host, fileUrl) finding := event.TemplateID + " " + event.MatcherName severity := event.Info.SeverityHolder.Severity.String() @@ -85,7 +117,7 @@ func (exporter *Exporter) Export(event *output.ResultEvent) error { dataBuilder.WriteString(format.CreateReportDescription(event, util.MarkdownFormatter{})) data := dataBuilder.Bytes() - return os.WriteFile(filepath.Join(exporter.directory, filename), data, 0644) + return os.WriteFile(filepath.Join(exporter.directory, subdirectory, filename), data, 0644) } func createFileName(event *output.ResultEvent) string { diff --git a/v2/pkg/types/types.go b/v2/pkg/types/types.go index 10411ea3f0..48347a6008 100644 --- a/v2/pkg/types/types.go +++ b/v2/pkg/types/types.go @@ -93,6 +93,8 @@ type Options struct { ReportingConfig string // MarkdownExportDirectory is the directory to export reports in Markdown format MarkdownExportDirectory string + // MarkdownExportSortMode is the method to sort the markdown reports (options: severity, template, host, none) + MarkdownExportSortMode string // SarifExport is the file to export sarif output format to SarifExport string // CloudURL is the URL for the nuclei cloud endpoint