diff --git a/cmd/nuclei/main.go b/cmd/nuclei/main.go index de29d6581..8fe5b2957 100644 --- a/cmd/nuclei/main.go +++ b/cmd/nuclei/main.go @@ -123,6 +123,13 @@ func main() { runner.ParseOptions(options) + if options.ScanUploadFile != "" { + if err := runner.UploadResultsToCloud(options); err != nil { + gologger.Fatal().Msgf("could not upload scan results to cloud dashboard: %s\n", err) + } + return + } + nucleiRunner, err := runner.New(options) if err != nil { gologger.Fatal().Msgf("Could not create runner: %s\n", err) @@ -420,9 +427,11 @@ on extensive configurability, massive extensibility and ease of use.`) flagSet.CreateGroup("cloud", "Cloud", flagSet.DynamicVar(&pdcpauth, "auth", "true", "configure projectdiscovery cloud (pdcp) api key"), flagSet.StringVarP(&options.TeamID, "team-id", "tid", _pdcp.TeamIDEnv, "upload scan results to given team id (optional)"), - flagSet.BoolVarP(&options.EnableCloudUpload, "cloud-upload", "cup", false, "upload scan results to pdcp dashboard"), + flagSet.BoolVarP(&options.EnableCloudUpload, "cloud-upload", "cup", false, "upload scan results to pdcp dashboard [DEPRECATED use -dashboard]"), flagSet.StringVarP(&options.ScanID, "scan-id", "sid", "", "upload scan results to existing scan id (optional)"), flagSet.StringVarP(&options.ScanName, "scan-name", "sname", "", "scan name to set (optional)"), + flagSet.BoolVarP(&options.EnableCloudUpload, "dashboard", "pd", false, "upload / view nuclei results in projectdiscovery cloud (pdcp) UI dashboard"), + flagSet.StringVarP(&options.ScanUploadFile, "dashboard-upload", "pdu", "", "upload / view nuclei results file (jsonl) in projectdiscovery cloud (pdcp) UI dashboard"), ) flagSet.CreateGroup("Authentication", "Authentication", diff --git a/internal/runner/runner.go b/internal/runner/runner.go index bfb2bc64b..b36d8ed58 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -784,6 +784,52 @@ func (r *Runner) SaveResumeConfig(path string) error { return os.WriteFile(path, data, permissionutil.ConfigFilePermission) } +// upload existing scan results to cloud with progress +func UploadResultsToCloud(options *types.Options) error { + h := &pdcpauth.PDCPCredHandler{} + creds, err := h.GetCreds() + if err != nil { + return errors.Wrap(err, "could not get credentials for cloud upload") + } + ctx := context.TODO() + uploadWriter, err := pdcp.NewUploadWriter(ctx, creds) + if err != nil { + return errors.Wrap(err, "could not create upload writer") + } + if options.ScanID != "" { + _ = uploadWriter.SetScanID(options.ScanID) + } + if options.ScanName != "" { + uploadWriter.SetScanName(options.ScanName) + } + if options.TeamID != "" { + uploadWriter.SetTeamID(options.TeamID) + } + + // Open file to count the number of results first + file, err := os.Open(options.ScanUploadFile) + if err != nil { + return errors.Wrap(err, "could not open scan upload file") + } + defer file.Close() + + gologger.Info().Msgf("Uploading scan results to cloud dashboard from %s", options.ScanUploadFile) + dec := json.NewDecoder(file) + for dec.More() { + var r output.ResultEvent + err := dec.Decode(&r) + if err != nil { + gologger.Warning().Msgf("Could not decode jsonl: %s\n", err) + continue + } + if err = uploadWriter.Write(&r); err != nil { + gologger.Warning().Msgf("[%s] failed to upload: %s\n", r.TemplateID, err) + } + } + uploadWriter.Close() + return nil +} + type WalkFunc func(reflect.Value, reflect.StructField) // Walk traverses a struct and executes a callback function on each value in the struct. diff --git a/pkg/types/types.go b/pkg/types/types.go index cab1aacf5..51375e734 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -384,6 +384,8 @@ type Options struct { ScanID string // ScanName is the name of the scan to be uploaded ScanName string + // ScanUploadFile is the jsonl file to upload scan results to cloud + ScanUploadFile string // TeamID is the team ID to use for cloud upload TeamID string // JsConcurrency is the number of concurrent js routines to run