2021-02-02 12:10:47 +05:30
|
|
|
package issues
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"os"
|
2021-02-08 01:43:51 +05:30
|
|
|
"strings"
|
2021-02-02 12:10:47 +05:30
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
"github.com/projectdiscovery/nuclei/v2/pkg/output"
|
|
|
|
|
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/issues/dedupe"
|
|
|
|
|
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/issues/github"
|
|
|
|
|
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/issues/gitlab"
|
|
|
|
|
"github.com/projectdiscovery/nuclei/v2/pkg/reporting/issues/jira"
|
|
|
|
|
"gopkg.in/yaml.v2"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Options is a configuration file for nuclei reporting module
|
|
|
|
|
type Options struct {
|
2021-02-08 01:43:51 +05:30
|
|
|
// AllowList contains a list of allowed events for reporting module
|
|
|
|
|
AllowList *Filter `yaml:"allow-list"`
|
|
|
|
|
// DenyList contains a list of denied events for reporting module
|
|
|
|
|
DenyList *Filter `yaml:"deny-list"`
|
2021-02-02 12:10:47 +05:30
|
|
|
// Github contains configuration options for Github Issue Tracker
|
|
|
|
|
Github *github.Options `yaml:"github"`
|
|
|
|
|
// Gitlab contains configuration options for Gitlab Issue Tracker
|
|
|
|
|
Gitlab *gitlab.Options `yaml:"gitlab"`
|
|
|
|
|
// Jira contains configuration options for Jira Issue Tracker
|
|
|
|
|
Jira *jira.Options `yaml:"jira"`
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-08 01:43:51 +05:30
|
|
|
// Filter filters the received event and decides whether to perform
|
|
|
|
|
// reporting for it or not.
|
|
|
|
|
type Filter struct {
|
|
|
|
|
Severity string `yaml:"severity"`
|
|
|
|
|
severity []string
|
|
|
|
|
Tags string `yaml:"tags"`
|
|
|
|
|
tags []string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compile compiles the filter creating match structures.
|
|
|
|
|
func (f *Filter) Compile() {
|
|
|
|
|
parts := strings.Split(f.Severity, ",")
|
|
|
|
|
for _, part := range parts {
|
|
|
|
|
f.severity = append(f.severity, strings.TrimSpace(part))
|
|
|
|
|
}
|
|
|
|
|
parts = strings.Split(f.Tags, ",")
|
|
|
|
|
for _, part := range parts {
|
|
|
|
|
f.tags = append(f.tags, strings.TrimSpace(part))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetMatch returns true if a filter matches result event
|
|
|
|
|
func (f *Filter) GetMatch(event *output.ResultEvent) bool {
|
|
|
|
|
severity := event.Info["severity"]
|
|
|
|
|
if len(f.severity) > 0 {
|
|
|
|
|
if stringSliceContains(f.severity, severity) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tags := event.Info["tags"]
|
|
|
|
|
tagParts := strings.Split(tags, ",")
|
|
|
|
|
for i, tag := range tagParts {
|
|
|
|
|
tagParts[i] = strings.TrimSpace(tag)
|
|
|
|
|
}
|
|
|
|
|
for _, tag := range f.tags {
|
|
|
|
|
if stringSliceContains(tagParts, tag) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-02 12:10:47 +05:30
|
|
|
// Tracker is an interface implemented by an issue tracker
|
|
|
|
|
type Tracker interface {
|
|
|
|
|
// CreateIssue creates an issue in the tracker
|
|
|
|
|
CreateIssue(event *output.ResultEvent) error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Client is a client for nuclei issue tracking module
|
|
|
|
|
type Client struct {
|
|
|
|
|
tracker Tracker
|
2021-02-08 01:43:51 +05:30
|
|
|
options *Options
|
2021-02-02 12:10:47 +05:30
|
|
|
dedupe *dedupe.Storage
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// New creates a new nuclei issue tracker reporting client
|
2021-02-07 23:41:33 +05:30
|
|
|
func New(config, db string) (*Client, error) {
|
2021-02-02 12:10:47 +05:30
|
|
|
file, err := os.Open(config)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "could not open reporting config file")
|
|
|
|
|
}
|
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
|
|
options := &Options{}
|
|
|
|
|
if err := yaml.NewDecoder(file).Decode(options); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2021-02-08 01:43:51 +05:30
|
|
|
if options.AllowList != nil {
|
|
|
|
|
options.AllowList.Compile()
|
|
|
|
|
}
|
|
|
|
|
if options.DenyList != nil {
|
|
|
|
|
options.DenyList.Compile()
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-02 12:10:47 +05:30
|
|
|
var tracker Tracker
|
|
|
|
|
if options.Github != nil {
|
|
|
|
|
tracker, err = github.New(options.Github)
|
|
|
|
|
}
|
|
|
|
|
if options.Gitlab != nil {
|
|
|
|
|
tracker, err = gitlab.New(options.Gitlab)
|
|
|
|
|
}
|
|
|
|
|
if options.Jira != nil {
|
|
|
|
|
tracker, err = jira.New(options.Jira)
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "could not create reporting client")
|
|
|
|
|
}
|
|
|
|
|
if tracker == nil {
|
|
|
|
|
return nil, errors.New("no issue tracker configuration found")
|
|
|
|
|
}
|
2021-02-07 23:41:33 +05:30
|
|
|
storage, err := dedupe.New(db)
|
2021-02-02 12:10:47 +05:30
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2021-02-08 01:43:51 +05:30
|
|
|
return &Client{tracker: tracker, dedupe: storage, options: options}, nil
|
2021-02-02 12:10:47 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close closes the issue tracker reporting client
|
|
|
|
|
func (c *Client) Close() {
|
|
|
|
|
c.dedupe.Close()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CreateIssue creates an issue in the tracker
|
|
|
|
|
func (c *Client) CreateIssue(event *output.ResultEvent) error {
|
2021-02-08 01:43:51 +05:30
|
|
|
if c.options.AllowList != nil && !c.options.AllowList.GetMatch(event) {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if c.options.DenyList != nil && c.options.DenyList.GetMatch(event) {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-02 12:10:47 +05:30
|
|
|
found, err := c.dedupe.Index(event)
|
|
|
|
|
if err != nil {
|
|
|
|
|
c.tracker.CreateIssue(event)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if found {
|
|
|
|
|
return c.tracker.CreateIssue(event)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2021-02-08 01:43:51 +05:30
|
|
|
|
|
|
|
|
func stringSliceContains(slice []string, item string) bool {
|
|
|
|
|
for _, i := range slice {
|
|
|
|
|
if strings.EqualFold(i, item) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|