2021-09-19 16:26:47 +05:30
|
|
|
package markdown
|
2021-03-22 14:03:05 +05:30
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"os"
|
2021-08-23 14:53:37 +03:00
|
|
|
"path/filepath"
|
2021-03-22 14:03:05 +05:30
|
|
|
"strings"
|
|
|
|
|
|
2024-12-13 01:55:27 +03:00
|
|
|
"github.com/google/uuid"
|
2023-07-21 16:54:06 -04:00
|
|
|
"github.com/projectdiscovery/gologger"
|
|
|
|
|
|
2023-10-17 17:44:13 +05:30
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
|
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util"
|
|
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/reporting/format"
|
2023-07-21 16:54:06 -04:00
|
|
|
fileutil "github.com/projectdiscovery/utils/file"
|
2022-11-06 21:24:23 +01:00
|
|
|
stringsutil "github.com/projectdiscovery/utils/strings"
|
2021-03-22 14:03:05 +05:30
|
|
|
)
|
|
|
|
|
|
2023-01-15 17:30:34 +01:00
|
|
|
const indexFileName = "index.md"
|
2023-06-22 14:27:32 +03:00
|
|
|
const extension = ".md"
|
2023-01-15 17:30:34 +01:00
|
|
|
|
2021-03-22 14:03:05 +05:30
|
|
|
type Exporter struct {
|
|
|
|
|
directory string
|
|
|
|
|
options *Options
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-07 17:31:46 +03:00
|
|
|
// Options contains the configuration options for GitHub issue tracker client
|
2021-03-22 14:03:05 +05:30
|
|
|
type Options struct {
|
|
|
|
|
// Directory is the directory to export found results to
|
2024-01-27 01:36:25 +03:00
|
|
|
Directory string `yaml:"directory"`
|
|
|
|
|
OmitRaw bool `yaml:"omit-raw"`
|
|
|
|
|
SortMode string `yaml:"sort-mode"`
|
2021-03-22 14:03:05 +05:30
|
|
|
}
|
|
|
|
|
|
2021-09-19 16:26:47 +05:30
|
|
|
// New creates a new markdown exporter integration client based on options.
|
2021-03-22 14:03:05 +05:30
|
|
|
func New(options *Options) (*Exporter, error) {
|
|
|
|
|
directory := options.Directory
|
|
|
|
|
if options.Directory == "" {
|
|
|
|
|
dir, err := os.Getwd()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
directory = dir
|
|
|
|
|
}
|
2021-11-24 17:09:38 +02:00
|
|
|
_ = os.MkdirAll(directory, 0755)
|
2023-01-15 17:30:34 +01:00
|
|
|
|
|
|
|
|
// index generation header
|
2023-06-22 14:27:32 +03:00
|
|
|
dataHeader := util.CreateTableHeader("Hostname/IP", "Finding", "Severity")
|
2023-01-15 17:30:34 +01:00
|
|
|
|
|
|
|
|
err := os.WriteFile(filepath.Join(directory, indexFileName), []byte(dataHeader), 0644)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-22 14:03:05 +05:30
|
|
|
return &Exporter{options: options, directory: directory}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-19 16:26:47 +05:30
|
|
|
// Export exports a passed result event to markdown
|
2021-12-07 18:01:34 +02:00
|
|
|
func (exporter *Exporter) Export(event *output.ResultEvent) error {
|
2023-06-22 14:27:32 +03:00
|
|
|
// index file generation
|
|
|
|
|
file, err := os.OpenFile(filepath.Join(exporter.directory, indexFileName), os.O_APPEND|os.O_WRONLY, 0644)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
|
|
filename := createFileName(event)
|
2023-07-21 16:54:06 -04:00
|
|
|
|
|
|
|
|
// 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)
|
2023-06-22 14:27:32 +03:00
|
|
|
finding := event.TemplateID + " " + event.MatcherName
|
|
|
|
|
severity := event.Info.SeverityHolder.Severity.String()
|
|
|
|
|
|
|
|
|
|
_, err = file.WriteString(util.CreateTableRow(host, finding, severity))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dataBuilder := &bytes.Buffer{}
|
|
|
|
|
dataBuilder.WriteString(util.CreateHeading3(format.Summary(event)))
|
|
|
|
|
dataBuilder.WriteString("\n")
|
|
|
|
|
dataBuilder.WriteString(util.CreateHorizontalLine())
|
2024-01-27 01:36:25 +03:00
|
|
|
dataBuilder.WriteString(format.CreateReportDescription(event, util.MarkdownFormatter{}, exporter.options.OmitRaw))
|
2023-06-22 14:27:32 +03:00
|
|
|
data := dataBuilder.Bytes()
|
|
|
|
|
|
2023-07-21 16:54:06 -04:00
|
|
|
return os.WriteFile(filepath.Join(exporter.directory, subdirectory, filename), data, 0644)
|
2023-06-22 14:27:32 +03:00
|
|
|
}
|
2021-03-22 14:03:05 +05:30
|
|
|
|
2023-06-22 14:27:32 +03:00
|
|
|
func createFileName(event *output.ResultEvent) string {
|
2021-03-22 14:03:05 +05:30
|
|
|
filenameBuilder := &strings.Builder{}
|
2021-03-22 14:28:29 +05:30
|
|
|
filenameBuilder.WriteString(event.TemplateID)
|
|
|
|
|
filenameBuilder.WriteString("-")
|
2024-12-13 01:55:27 +03:00
|
|
|
filenameBuilder.WriteString(event.Host)
|
|
|
|
|
filenameBuilder.WriteString("-")
|
|
|
|
|
filenameBuilder.WriteString(uuid.NewString())
|
2021-09-26 18:20:05 +05:30
|
|
|
|
|
|
|
|
var suffix string
|
2021-09-25 21:40:38 +05:30
|
|
|
if event.MatcherName != "" {
|
2021-09-26 18:20:05 +05:30
|
|
|
suffix = event.MatcherName
|
2021-09-25 21:40:38 +05:30
|
|
|
} else if event.ExtractorName != "" {
|
2021-09-26 18:20:05 +05:30
|
|
|
suffix = event.ExtractorName
|
|
|
|
|
}
|
|
|
|
|
if suffix != "" {
|
|
|
|
|
filenameBuilder.WriteRune('-')
|
|
|
|
|
filenameBuilder.WriteString(event.MatcherName)
|
2021-09-25 21:40:38 +05:30
|
|
|
}
|
2023-06-22 14:27:32 +03:00
|
|
|
filenameBuilder.WriteString(extension)
|
|
|
|
|
return sanitizeFilename(filenameBuilder.String())
|
2021-03-22 14:03:05 +05:30
|
|
|
}
|
2021-06-05 18:01:08 +05:30
|
|
|
|
|
|
|
|
// Close closes the exporter after operation
|
2021-12-07 18:01:34 +02:00
|
|
|
func (exporter *Exporter) Close() error {
|
2021-06-05 18:01:08 +05:30
|
|
|
return nil
|
|
|
|
|
}
|
2021-08-26 02:43:58 +05:30
|
|
|
|
|
|
|
|
func sanitizeFilename(filename string) string {
|
|
|
|
|
if len(filename) > 256 {
|
|
|
|
|
filename = filename[0:255]
|
|
|
|
|
}
|
2022-10-25 09:32:35 +02:00
|
|
|
return stringsutil.ReplaceAll(filename, "_", "?", "/", ">", "|", ":", ";", "*", "<", "\"", "'", " ")
|
2021-08-26 02:43:58 +05:30
|
|
|
}
|