From aeddddc31f7faa14b22228406e2dc53bcc583a9f Mon Sep 17 00:00:00 2001 From: Chris Mandich Date: Mon, 12 Dec 2022 07:56:32 -0800 Subject: [PATCH] Add exporter for splunk hec reporting (#3008) * Update LICENSE.md * removing per project COC in favor of global one (#2983) * removing per project COC in favor of global one * using global security info * go mod update * Add Splunk HEC Exporter support to Nuclei * small refactor Co-authored-by: Jane <5116641+JaneX8@users.noreply.github.com> Co-authored-by: Sandeep Singh Co-authored-by: sandeep <8293321+ehsandeep@users.noreply.github.com> Co-authored-by: Mzack9999 --- CODE_OF_CONDUCT.md | 76 ---------- LICENSE.md | 2 +- SECURITY.md | 5 - .../reporting/exporters/splunk/splunkhec.go | 131 ++++++++++++++++++ v2/pkg/reporting/reporting.go | 12 ++ 5 files changed, 144 insertions(+), 82 deletions(-) delete mode 100644 CODE_OF_CONDUCT.md delete mode 100644 SECURITY.md create mode 100644 v2/pkg/reporting/exporters/splunk/splunkhec.go diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 7ff48c188..000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,76 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at contact@projectdiscovery.io. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq diff --git a/LICENSE.md b/LICENSE.md index b22968b6c..815df8a9b 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 ProjectDiscovery, Inc. +Copyright (c) 2022 ProjectDiscovery, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 699a1f3e9..000000000 --- a/SECURITY.md +++ /dev/null @@ -1,5 +0,0 @@ -# Security Policy - -## Reporting a Vulnerability - -DO NOT CREATE AN ISSUE to report a security problem. Instead, please send an email to security@projectdiscovery.io, and we will acknowledge it within 3 working days. diff --git a/v2/pkg/reporting/exporters/splunk/splunkhec.go b/v2/pkg/reporting/exporters/splunk/splunkhec.go new file mode 100644 index 000000000..a89028009 --- /dev/null +++ b/v2/pkg/reporting/exporters/splunk/splunkhec.go @@ -0,0 +1,131 @@ +package splunk + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "time" + + "github.com/corpix/uarand" + "github.com/pkg/errors" + "github.com/projectdiscovery/nuclei/v2/pkg/output" + "github.com/projectdiscovery/nuclei/v2/pkg/protocols/common/protocolstate" + "github.com/projectdiscovery/retryablehttp-go" +) + +// Options contains necessary options required for splunk communication +type Options struct { + // Host is the hostname and port of the splunk instance + Host string `yaml:"host" validate:"required"` + Port int `yaml:"port" validate:"gte=0,lte=65535"` + // SSL (optional) enables ssl for splunk connection + SSL bool `yaml:"ssl"` + // SSLVerification (optional) disables SSL verification for splunk + SSLVerification bool `yaml:"ssl-verification"` + // Token for HEC instance + Token string `yaml:"token" validate:"required"` + IndexName string `yaml:"index-name" validate:"required"` + + HttpClient *retryablehttp.Client `yaml:"-"` +} + +type data struct { + Event *output.ResultEvent `json:"event"` +} + +// Exporter type for splunk +type Exporter struct { + url string + authentication string + splunk *http.Client +} + +// New creates and returns a new exporter for splunk +func New(option *Options) (*Exporter, error) { + var ei *Exporter + + var client *http.Client + if option.HttpClient != nil { + client = option.HttpClient.HTTPClient + } else { + client = &http.Client{ + Timeout: 5 * time.Second, + Transport: &http.Transport{ + MaxIdleConns: 10, + MaxIdleConnsPerHost: 10, + DialContext: protocolstate.Dialer.Dial, + DialTLSContext: protocolstate.Dialer.DialTLS, + TLSClientConfig: &tls.Config{InsecureSkipVerify: option.SSLVerification}, + }, + } + } + + // preparing url for splunk + scheme := "http://" + if option.SSL { + scheme = "https://" + } + + // Authentication header for HEC + authentication := "Splunk " + option.Token + + // add HEC endpoint, index, source, sourcetype + addr := option.Host + if option.Port > 0 { + addr = net.JoinHostPort(addr, fmt.Sprint(option.Port)) + } + base_url := fmt.Sprintf("%s%s", scheme, addr) + sourcetype := "nuclei:splunk-hec:exporter:json" + url := fmt.Sprintf("%s/services/collector/event?index=%s&sourcetype=%s&source=%s", base_url, option.IndexName, sourcetype, base_url) + + ei = &Exporter{ + url: url, + authentication: authentication, + splunk: client, + } + return ei, nil +} + +// Export exports a passed result event to Splunk +func (exporter *Exporter) Export(event *output.ResultEvent) error { + // creating a request + req, err := http.NewRequest(http.MethodPost, exporter.url, nil) + if err != nil { + return errors.Wrap(err, "could not make request") + } + if len(exporter.authentication) > 0 { + req.Header.Add("Authorization", exporter.authentication) + } + req.Header.Set("User-Agent", uarand.GetRandom()) + req.Header.Add("Content-Type", "application/json") + + d := data{Event: event} + b, err := json.Marshal(&d) + if err != nil { + return err + } + req.Body = io.NopCloser(bytes.NewReader(b)) + + res, err := exporter.splunk.Do(req) + if err != nil { + return err + } + + b, err = io.ReadAll(res.Body) + if err != nil { + return errors.New(err.Error() + "error thrown by splunk " + string(b)) + } + if res.StatusCode >= http.StatusMultipleChoices { + return errors.New("splunk responded with an error: " + string(b)) + } + return nil +} + +// Close closes the exporter after operation +func (exporter *Exporter) Close() error { + return nil +} diff --git a/v2/pkg/reporting/reporting.go b/v2/pkg/reporting/reporting.go index a6fa8d4eb..a6504a605 100644 --- a/v2/pkg/reporting/reporting.go +++ b/v2/pkg/reporting/reporting.go @@ -17,6 +17,7 @@ import ( "github.com/projectdiscovery/nuclei/v2/pkg/reporting/exporters/es" "github.com/projectdiscovery/nuclei/v2/pkg/reporting/exporters/markdown" "github.com/projectdiscovery/nuclei/v2/pkg/reporting/exporters/sarif" + "github.com/projectdiscovery/nuclei/v2/pkg/reporting/exporters/splunk" "github.com/projectdiscovery/nuclei/v2/pkg/reporting/trackers/github" "github.com/projectdiscovery/nuclei/v2/pkg/reporting/trackers/gitlab" "github.com/projectdiscovery/nuclei/v2/pkg/reporting/trackers/jira" @@ -42,6 +43,8 @@ type Options struct { SarifExporter *sarif.Options `yaml:"sarif"` // ElasticsearchExporter contains configuration options for Elasticsearch Exporter Module ElasticsearchExporter *es.Options `yaml:"elasticsearch"` + // SplunkExporter contains configuration options for splunkhec Exporter Module + SplunkExporter *splunk.Options `yaml:"splunkhec"` HttpClient *retryablehttp.Client `yaml:"-"` } @@ -167,6 +170,14 @@ func New(options *Options, db string) (*Client, error) { } client.exporters = append(client.exporters, exporter) } + if options.SplunkExporter != nil { + options.SplunkExporter.HttpClient = options.HttpClient + exporter, err := splunk.New(options.SplunkExporter) + if err != nil { + return nil, errors.Wrap(err, exportClientCreationErrorMessage) + } + client.exporters = append(client.exporters, exporter) + } storage, err := dedupe.New(db) if err != nil { @@ -198,6 +209,7 @@ func CreateConfigIfNotExists() error { MarkdownExporter: &markdown.Options{}, SarifExporter: &sarif.Options{}, ElasticsearchExporter: &es.Options{}, + SplunkExporter: &splunk.Options{}, } reportingFile, err := os.Create(reportingConfig) if err != nil {